diff options
author | Marek Safar <marek.safar@gmail.com> | 2022-01-20 13:01:02 +0300 |
---|---|---|
committer | Marek Safar <marek.safar@gmail.com> | 2022-01-20 13:01:02 +0300 |
commit | cdc0adc432b275c379f6db3db90a731b58525335 (patch) | |
tree | cdd8c3e0471f0fd5d1c131987b01a3c5db131d8e | |
parent | 03aabb40d2a5d9b22c94bfc6e4fe5a5b85badb8f (diff) | |
parent | 79b43e8e72866f450dee8b59510cb5767b1491ec (diff) |
Merge remote-tracking branch 'upstream/master'
25 files changed, 1073 insertions, 281 deletions
diff --git a/Directory.Build.props b/Directory.Build.props index cb10f01..06f38c4 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -7,7 +7,7 @@ <PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign> <SignAssembly>true</SignAssembly> <AssemblyOriginatorKeyFile>$(MSBuildThisFileDirectory)\cecil.snk</AssemblyOriginatorKeyFile> - <DefineConstants Condition=" '$(TargetFramework)' == 'netstandard2.0' OR '$(TargetFramework)' == 'netcoreapp2.1' ">$(DefineConstants);NET_CORE</DefineConstants> + <DefineConstants Condition=" '$(TargetFramework)' == 'netstandard2.0' OR '$(TargetFramework)' == 'netcoreapp3.1' ">$(DefineConstants);NET_CORE</DefineConstants> <RootNamespace></RootNamespace> </PropertyGroup> <PropertyGroup Condition=" '$(TargetFramework)' == 'net40' "> diff --git a/Mono.Cecil.Cil/MethodBody.cs b/Mono.Cecil.Cil/MethodBody.cs index 1aecb57..c9236db 100644 --- a/Mono.Cecil.Cil/MethodBody.cs +++ b/Mono.Cecil.Cil/MethodBody.cs @@ -384,11 +384,16 @@ namespace Mono.Cecil.Cil { // resolve by walking the instructions from start and don't cache the result. int size = 0; for (int i = 0; i < items.Length; i++) { + // The array can be larger than the actual size, in which case its padded with nulls at the end + // so when we reach null, treat it as an end of the IL. + if (items [i] == null) + return new InstructionOffset (i == 0 ? items [0] : items [i - 1]); + if (size == offset) return new InstructionOffset (items [i]); if (size > offset) - return new InstructionOffset (items [i - 1]); + return new InstructionOffset (i == 0 ? items [0] : items [i - 1]); size += items [i].GetSize (); } @@ -407,15 +412,15 @@ namespace Mono.Cecil.Cil { // Allow for trailing null values in the case of // instructions.Size < instructions.Capacity if (item == null) - return new InstructionOffset (items [i - 1]); + return new InstructionOffset (i == 0 ? items [0] : items [i - 1]); cache.Instruction = item; if (cache.Offset == offset) return new InstructionOffset (cache.Instruction); - if (cache.Offset > offset) - return new InstructionOffset (items [i - 1]); + if (cache.Offset > offset) + return new InstructionOffset (i == 0 ? items [0] : items [i - 1]); size += item.GetSize (); } diff --git a/Mono.Cecil.Cil/PortablePdb.cs b/Mono.Cecil.Cil/PortablePdb.cs index aff6b29..6664bee 100644 --- a/Mono.Cecil.Cil/PortablePdb.cs +++ b/Mono.Cecil.Cil/PortablePdb.cs @@ -12,7 +12,7 @@ using System; using System.Collections.Generic; using System.IO; using System.IO.Compression; - +using System.Security.Cryptography; using Mono.Cecil.Metadata; using Mono.Cecil.PE; @@ -39,7 +39,7 @@ namespace Mono.Cecil.Cil { ISymbolReader GetSymbolReader (ModuleDefinition module, Disposable<Stream> symbolStream, string fileName) { - return new PortablePdbReader (ImageReader.ReadPortablePdb (symbolStream, fileName), module); + return new PortablePdbReader (ImageReader.ReadPortablePdb (symbolStream, fileName, out _), module); } } @@ -234,8 +234,8 @@ namespace Mono.Cecil.Cil { Mixin.CheckModule (module); Mixin.CheckFileName (fileName); - var file = File.OpenWrite (Mixin.GetPdbFileName (fileName)); - return GetSymbolWriter (module, Disposable.Owned (file as Stream)); + var file = File.Open (Mixin.GetPdbFileName (fileName), FileMode.OpenOrCreate, FileAccess.ReadWrite); + return GetSymbolWriter (module, Disposable.Owned (file as Stream), Disposable.NotOwned ((Stream)null)); } public ISymbolWriter GetSymbolWriter (ModuleDefinition module, Stream symbolStream) @@ -243,15 +243,18 @@ namespace Mono.Cecil.Cil { Mixin.CheckModule (module); Mixin.CheckStream (symbolStream); - return GetSymbolWriter (module, Disposable.NotOwned (symbolStream)); + // In order to compute the PDB checksum, the stream we're writing to needs to be able to + // seek and read as well. We can't assume this about a stream provided by the user. + // So in this case, create a memory stream to cache the PDB. + return GetSymbolWriter (module, Disposable.Owned (new MemoryStream() as Stream), Disposable.NotOwned (symbolStream)); } - ISymbolWriter GetSymbolWriter (ModuleDefinition module, Disposable<Stream> stream) + ISymbolWriter GetSymbolWriter (ModuleDefinition module, Disposable<Stream> stream, Disposable<Stream> final_stream) { var metadata = new MetadataBuilder (module, this); var writer = ImageWriter.CreateDebugWriter (module, metadata, stream); - return new PortablePdbWriter (metadata, module, writer); + return new PortablePdbWriter (metadata, module, writer, final_stream); } } @@ -260,9 +263,14 @@ namespace Mono.Cecil.Cil { readonly MetadataBuilder pdb_metadata; readonly ModuleDefinition module; readonly ImageWriter writer; + readonly Disposable<Stream> final_stream; MetadataBuilder module_metadata; + internal byte [] pdb_checksum; + internal Guid pdb_id_guid; + internal uint pdb_id_stamp; + bool IsEmbedded { get { return writer == null; } } internal PortablePdbWriter (MetadataBuilder pdb_metadata, ModuleDefinition module) @@ -278,10 +286,11 @@ namespace Mono.Cecil.Cil { pdb_metadata.AddCustomDebugInformations (module); } - internal PortablePdbWriter (MetadataBuilder pdb_metadata, ModuleDefinition module, ImageWriter writer) + internal PortablePdbWriter (MetadataBuilder pdb_metadata, ModuleDefinition module, ImageWriter writer, Disposable<Stream> final_stream) : this (pdb_metadata, module) { this.writer = writer; + this.final_stream = final_stream; } public ISymbolReaderProvider GetReaderProvider () @@ -289,45 +298,88 @@ namespace Mono.Cecil.Cil { return new PortablePdbReaderProvider (); } + public void Write (MethodDebugInformation info) + { + CheckMethodDebugInformationTable (); + + pdb_metadata.AddMethodDebugInformation (info); + } + + public void Write () + { + if (IsEmbedded) + return; + + WritePdbFile (); + + if (final_stream.value != null) { + writer.BaseStream.Seek (0, SeekOrigin.Begin); + var buffer = new byte [8192]; + CryptoService.CopyStreamChunk (writer.BaseStream, final_stream.value, buffer, (int)writer.BaseStream.Length); + } + } + public ImageDebugHeader GetDebugHeader () { if (IsEmbedded) return new ImageDebugHeader (); - var directory = new ImageDebugDirectory () { - MajorVersion = 256, - MinorVersion = 20557, - Type = ImageDebugType.CodeView, - TimeDateStamp = (int) module.timestamp, - }; - - var buffer = new ByteBuffer (); - // RSDS - buffer.WriteUInt32 (0x53445352); - // Module ID - buffer.WriteBytes (module.Mvid.ToByteArray ()); - // PDB Age - buffer.WriteUInt32 (1); - // PDB Path - var fileName = writer.BaseStream.GetFileName (); - if (string.IsNullOrEmpty (fileName)) { - fileName = module.Assembly.Name.Name + ".pdb"; + ImageDebugHeaderEntry codeViewEntry; + { + var codeViewDirectory = new ImageDebugDirectory () { + MajorVersion = 256, + MinorVersion = 20557, + Type = ImageDebugType.CodeView, + TimeDateStamp = (int)pdb_id_stamp, + }; + + var buffer = new ByteBuffer (); + // RSDS + buffer.WriteUInt32 (0x53445352); + // Module ID + buffer.WriteBytes (pdb_id_guid.ToByteArray ()); + // PDB Age + buffer.WriteUInt32 (1); + // PDB Path + var fileName = writer.BaseStream.GetFileName (); + if (string.IsNullOrEmpty (fileName)) { + fileName = module.Assembly.Name.Name + ".pdb"; + } + buffer.WriteBytes (System.Text.Encoding.UTF8.GetBytes (fileName)); + buffer.WriteByte (0); + + var data = new byte [buffer.length]; + Buffer.BlockCopy (buffer.buffer, 0, data, 0, buffer.length); + codeViewDirectory.SizeOfData = data.Length; + + codeViewEntry = new ImageDebugHeaderEntry (codeViewDirectory, data); } - buffer.WriteBytes (System.Text.Encoding.UTF8.GetBytes (fileName)); - buffer.WriteByte (0); - var data = new byte [buffer.length]; - Buffer.BlockCopy (buffer.buffer, 0, data, 0, buffer.length); - directory.SizeOfData = data.Length; + ImageDebugHeaderEntry pdbChecksumEntry; + { + var pdbChecksumDirectory = new ImageDebugDirectory () { + MajorVersion = 1, + MinorVersion = 0, + Type = ImageDebugType.PdbChecksum, + TimeDateStamp = 0 + }; - return new ImageDebugHeader (new ImageDebugHeaderEntry (directory, data)); - } + var buffer = new ByteBuffer (); + // SHA256 - Algorithm name + buffer.WriteBytes (System.Text.Encoding.UTF8.GetBytes ("SHA256")); + buffer.WriteByte (0); - public void Write (MethodDebugInformation info) - { - CheckMethodDebugInformationTable (); + // Checksum - 32 bytes + buffer.WriteBytes (pdb_checksum); - pdb_metadata.AddMethodDebugInformation (info); + var data = new byte [buffer.length]; + Buffer.BlockCopy (buffer.buffer, 0, data, 0, buffer.length); + pdbChecksumDirectory.SizeOfData = data.Length; + + pdbChecksumEntry = new ImageDebugHeaderEntry (pdbChecksumDirectory, data); + } + + return new ImageDebugHeader (new ImageDebugHeaderEntry [] { codeViewEntry, pdbChecksumEntry }); } void CheckMethodDebugInformationTable () @@ -343,10 +395,8 @@ namespace Mono.Cecil.Cil { public void Dispose () { - if (IsEmbedded) - return; - - WritePdbFile (); + writer.stream.Dispose (); + final_stream.Dispose (); } void WritePdbFile () @@ -360,15 +410,18 @@ namespace Mono.Cecil.Cil { writer.WriteMetadata (); writer.Flush (); - writer.stream.Dispose (); + + ComputeChecksumAndPdbId (); + + WritePdbId (); } void WritePdbHeap () { var pdb_heap = pdb_metadata.pdb_heap; - pdb_heap.WriteBytes (module.Mvid.ToByteArray ()); - pdb_heap.WriteUInt32 (module_metadata.timestamp); + // PDB ID ( GUID + TimeStamp ) are left zeroed out for now. Will be filled at the end with a hash. + pdb_heap.WriteBytes (20); pdb_heap.WriteUInt32 (module_metadata.entry_point.ToUInt32 ()); @@ -399,6 +452,32 @@ namespace Mono.Cecil.Cil { pdb_metadata.table_heap.ComputeTableInformations (); pdb_metadata.table_heap.WriteTableHeap (); } + + void ComputeChecksumAndPdbId () + { + var buffer = new byte [8192]; + + // Compute the has of the entire file - PDB ID is zeroes still + writer.BaseStream.Seek (0, SeekOrigin.Begin); + var sha256 = SHA256.Create (); + using (var crypto_stream = new CryptoStream (Stream.Null, sha256, CryptoStreamMode.Write)) { + CryptoService.CopyStreamChunk (writer.BaseStream, crypto_stream, buffer, (int)writer.BaseStream.Length); + } + + pdb_checksum = sha256.Hash; + + var hashBytes = new ByteBuffer (pdb_checksum); + pdb_id_guid = new Guid (hashBytes.ReadBytes (16)); + pdb_id_stamp = hashBytes.ReadUInt32 (); + } + + void WritePdbId () + { + // PDB ID is the first 20 bytes of the PdbHeap + writer.MoveToRVA (TextSegment.PdbHeap); + writer.WriteBytes (pdb_id_guid.ToByteArray ()); + writer.WriteUInt32 (pdb_id_stamp); + } } public sealed class EmbeddedPortablePdbWriterProvider : ISymbolWriterProvider { @@ -435,9 +514,14 @@ namespace Mono.Cecil.Cil { return new EmbeddedPortablePdbReaderProvider (); } + public void Write (MethodDebugInformation info) + { + writer.Write (info); + } + public ImageDebugHeader GetDebugHeader () { - writer.Dispose (); + ImageDebugHeader pdbDebugHeader = writer.GetDebugHeader (); var directory = new ImageDebugDirectory { Type = ImageDebugType.EmbeddedPortablePdb, @@ -462,19 +546,22 @@ namespace Mono.Cecil.Cil { directory.SizeOfData = (int) data.Length; - return new ImageDebugHeader (new [] { - writer.GetDebugHeader ().Entries [0], - new ImageDebugHeaderEntry (directory, data.ToArray ()) - }); + var debugHeaderEntries = new ImageDebugHeaderEntry [pdbDebugHeader.Entries.Length + 1]; + for (int i = 0; i < pdbDebugHeader.Entries.Length; i++) + debugHeaderEntries [i] = pdbDebugHeader.Entries [i]; + debugHeaderEntries [debugHeaderEntries.Length - 1] = new ImageDebugHeaderEntry (directory, data.ToArray ()); + + return new ImageDebugHeader (debugHeaderEntries); } - public void Write (MethodDebugInformation info) + public void Write () { - writer.Write (info); + writer.Write (); } public void Dispose () { + writer.Dispose (); } } diff --git a/Mono.Cecil.Cil/Symbols.cs b/Mono.Cecil.Cil/Symbols.cs index 5e92b67..744e7a8 100644 --- a/Mono.Cecil.Cil/Symbols.cs +++ b/Mono.Cecil.Cil/Symbols.cs @@ -39,6 +39,7 @@ namespace Mono.Cecil.Cil { CodeView = 2, Deterministic = 16, EmbeddedPortablePdb = 17, + PdbChecksum = 19, } public sealed class ImageDebugHeader { @@ -1114,6 +1115,7 @@ namespace Mono.Cecil.Cil { ISymbolReaderProvider GetReaderProvider (); ImageDebugHeader GetDebugHeader (); void Write (MethodDebugInformation info); + void Write (); } public interface ISymbolWriterProvider { @@ -1174,6 +1176,11 @@ namespace Mono.Cecil { return GetEntry (header, ImageDebugType.EmbeddedPortablePdb); } + public static ImageDebugHeaderEntry GetPdbChecksumEntry (this ImageDebugHeader header) + { + return GetEntry (header, ImageDebugType.PdbChecksum); + } + private static ImageDebugHeaderEntry GetEntry (this ImageDebugHeader header, ImageDebugType type) { if (!header.HasEntries) diff --git a/Mono.Cecil.Metadata/Buffers.cs b/Mono.Cecil.Metadata/Buffers.cs index 0dbf568..b32dd43 100644 --- a/Mono.Cecil.Metadata/Buffers.cs +++ b/Mono.Cecil.Metadata/Buffers.cs @@ -256,17 +256,33 @@ namespace Mono.Cecil.Metadata { sealed class DataBuffer : ByteBuffer { + int buffer_align = 4; + public DataBuffer () : base (0) { } - public RVA AddData (byte [] data) + void Align (int align) + { + align--; + // Compute the number of bytes to align the current position. + // Values of 0 will be written. + WriteBytes (((position + align) & ~align) - position); + } + + public RVA AddData (byte [] data, int align) { + if (buffer_align < align) + buffer_align = align; + + Align (align); var rva = (RVA) position; WriteBytes (data); return rva; } + + public int BufferAlign => buffer_align; } abstract class HeapBuffer : ByteBuffer { diff --git a/Mono.Cecil.PE/ImageReader.cs b/Mono.Cecil.PE/ImageReader.cs index 5358129..a34e64d 100644 --- a/Mono.Cecil.PE/ImageReader.cs +++ b/Mono.Cecil.PE/ImageReader.cs @@ -27,6 +27,7 @@ namespace Mono.Cecil.PE { DataDirectory metadata; uint table_heap_offset; + uint pdb_heap_offset; public ImageReader (Disposable<Stream> stream, string file_name) : base (stream.value) @@ -400,6 +401,7 @@ namespace Mono.Cecil.PE { break; case "#Pdb": image.PdbHeap = new PdbHeap (data); + pdb_heap_offset = offset; break; } } @@ -768,7 +770,7 @@ namespace Mono.Cecil.PE { } } - public static Image ReadPortablePdb (Disposable<Stream> stream, string file_name) + public static Image ReadPortablePdb (Disposable<Stream> stream, string file_name, out uint pdb_heap_offset) { try { var reader = new ImageReader (stream, file_name); @@ -785,6 +787,7 @@ namespace Mono.Cecil.PE { reader.metadata = new DataDirectory (0, length); reader.ReadMetadata (); + pdb_heap_offset = reader.pdb_heap_offset; return reader.image; } catch (EndOfStreamException e) { throw new BadImageFormatException (stream.value.GetFileName (), e); diff --git a/Mono.Cecil.PE/ImageWriter.cs b/Mono.Cecil.PE/ImageWriter.cs index a8a3fa8..c2eb97a 100644 --- a/Mono.Cecil.PE/ImageWriter.cs +++ b/Mono.Cecil.PE/ImageWriter.cs @@ -48,6 +48,8 @@ namespace Mono.Cecil.PE { ushort sections; + internal long debug_header_entries_position; + ImageWriter (ModuleDefinition module, string runtime_version, MetadataBuilder metadata, Disposable<Stream> stream, bool metadataOnly = false) : base (stream.value) { @@ -64,7 +66,7 @@ namespace Mono.Cecil.PE { this.GetDebugHeader (); this.GetWin32Resources (); this.BuildTextMap (); - this.sections = (ushort) (has_reloc ? 2 : 1); // text + reloc? + this.sections = (ushort)(has_reloc ? 2 : 1); // text + reloc? } void GetDebugHeader () @@ -98,7 +100,7 @@ namespace Mono.Cecil.PE { public static ImageWriter CreateWriter (ModuleDefinition module, MetadataBuilder metadata, Disposable<Stream> stream) { - var writer = new ImageWriter (module, module.runtime_version, metadata, stream); + var writer = new ImageWriter (module, module.runtime_version, metadata, stream, metadataOnly: false); writer.BuildSections (); return writer; } @@ -379,7 +381,7 @@ namespace Mono.Cecil.PE { BaseStream.Seek (GetRVAFileOffset (section, rva), SeekOrigin.Begin); } - void MoveToRVA (TextSegment segment) + internal void MoveToRVA (TextSegment segment) { MoveToRVA (text, text_map.GetRVA (segment)); } @@ -600,7 +602,9 @@ namespace Mono.Cecil.PE { data_start += entry.Data.Length; } - + + debug_header_entries_position = BaseStream.Position; + for (var i = 0; i < debug_header.Entries.Length; i++) { var entry = debug_header.Entries [i]; WriteBytes (entry.Data); @@ -694,7 +698,7 @@ namespace Mono.Cecil.PE { map.AddMap (TextSegment.Code, metadata.code.length, !pe64 ? 4 : 16); map.AddMap (TextSegment.Resources, metadata.resources.length, 8); - map.AddMap (TextSegment.Data, metadata.data.length, 4); + map.AddMap (TextSegment.Data, metadata.data.length, metadata.data.BufferAlign); if (metadata.data.length > 0) metadata.table_heap.FixupData (map.GetRVA (TextSegment.Data)); map.AddMap (TextSegment.StrongNameSignature, GetStrongNameLength (), 4); diff --git a/Mono.Cecil/AssemblyWriter.cs b/Mono.Cecil/AssemblyWriter.cs index c83f997..5799f07 100644 --- a/Mono.Cecil/AssemblyWriter.cs +++ b/Mono.Cecil/AssemblyWriter.cs @@ -118,13 +118,16 @@ namespace Mono.Cecil { metadata.SetSymbolWriter (symbol_writer); BuildMetadata (module, metadata); - if (parameters.DeterministicMvid) - metadata.ComputeDeterministicMvid (); + if (symbol_writer != null) + symbol_writer.Write (); var writer = ImageWriter.CreateWriter (module, metadata, stream); stream.value.SetLength (0); writer.WriteImage (); + if (parameters.DeterministicMvid) + ComputeDeterministicMvid (writer, module); + if (parameters.HasStrongNameKey) CryptoService.StrongName (stream.value, writer, parameters); } @@ -156,6 +159,26 @@ namespace Mono.Cecil { return symbol_writer_provider.GetSymbolWriter (module, fq_name); } + + static void ComputeDeterministicMvid (ImageWriter writer, ModuleDefinition module) + { + long previousPosition = writer.BaseStream.Position; + writer.BaseStream.Seek(0, SeekOrigin.Begin); + + // The hash should be computed with the MVID set to all zeroes + // which it is - we explicitly write all zeroes GUID into the heap + // as the MVID. + // Same goes for strong name signature, which also already in the image but all zeroes right now. + Guid guid = CryptoService.ComputeGuid (CryptoService.ComputeHash (writer.BaseStream)); + + // The MVID GUID is always the first GUID in the GUID heap + writer.MoveToRVA (TextSegment.GuidHeap); + writer.WriteBytes (guid.ToByteArray ()); + writer.Flush (); + module.Mvid = guid; + + writer.BaseStream.Seek(previousPosition, SeekOrigin.Begin); + } } abstract class MetadataTable { @@ -1619,8 +1642,21 @@ namespace Mono.Cecil { void AddFieldRVA (FieldDefinition field) { var table = GetTable<FieldRVATable> (Table.FieldRVA); + + // To allow for safe implementation of metadata rewriters for code which uses CreateSpan<T> + // if the Field RVA refers to a locally defined type with a pack > 1, align the InitialValue + // to pack boundary. This logic is restricted to only being affected by metadata local to the module + // as PackingSize is only used when examining a type local to the module being written. + + int align = 1; + if (field.FieldType.IsDefinition && !field.FieldType.IsGenericInstance) { + var type = field.FieldType.Resolve (); + + if ((type.Module == module) && (type.PackingSize > 1)) + align = type.PackingSize; + } table.AddRow (new FieldRVARow ( - data.AddData (field.InitialValue), + data.AddData (field.InitialValue, align), field.token.RID)); } @@ -2642,25 +2678,6 @@ namespace Mono.Cecil { method_debug_information_table.rows [rid - 1].Col2 = GetBlobIndex (signature); } - - public void ComputeDeterministicMvid () - { - var guid = CryptoService.ComputeGuid (CryptoService.ComputeHash ( - data, - resources, - string_heap, - user_string_heap, - blob_heap, - table_heap, - code)); - - var position = guid_heap.position; - guid_heap.position = 0; - guid_heap.WriteBytes (guid.ToByteArray ()); - guid_heap.position = position; - - module.Mvid = guid; - } } sealed class SignatureWriter : ByteBuffer { diff --git a/Mono.Security.Cryptography/CryptoService.cs b/Mono.Security.Cryptography/CryptoService.cs index dd9613f..3877deb 100644 --- a/Mono.Security.Cryptography/CryptoService.cs +++ b/Mono.Security.Cryptography/CryptoService.cs @@ -109,7 +109,7 @@ namespace Mono.Cecil { return sha1.Hash; } - static void CopyStreamChunk (Stream stream, Stream dest_stream, byte [] buffer, int length) + public static void CopyStreamChunk (Stream stream, Stream dest_stream, byte [] buffer, int length) { while (length > 0) { int read = stream.Read (buffer, 0, System.Math.Min (buffer.Length, length)); diff --git a/Test/Mono.Cecil.Tests.csproj b/Test/Mono.Cecil.Tests.csproj index 2d3d8e4..f7762e3 100644 --- a/Test/Mono.Cecil.Tests.csproj +++ b/Test/Mono.Cecil.Tests.csproj @@ -1,9 +1,9 @@ <Project Sdk="Microsoft.NET.Sdk" ToolsVersion="Current"> <Import Project="..\Mono.Cecil.Tests.props" /> <PropertyGroup> - <TargetFrameworks>netcoreapp2.1;net40</TargetFrameworks> + <TargetFrameworks>netcoreapp3.1;net40</TargetFrameworks> </PropertyGroup> - <ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp2.1' "> + <ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp3.1' "> <PackageReference Include="Microsoft.CodeAnalysis.CSharp"> <Version>2.10.0</Version> </PackageReference> diff --git a/Test/Mono.Cecil.Tests/FieldTests.cs b/Test/Mono.Cecil.Tests/FieldTests.cs index 4f575de..93ed350 100644 --- a/Test/Mono.Cecil.Tests/FieldTests.cs +++ b/Test/Mono.Cecil.Tests/FieldTests.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using Mono.Cecil.PE; @@ -133,6 +134,49 @@ namespace Mono.Cecil.Tests { }); } + int AlignmentOfInteger(int input) + { + if (input == 0) + return 0x40000000; + if (input < 0) + Assert.Fail (); + int alignment = 1; + while ((input & alignment) == 0) + alignment *= 2; + + return alignment; + } + + [Test] + public void FieldRVAAlignment () + { + TestIL ("FieldRVAAlignment.il", ilmodule => { + + var path = Path.GetTempFileName (); + + ilmodule.Write (path); + + using (var module = ModuleDefinition.ReadModule (path, new ReaderParameters { ReadWrite = true })) { + var priv_impl = GetPrivateImplementationType (module); + Assert.IsNotNull (priv_impl); + + Assert.AreEqual (6, priv_impl.Fields.Count); + + foreach (var field in priv_impl.Fields) + { + Assert.IsNotNull (field); + + Assert.AreNotEqual (0, field.RVA); + Assert.IsNotNull (field.InitialValue); + + int rvaAlignment = AlignmentOfInteger (field.RVA); + int desiredAlignment = Math.Min(8, AlignmentOfInteger (field.InitialValue.Length)); + Assert.GreaterOrEqual (rvaAlignment, desiredAlignment); + } + } + }); + } + [Test] public void GenericFieldDefinition () { diff --git a/Test/Mono.Cecil.Tests/ILProcessorTests.cs b/Test/Mono.Cecil.Tests/ILProcessorTests.cs index c585403..c1dc13c 100644 --- a/Test/Mono.Cecil.Tests/ILProcessorTests.cs +++ b/Test/Mono.Cecil.Tests/ILProcessorTests.cs @@ -152,16 +152,18 @@ namespace Mono.Cecil.Tests { AssertOpCodeSequence (new OpCode[] { }, method); } - [TestCase (RoundtripType.None, false, false)] - [TestCase (RoundtripType.Pdb, false, false)] - [TestCase (RoundtripType.Pdb, true, false)] - [TestCase (RoundtripType.Pdb, true, true)] - [TestCase (RoundtripType.PortablePdb, false, false)] - [TestCase (RoundtripType.PortablePdb, true, false)] - [TestCase (RoundtripType.PortablePdb, true, true)] - public void InsertAfterWithSymbolRoundtrip (RoundtripType roundtripType, bool forceUnresolved, bool reverseScopes) + [TestCase (RoundtripType.None, false, false, false)] + [TestCase (RoundtripType.Pdb, false, false, false)] + [TestCase (RoundtripType.Pdb, true, false, false)] + [TestCase (RoundtripType.Pdb, true, false, true)] + [TestCase (RoundtripType.Pdb, true, true, false)] + [TestCase (RoundtripType.PortablePdb, false, false, false)] + [TestCase (RoundtripType.PortablePdb, true, false, false)] + [TestCase (RoundtripType.PortablePdb, true, false, true)] + [TestCase (RoundtripType.PortablePdb, true, true, false)] + public void InsertAfterWithSymbolRoundtrip (RoundtripType roundtripType, bool forceUnresolved, bool reverseScopes, bool padIL) { - var methodBody = CreateTestMethodWithLocalScopes (); + var methodBody = CreateTestMethodWithLocalScopes (padIL); methodBody = RoundtripMethodBody (methodBody, roundtripType, forceUnresolved, reverseScopes); var il = methodBody.GetILProcessor (); @@ -176,16 +178,18 @@ namespace Mono.Cecil.Tests { methodBody.Method.Module.Dispose (); } - [TestCase (RoundtripType.None, false, false)] - [TestCase (RoundtripType.Pdb, false, false)] - [TestCase (RoundtripType.Pdb, true, false)] - [TestCase (RoundtripType.Pdb, true, true)] - [TestCase (RoundtripType.PortablePdb, false, false)] - [TestCase (RoundtripType.PortablePdb, true, false)] - [TestCase (RoundtripType.PortablePdb, true, true)] - public void RemoveWithSymbolRoundtrip (RoundtripType roundtripType, bool forceUnresolved, bool reverseScopes) + [TestCase (RoundtripType.None, false, false, false)] + [TestCase (RoundtripType.Pdb, false, false, false)] + [TestCase (RoundtripType.Pdb, true, false, false)] + [TestCase (RoundtripType.Pdb, true, false, true)] + [TestCase (RoundtripType.Pdb, true, true, false)] + [TestCase (RoundtripType.PortablePdb, false, false, false)] + [TestCase (RoundtripType.PortablePdb, true, false, false)] + [TestCase (RoundtripType.PortablePdb, true, false, true)] + [TestCase (RoundtripType.PortablePdb, true, true, false)] + public void RemoveWithSymbolRoundtrip (RoundtripType roundtripType, bool forceUnresolved, bool reverseScopes, bool padIL) { - var methodBody = CreateTestMethodWithLocalScopes (); + var methodBody = CreateTestMethodWithLocalScopes (padIL); methodBody = RoundtripMethodBody (methodBody, roundtripType, forceUnresolved, reverseScopes); var il = methodBody.GetILProcessor (); @@ -200,16 +204,18 @@ namespace Mono.Cecil.Tests { methodBody.Method.Module.Dispose (); } - [TestCase (RoundtripType.None, false, false)] - [TestCase (RoundtripType.Pdb, false, false)] - [TestCase (RoundtripType.Pdb, true, false)] - [TestCase (RoundtripType.Pdb, true, true)] - [TestCase (RoundtripType.PortablePdb, false, false)] - [TestCase (RoundtripType.PortablePdb, true, false)] - [TestCase (RoundtripType.PortablePdb, true, true)] - public void ReplaceWithSymbolRoundtrip (RoundtripType roundtripType, bool forceUnresolved, bool reverseScopes) + [TestCase (RoundtripType.None, false, false, false)] + [TestCase (RoundtripType.Pdb, false, false, false)] + [TestCase (RoundtripType.Pdb, true, false, false)] + [TestCase (RoundtripType.Pdb, true, false, true)] + [TestCase (RoundtripType.Pdb, true, true, false)] + [TestCase (RoundtripType.PortablePdb, false, false, false)] + [TestCase (RoundtripType.PortablePdb, true, false, false)] + [TestCase (RoundtripType.PortablePdb, true, false, true)] + [TestCase (RoundtripType.PortablePdb, true, true, false)] + public void ReplaceWithSymbolRoundtrip (RoundtripType roundtripType, bool forceUnresolved, bool reverseScopes, bool padIL) { - var methodBody = CreateTestMethodWithLocalScopes (); + var methodBody = CreateTestMethodWithLocalScopes (padIL); methodBody = RoundtripMethodBody (methodBody, roundtripType, forceUnresolved, reverseScopes); var il = methodBody.GetILProcessor (); @@ -224,16 +230,18 @@ namespace Mono.Cecil.Tests { methodBody.Method.Module.Dispose (); } - [TestCase (RoundtripType.None, false, false)] - [TestCase (RoundtripType.Pdb, false, false)] - [TestCase (RoundtripType.Pdb, true, false)] - [TestCase (RoundtripType.Pdb, true, true)] - [TestCase (RoundtripType.PortablePdb, false, false)] - [TestCase (RoundtripType.PortablePdb, true, false)] - [TestCase (RoundtripType.PortablePdb, true, true)] - public void EditBodyWithScopesAndSymbolRoundtrip (RoundtripType roundtripType, bool forceUnresolved, bool reverseScopes) + [TestCase (RoundtripType.None, false, false, false)] + [TestCase (RoundtripType.Pdb, false, false, false)] + [TestCase (RoundtripType.Pdb, true, false, false)] + [TestCase (RoundtripType.Pdb, true, false, true)] + [TestCase (RoundtripType.Pdb, true, true, false)] + [TestCase (RoundtripType.PortablePdb, false, false, false)] + [TestCase (RoundtripType.PortablePdb, true, false, false)] + [TestCase (RoundtripType.PortablePdb, true, false, true)] + [TestCase (RoundtripType.PortablePdb, true, true, false)] + public void EditBodyWithScopesAndSymbolRoundtrip (RoundtripType roundtripType, bool forceUnresolved, bool reverseScopes, bool padIL) { - var methodBody = CreateTestMethodWithLocalScopes (); + var methodBody = CreateTestMethodWithLocalScopes (padIL); methodBody = RoundtripMethodBody (methodBody, roundtripType, forceUnresolved, reverseScopes); var il = methodBody.GetILProcessor (); @@ -320,13 +328,16 @@ namespace Mono.Cecil.Tests { Assert.IsTrue (scope.End.IsEndOfMethod); } - static MethodBody CreateTestMethodWithLocalScopes () + static MethodBody CreateTestMethodWithLocalScopes (bool padILWithNulls) { var module = ModuleDefinition.CreateModule ("TestILProcessor", ModuleKind.Dll); var type = new TypeDefinition ("NS", "TestType", TypeAttributes.Public | TypeAttributes.Abstract | TypeAttributes.Sealed, module.ImportReference (typeof (object))); module.Types.Add (type); var methodBody = CreateTestMethod (OpCodes.Nop, OpCodes.Ldloc_0, OpCodes.Nop, OpCodes.Ldloc_1, OpCodes.Nop, OpCodes.Ldloc_2, OpCodes.Nop); + if (padILWithNulls) + methodBody.Instructions.Capacity += 10; + var method = methodBody.Method; method.ReturnType = module.ImportReference (typeof (void)); type.Methods.Add (method); diff --git a/Test/Mono.Cecil.Tests/ImageReadTests.cs b/Test/Mono.Cecil.Tests/ImageReadTests.cs index ef6ab06..f663b60 100644 --- a/Test/Mono.Cecil.Tests/ImageReadTests.cs +++ b/Test/Mono.Cecil.Tests/ImageReadTests.cs @@ -237,9 +237,16 @@ namespace Mono.Cecil.Tests { var header = module.GetDebugHeader (); - Assert.AreEqual (2, header.Entries.Length); + Assert.IsTrue (header.Entries.Length >= 2); Assert.IsTrue (header.Entries.Any (e => e.Directory.Type == ImageDebugType.CodeView)); Assert.IsTrue (header.Entries.Any (e => e.Directory.Type == ImageDebugType.Deterministic)); + + // If read directly from a file the PdbChecksum may not be persent (in this test case it isn't) + // but when written through Cecil it will always be there. + if (header.Entries.Length > 2) { + Assert.AreEqual (3, header.Entries.Length); + Assert.IsTrue (header.Entries.Any (e => e.Directory.Type == ImageDebugType.PdbChecksum)); + } }, symbolReaderProvider: typeof (PortablePdbReaderProvider), symbolWriterProvider: typeof (PortablePdbWriterProvider)); } @@ -251,10 +258,17 @@ namespace Mono.Cecil.Tests { var header = module.GetDebugHeader (); - Assert.AreEqual (3, header.Entries.Length); + Assert.IsTrue (header.Entries.Length >= 3); Assert.IsTrue (header.Entries.Any (e => e.Directory.Type == ImageDebugType.CodeView)); Assert.IsTrue (header.Entries.Any (e => e.Directory.Type == ImageDebugType.Deterministic)); Assert.IsTrue (header.Entries.Any (e => e.Directory.Type == ImageDebugType.EmbeddedPortablePdb)); + + // If read directly from a file the PdbChecksum may not be persent (in this test case it isn't) + // but when written through Cecil it will always be there. + if (header.Entries.Length > 3) { + Assert.AreEqual (4, header.Entries.Length); + Assert.IsTrue (header.Entries.Any (e => e.Directory.Type == ImageDebugType.PdbChecksum)); + } }, symbolReaderProvider: typeof (EmbeddedPortablePdbReaderProvider), symbolWriterProvider: typeof (EmbeddedPortablePdbWriterProvider)); } } diff --git a/Test/Mono.Cecil.Tests/PortablePdbTests.cs b/Test/Mono.Cecil.Tests/PortablePdbTests.cs index d9786cb..bd524fd 100644 --- a/Test/Mono.Cecil.Tests/PortablePdbTests.cs +++ b/Test/Mono.Cecil.Tests/PortablePdbTests.cs @@ -1,6 +1,8 @@ using System; using System.IO; +using System.IO.Compression; using System.Linq; +using System.Security.Cryptography; using System.Text; using NUnit.Framework; @@ -357,15 +359,23 @@ namespace Mono.Cecil.Tests { var header = module.GetDebugHeader (); Assert.IsNotNull (header); - Assert.AreEqual (2, header.Entries.Length); + Assert.IsTrue (header.Entries.Length >= 2); - var cv = header.Entries [0]; + int i = 0; + var cv = header.Entries [i++]; Assert.AreEqual (ImageDebugType.CodeView, cv.Directory.Type); - var eppdb = header.Entries [1]; + if (header.Entries.Length > 2) { + Assert.AreEqual (3, header.Entries.Length); + var pdbChecksum = header.Entries [i++]; + Assert.AreEqual (ImageDebugType.PdbChecksum, pdbChecksum.Directory.Type); + } + + var eppdb = header.Entries [i++]; Assert.AreEqual (ImageDebugType.EmbeddedPortablePdb, eppdb.Directory.Type); Assert.AreEqual (0x0100, eppdb.Directory.MajorVersion); Assert.AreEqual (0x0100, eppdb.Directory.MinorVersion); + }, symbolReaderProvider: typeof (EmbeddedPortablePdbReaderProvider), symbolWriterProvider: typeof (EmbeddedPortablePdbWriterProvider)); } @@ -400,7 +410,7 @@ namespace Mono.Cecil.Tests { { TestModule ("PdbTarget.exe", test, symbolReaderProvider: typeof (PortablePdbReaderProvider), symbolWriterProvider: typeof (PortablePdbWriterProvider)); TestModule ("EmbeddedPdbTarget.exe", test, verify: !Platform.OnMono); - TestModule ("EmbeddedCompressedPdbTarget.exe", test, symbolReaderProvider: typeof(EmbeddedPortablePdbReaderProvider), symbolWriterProvider: typeof(EmbeddedPortablePdbWriterProvider)); + TestModule ("EmbeddedCompressedPdbTarget.exe", test, symbolReaderProvider: typeof(EmbeddedPortablePdbReaderProvider), symbolWriterProvider: typeof (EmbeddedPortablePdbWriterProvider)); } [Test] @@ -604,7 +614,7 @@ class Program } [Test] - public void PortablePdbLineInfo () + public void PortablePdbLineInfo() { TestModule ("line.exe", module => { var type = module.GetType ("Tests"); @@ -698,6 +708,11 @@ class Program symbol_writer.Write (info); } + public void Write () + { + symbol_writer.Write (); + } + public void Dispose () { symbol_writer.Dispose (); @@ -809,5 +824,310 @@ class Program } }); } + + [Test] + public void DoubleWriteAndReadWithDeterministicMvidAndVariousChanges () + { + Guid mvidIn, mvidARM64Out, mvidX64Out; + + const string resource = "mylib.dll"; + { + string destination = Path.GetTempFileName (); + + using (var module = GetResourceModule (resource, new ReaderParameters { ReadSymbols = true })) { + mvidIn = module.Mvid; + module.Architecture = TargetArchitecture.ARM64; // Can't use I386 as it writes different import table size -> differnt MVID + module.Write (destination, new WriterParameters { DeterministicMvid = true, WriteSymbols = true }); + } + + using (var module = ModuleDefinition.ReadModule (destination, new ReaderParameters { ReadSymbols = true })) { + mvidARM64Out = module.Mvid; + } + + Assert.AreNotEqual (mvidIn, mvidARM64Out); + } + + { + string destination = Path.GetTempFileName (); + + using (var module = GetResourceModule (resource, new ReaderParameters { ReadSymbols = true })) { + Assert.AreEqual (mvidIn, module.Mvid); + module.Architecture = TargetArchitecture.AMD64; + module.Write (destination, new WriterParameters { DeterministicMvid = true, WriteSymbols = true }); + } + + using (var module = ModuleDefinition.ReadModule (destination, new ReaderParameters { ReadSymbols = true })) { + mvidX64Out = module.Mvid; + } + + Assert.AreNotEqual (mvidARM64Out, mvidX64Out); + } + + { + string destination = Path.GetTempFileName (); + + using (var module = GetResourceModule (resource, new ReaderParameters { ReadSymbols = true })) { + Assert.AreEqual (mvidIn, module.Mvid); + module.Architecture = TargetArchitecture.AMD64; + module.timestamp = 42; + module.Write (destination, new WriterParameters { DeterministicMvid = true, WriteSymbols = true }); + } + + Guid mvidDifferentTimeStamp; + using (var module = ModuleDefinition.ReadModule (destination, new ReaderParameters { ReadSymbols = true })) { + mvidDifferentTimeStamp = module.Mvid; + } + + Assert.AreNotEqual (mvidX64Out, mvidDifferentTimeStamp); + } + } + + [Test] + public void ReadPortablePdbChecksum () + { + const string resource = "PdbChecksumLib.dll"; + + using (var module = GetResourceModule (resource, new ReaderParameters { ReadSymbols = true })) { + GetPdbChecksumData (module.GetDebugHeader (), out string algorithmName, out byte [] checksum); + Assert.AreEqual ("SHA256", algorithmName); + GetCodeViewPdbId (module, out byte[] pdbId); + + string pdbPath = Mixin.GetPdbFileName (module.FileName); + CalculatePdbChecksumAndId (pdbPath, out byte [] expectedChecksum, out byte [] expectedPdbId); + + CollectionAssert.AreEqual (expectedChecksum, checksum); + CollectionAssert.AreEqual (expectedPdbId, pdbId); + } + } + + [Test] + public void ReadEmbeddedPortablePdbChecksum () + { + const string resource = "EmbeddedPdbChecksumLib.dll"; + + using (var module = GetResourceModule (resource, new ReaderParameters { ReadSymbols = true })) { + var debugHeader = module.GetDebugHeader (); + GetPdbChecksumData (debugHeader, out string algorithmName, out byte [] checksum); + Assert.AreEqual ("SHA256", algorithmName); + GetCodeViewPdbId (module, out byte [] pdbId); + + GetEmbeddedPdb (debugHeader, out byte [] embeddedPdb); + CalculatePdbChecksumAndId (embeddedPdb, out byte [] expectedChecksum, out byte [] expectedPdbId); + + CollectionAssert.AreEqual (expectedChecksum, checksum); + CollectionAssert.AreEqual (expectedPdbId, pdbId); + } + } + + [Test] + public void WritePortablePdbChecksum () + { + const string resource = "PdbChecksumLib.dll"; + string destination = Path.GetTempFileName (); + + using (var module = GetResourceModule (resource, new ReaderParameters { ReadSymbols = true })) { + module.Write (destination, new WriterParameters { DeterministicMvid = true, WriteSymbols = true }); + } + + using (var module = ModuleDefinition.ReadModule (destination, new ReaderParameters { ReadSymbols = true })) { + GetPdbChecksumData (module.GetDebugHeader (), out string algorithmName, out byte [] checksum); + Assert.AreEqual ("SHA256", algorithmName); + GetCodeViewPdbId (module, out byte [] pdbId); + + string pdbPath = Mixin.GetPdbFileName (module.FileName); + CalculatePdbChecksumAndId (pdbPath, out byte [] expectedChecksum, out byte [] expectedPdbId); + + CollectionAssert.AreEqual (expectedChecksum, checksum); + CollectionAssert.AreEqual (expectedPdbId, pdbId); + } + } + + [Test] + public void WritePortablePdbToWriteOnlyStream () + { + const string resource = "PdbChecksumLib.dll"; + string destination = Path.GetTempFileName (); + + // Note that the module stream already requires read access even on writing to be able to compute strong name + using (var module = GetResourceModule (resource, new ReaderParameters { ReadSymbols = true })) + using (var pdbStream = new FileStream (destination + ".pdb", FileMode.Create, FileAccess.Write)) { + module.Write (destination, new WriterParameters { + DeterministicMvid = true, + WriteSymbols = true, + SymbolWriterProvider = new PortablePdbWriterProvider (), + SymbolStream = pdbStream + }); + } + } + + [Test] + public void DoubleWritePortablePdbDeterministicPdbId () + { + const string resource = "PdbChecksumLib.dll"; + string destination = Path.GetTempFileName (); + + using (var module = GetResourceModule (resource, new ReaderParameters { ReadSymbols = true })) { + module.Write (destination, new WriterParameters { DeterministicMvid = true, WriteSymbols = true }); + } + + byte [] pdbIdOne; + using (var module = ModuleDefinition.ReadModule (destination, new ReaderParameters { ReadSymbols = true })) { + string pdbPath = Mixin.GetPdbFileName (module.FileName); + CalculatePdbChecksumAndId (pdbPath, out byte [] expectedChecksum, out pdbIdOne); + } + + using (var module = GetResourceModule (resource, new ReaderParameters { ReadSymbols = true })) { + module.Write (destination, new WriterParameters { DeterministicMvid = true, WriteSymbols = true }); + } + + byte [] pdbIdTwo; + using (var module = ModuleDefinition.ReadModule (destination, new ReaderParameters { ReadSymbols = true })) { + string pdbPath = Mixin.GetPdbFileName (module.FileName); + CalculatePdbChecksumAndId (pdbPath, out byte [] expectedChecksum, out pdbIdTwo); + } + + CollectionAssert.AreEqual (pdbIdOne, pdbIdTwo); + } + + [Test] + public void WriteEmbeddedPortablePdbChecksum () + { + const string resource = "EmbeddedPdbChecksumLib.dll"; + string destination = Path.GetTempFileName (); + + using (var module = GetResourceModule (resource, new ReaderParameters { ReadSymbols = true })) { + module.Write (destination, new WriterParameters { DeterministicMvid = true, WriteSymbols = true }); + } + + using (var module = ModuleDefinition.ReadModule (destination, new ReaderParameters { ReadSymbols = true })) { + var debugHeader = module.GetDebugHeader (); + GetPdbChecksumData (debugHeader, out string algorithmName, out byte [] checksum); + Assert.AreEqual ("SHA256", algorithmName); + GetCodeViewPdbId (module, out byte [] pdbId); + + GetEmbeddedPdb (debugHeader, out byte [] embeddedPdb); + CalculatePdbChecksumAndId (embeddedPdb, out byte [] expectedChecksum, out byte [] expectedPdbId); + + CollectionAssert.AreEqual (expectedChecksum, checksum); + CollectionAssert.AreEqual (expectedPdbId, pdbId); + } + } + + [Test] + public void DoubleWriteEmbeddedPortablePdbChecksum () + { + const string resource = "EmbeddedPdbChecksumLib.dll"; + string destination = Path.GetTempFileName (); + + using (var module = GetResourceModule (resource, new ReaderParameters { ReadSymbols = true })) { + module.Write (destination, new WriterParameters { DeterministicMvid = true, WriteSymbols = true }); + } + + byte [] pdbIdOne; + using (var module = ModuleDefinition.ReadModule (destination, new ReaderParameters { ReadSymbols = true })) { + var debugHeader = module.GetDebugHeader (); + GetEmbeddedPdb (debugHeader, out byte [] embeddedPdb); + CalculatePdbChecksumAndId (embeddedPdb, out byte [] expectedChecksum, out pdbIdOne); + } + + using (var module = GetResourceModule (resource, new ReaderParameters { ReadSymbols = true })) { + module.Write (destination, new WriterParameters { DeterministicMvid = true, WriteSymbols = true }); + } + + byte [] pdbIdTwo; + using (var module = ModuleDefinition.ReadModule (destination, new ReaderParameters { ReadSymbols = true })) { + var debugHeader = module.GetDebugHeader (); + GetEmbeddedPdb (debugHeader, out byte [] embeddedPdb); + CalculatePdbChecksumAndId (embeddedPdb, out byte [] expectedChecksum, out pdbIdTwo); + } + + CollectionAssert.AreEqual (pdbIdOne, pdbIdTwo); + } + + private void GetEmbeddedPdb (ImageDebugHeader debugHeader, out byte [] embeddedPdb) + { + var entry = Mixin.GetEmbeddedPortablePdbEntry (debugHeader); + Assert.IsNotNull (entry); + + var compressed_stream = new MemoryStream (entry.Data); + var reader = new BinaryStreamReader (compressed_stream); + Assert.AreEqual (0x4244504D, reader.ReadInt32 ()); + var length = reader.ReadInt32 (); + var decompressed_stream = new MemoryStream (length); + + using (var deflate = new DeflateStream (compressed_stream, CompressionMode.Decompress, leaveOpen: true)) + deflate.CopyTo (decompressed_stream); + + embeddedPdb = decompressed_stream.ToArray (); + } + + private void GetPdbChecksumData (ImageDebugHeader debugHeader, out string algorithmName, out byte [] checksum) + { + var entry = Mixin.GetPdbChecksumEntry (debugHeader); + Assert.IsNotNull (entry); + + var length = Array.IndexOf (entry.Data, (byte)0, 0); + var bytes = new byte [length]; + Buffer.BlockCopy (entry.Data, 0, bytes, 0, length); + algorithmName = Encoding.UTF8.GetString (bytes); + int checksumSize = 0; + switch (algorithmName) { + case "SHA256": checksumSize = 32; break; + case "SHA384": checksumSize = 48; break; + case "SHA512": checksumSize = 64; break; + } + checksum = new byte [checksumSize]; + Buffer.BlockCopy (entry.Data, length + 1, checksum, 0, checksumSize); + } + + private void CalculatePdbChecksumAndId (string filePath, out byte [] pdbChecksum, out byte [] pdbId) + { + using (var fs = File.OpenRead (filePath)) + CalculatePdbChecksumAndId (fs, out pdbChecksum, out pdbId); + } + + private void CalculatePdbChecksumAndId (byte [] data, out byte [] pdbChecksum, out byte [] pdbId) + { + using (var pdb = new MemoryStream (data)) + CalculatePdbChecksumAndId (pdb, out pdbChecksum, out pdbId); + } + + private void CalculatePdbChecksumAndId (Stream pdbStream, out byte [] pdbChecksum, out byte [] pdbId) + { + // Get the offset of the PDB heap (this requires parsing several headers + // so it's easier to use the ImageReader directly for this) + Image image = ImageReader.ReadPortablePdb (new Disposable<Stream> (pdbStream, false), "test.pdb", out uint pdbHeapOffset); + pdbId = new byte [20]; + Array.Copy (image.PdbHeap.data, 0, pdbId, 0, 20); + + pdbStream.Seek (0, SeekOrigin.Begin); + byte [] rawBytes = pdbStream.ReadAll (); + + var bytes = new byte [rawBytes.Length]; + + Array.Copy (rawBytes, 0, bytes, 0, pdbHeapOffset); + + // Zero out the PDB ID (20 bytes) + for (int i = 0; i < 20; bytes [i + pdbHeapOffset] = 0, i++) ; + + Array.Copy (rawBytes, pdbHeapOffset + 20, bytes, pdbHeapOffset + 20, rawBytes.Length - pdbHeapOffset - 20); + + var sha256 = SHA256.Create (); + pdbChecksum = sha256.ComputeHash (bytes); + } + + static void GetCodeViewPdbId (ModuleDefinition module, out byte[] pdbId) + { + var header = module.GetDebugHeader (); + var cv = Mixin.GetCodeViewEntry (header); + Assert.IsNotNull (cv); + + CollectionAssert.AreEqual (new byte [] { 0x52, 0x53, 0x44, 0x53 }, cv.Data.Take (4)); + + ByteBuffer buffer = new ByteBuffer (20); + buffer.WriteBytes (cv.Data.Skip (4).Take (16).ToArray ()); + buffer.WriteInt32 (cv.Directory.TimeDateStamp); + pdbId = buffer.buffer; + } } } diff --git a/Test/Resources/assemblies/EmbeddedPdbChecksumLib.dll b/Test/Resources/assemblies/EmbeddedPdbChecksumLib.dll Binary files differnew file mode 100644 index 0000000..2de939b --- /dev/null +++ b/Test/Resources/assemblies/EmbeddedPdbChecksumLib.dll diff --git a/Test/Resources/assemblies/PdbChecksumLib.dll b/Test/Resources/assemblies/PdbChecksumLib.dll Binary files differnew file mode 100644 index 0000000..9c28db7 --- /dev/null +++ b/Test/Resources/assemblies/PdbChecksumLib.dll diff --git a/Test/Resources/assemblies/PdbChecksumLib.pdb b/Test/Resources/assemblies/PdbChecksumLib.pdb Binary files differnew file mode 100644 index 0000000..81975e7 --- /dev/null +++ b/Test/Resources/assemblies/PdbChecksumLib.pdb diff --git a/Test/Resources/il/FieldRVAAlignment.il b/Test/Resources/il/FieldRVAAlignment.il new file mode 100644 index 0000000..8149f7a --- /dev/null +++ b/Test/Resources/il/FieldRVAAlignment.il @@ -0,0 +1,71 @@ +.assembly extern mscorlib +{ + .publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4.. + .ver 4:0:0:0 +} +.assembly FieldRVAAlignment {} + +.module FieldRVAAlignment.dll + +.class private auto ansi '<PrivateImplementationDetails>{9B33BB20-87EF-4094-9948-34882DB2F001}' + extends [mscorlib]System.Object +{ + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + .class explicit ansi sealed nested private '__StaticArrayInitTypeSize=3' + extends [mscorlib]System.ValueType + { + .pack 1 + .size 3 + } // end of class '__StaticArrayInitTypeSize=3' + + .class explicit ansi sealed nested private '__StaticArrayInitTypeSize=16' + extends [mscorlib]System.ValueType + { + .pack 8 + .size 16 + } // end of class '__StaticArrayInitTypeSize=16' + + .class explicit ansi sealed nested private '__StaticArrayInitTypeSize=20' + extends [mscorlib]System.ValueType + { + .pack 4 + .size 20 + } // end of class '__StaticArrayInitTypeSize=20' + + .class explicit ansi sealed nested private '__StaticArrayInitTypeSize=5' + extends [mscorlib]System.ValueType + { + .pack 1 + .size 5 + } // end of class '__StaticArrayInitTypeSize=5' + + .class explicit ansi sealed nested private '__StaticArrayInitTypeSize=6' + extends [mscorlib]System.ValueType + { + .pack 2 + .size 6 + } // end of class '__StaticArrayInitTypeSize=6' + + .field static assembly valuetype '<PrivateImplementationDetails>{9B33BB20-87EF-4094-9948-34882DB2F001}'/'__StaticArrayInitTypeSize=3' '$$method0x6000001-1' at I_000020F0 + .field static assembly valuetype '<PrivateImplementationDetails>{9B33BB20-87EF-4094-9948-34882DB2F001}'/'__StaticArrayInitTypeSize=20' '$$method0x6000001-2' at I_000020F8 + .field static assembly valuetype '<PrivateImplementationDetails>{9B33BB20-87EF-4094-9948-34882DB2F001}'/'__StaticArrayInitTypeSize=5' '$$method0x6000001-3' at I_00002108 + .field static assembly valuetype '<PrivateImplementationDetails>{9B33BB20-87EF-4094-9948-34882DB2F001}'/'__StaticArrayInitTypeSize=20' '$$method0x6000001-4' at I_00002110 + .field static assembly valuetype '<PrivateImplementationDetails>{9B33BB20-87EF-4094-9948-34882DB2F001}'/'__StaticArrayInitTypeSize=6' '$$method0x6000001-5' at I_00002120 + .field static assembly valuetype '<PrivateImplementationDetails>{9B33BB20-87EF-4094-9948-34882DB2F001}'/'__StaticArrayInitTypeSize=16' '$$method0x6000001-6' at I_00002130 +} // end of class '<PrivateImplementationDetails>{9B33BB20-87EF-4094-9948-34882DB2F001}' + + +// ============================================================= + +.data cil I_000020F0 = bytearray ( + 01 02 03) +.data cil I_000020F8 = bytearray ( + 01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 05 00 00 00) +.data cil I_00002108 = bytearray ( + 04 05 06 07 08) +.data cil I_00002110 = bytearray ( + 01 00 00 00 02 00 00 00 03 00 00 00 05 00 00 00 06 00 00 00) +.data cil I_00002120 = bytearray ( + 08 00 0C 00 0D 00) +.data cil I_00002130 = bytearray ( + 01 00 00 00 02 00 00 00 03 00 00 00 05 00 00 00) diff --git a/rocks/Mono.Cecil.Rocks/DocCommentId.cs b/rocks/Mono.Cecil.Rocks/DocCommentId.cs index da74bed..8af12dd 100644 --- a/rocks/Mono.Cecil.Rocks/DocCommentId.cs +++ b/rocks/Mono.Cecil.Rocks/DocCommentId.cs @@ -10,16 +10,18 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Text; namespace Mono.Cecil.Rocks { - public class DocCommentId - { + public class DocCommentId { + IMemberDefinition commentMember; StringBuilder id; - DocCommentId () + DocCommentId (IMemberDefinition member) { + commentMember = member; id = new StringBuilder (); } @@ -87,54 +89,66 @@ namespace Mono.Cecil.Rocks { void WriteTypeSignature (TypeReference type) { - switch (type.MetadataType) - { - case MetadataType.Array: - WriteArrayTypeSignature ((ArrayType) type); - break; - case MetadataType.ByReference: - WriteTypeSignature (((ByReferenceType) type).ElementType); - id.Append ('@'); - break; - case MetadataType.FunctionPointer: - WriteFunctionPointerTypeSignature ((FunctionPointerType) type); - break; - case MetadataType.GenericInstance: - WriteGenericInstanceTypeSignature ((GenericInstanceType) type); - break; - case MetadataType.Var: + switch (type.MetadataType) { + case MetadataType.Array: + WriteArrayTypeSignature ((ArrayType)type); + break; + case MetadataType.ByReference: + WriteTypeSignature (((ByReferenceType)type).ElementType); + id.Append ('@'); + break; + case MetadataType.FunctionPointer: + WriteFunctionPointerTypeSignature ((FunctionPointerType)type); + break; + case MetadataType.GenericInstance: + WriteGenericInstanceTypeSignature ((GenericInstanceType)type); + break; + case MetadataType.Var: + if (IsGenericMethodTypeParameter (type)) id.Append ('`'); - id.Append (((GenericParameter) type).Position); - break; - case MetadataType.MVar: - id.Append ('`').Append ('`'); - id.Append (((GenericParameter) type).Position); - break; - case MetadataType.OptionalModifier: - WriteModiferTypeSignature ((OptionalModifierType) type, '!'); - break; - case MetadataType.RequiredModifier: - WriteModiferTypeSignature ((RequiredModifierType) type, '|'); - break; - case MetadataType.Pointer: - WriteTypeSignature (((PointerType) type).ElementType); - id.Append ('*'); - break; - default: - WriteTypeFullName (type); - break; + id.Append ('`'); + id.Append (((GenericParameter)type).Position); + break; + case MetadataType.MVar: + id.Append ('`').Append ('`'); + id.Append (((GenericParameter)type).Position); + break; + case MetadataType.OptionalModifier: + WriteModiferTypeSignature ((OptionalModifierType)type, '!'); + break; + case MetadataType.RequiredModifier: + WriteModiferTypeSignature ((RequiredModifierType)type, '|'); + break; + case MetadataType.Pointer: + WriteTypeSignature (((PointerType)type).ElementType); + id.Append ('*'); + break; + default: + WriteTypeFullName (type); + break; } } + bool IsGenericMethodTypeParameter (TypeReference type) + { + if (commentMember is MethodDefinition methodDefinition && type is GenericParameter genericParameter) + return methodDefinition.GenericParameters.Any (i => i.Name == genericParameter.Name); + + return false; + } + void WriteGenericInstanceTypeSignature (GenericInstanceType type) { if (type.ElementType.IsTypeSpecification ()) throw new NotSupportedException (); - WriteTypeFullName (type.ElementType, stripGenericArity: true); - id.Append ('{'); - WriteList (type.GenericArguments, WriteTypeSignature); - id.Append ('}'); + GenericTypeOptions options = new GenericTypeOptions { + IsArgument = true, + IsNestedType = type.IsNested, + Arguments = type.GenericArguments + }; + + WriteTypeFullName (type.ElementType, options); } void WriteList<T> (IList<T> list, Action<T> action) @@ -197,10 +211,15 @@ namespace Mono.Cecil.Rocks { WriteItemName (member.Name); } - void WriteTypeFullName (TypeReference type, bool stripGenericArity = false) + void WriteTypeFullName (TypeReference type) + { + WriteTypeFullName (type, GenericTypeOptions.Empty ()); + } + + void WriteTypeFullName (TypeReference type, GenericTypeOptions options) { if (type.DeclaringType != null) { - WriteTypeFullName (type.DeclaringType); + WriteTypeFullName (type.DeclaringType, options); id.Append ('.'); } @@ -211,18 +230,69 @@ namespace Mono.Cecil.Rocks { var name = type.Name; - if (stripGenericArity) { + if (options.IsArgument) { var index = name.LastIndexOf ('`'); if (index > 0) name = name.Substring (0, index); } id.Append (name); + + WriteGenericTypeParameters (type, options); + } + + void WriteGenericTypeParameters (TypeReference type, GenericTypeOptions options) + { + if (options.IsArgument && IsGenericType (type)) { + id.Append ('{'); + WriteList (GetGenericTypeArguments (type, options), WriteTypeSignature); + id.Append ('}'); + } + } + + static bool IsGenericType (TypeReference type) + { + // When the type is a nested type and that is defined in a generic class, + // the nested type will have generic parameters but sometimes that is not a generic type. + if (type.HasGenericParameters) { + var name = string.Empty; + var index = type.Name.LastIndexOf ('`'); + if (index >= 0) + name = type.Name.Substring (0, index); + + return type.Name.LastIndexOf ('`') == name.Length; + } + + return false; } + IList<TypeReference> GetGenericTypeArguments (TypeReference type, GenericTypeOptions options) + { + if (options.IsNestedType) { + var typeParameterCount = type.GenericParameters.Count; + var typeGenericArguments = options.Arguments.Skip (options.ArgumentIndex).Take (typeParameterCount).ToList (); + + options.ArgumentIndex += typeParameterCount; + + return typeGenericArguments; + } + + return options.Arguments; + } + + //int GetGenericTypeParameterCount (TypeReference type) + //{ + // var returnValue = 0; + // var index = type.Name.LastIndexOf ('`'); + // if (index >= 0) + // returnValue = int.Parse (type.Name.Substring (index + 1)); + + // return returnValue; + //} + void WriteItemName (string name) { - id.Append (name.Replace ('.', '#').Replace('<', '{').Replace('>', '}')); + id.Append (name.Replace('.', '#').Replace('<', '{').Replace('>', '}')); } public override string ToString () @@ -235,30 +305,44 @@ namespace Mono.Cecil.Rocks { if (member == null) throw new ArgumentNullException ("member"); - var documentId = new DocCommentId (); - - switch (member.MetadataToken.TokenType) - { - case TokenType.Field: - documentId.WriteField ((FieldDefinition) member); - break; - case TokenType.Method: - documentId.WriteMethod ((MethodDefinition) member); - break; - case TokenType.TypeDef: - documentId.WriteType ((TypeDefinition) member); - break; - case TokenType.Event: - documentId.WriteEvent ((EventDefinition) member); - break; - case TokenType.Property: - documentId.WriteProperty ((PropertyDefinition) member); - break; - default: - throw new NotSupportedException (member.FullName); + var documentId = new DocCommentId (member); + + switch (member.MetadataToken.TokenType) { + case TokenType.Field: + documentId.WriteField ((FieldDefinition)member); + break; + case TokenType.Method: + documentId.WriteMethod ((MethodDefinition)member); + break; + case TokenType.TypeDef: + documentId.WriteType ((TypeDefinition)member); + break; + case TokenType.Event: + documentId.WriteEvent ((EventDefinition)member); + break; + case TokenType.Property: + documentId.WriteProperty ((PropertyDefinition)member); + break; + default: + throw new NotSupportedException (member.FullName); } return documentId.ToString (); } + + class GenericTypeOptions { + public bool IsArgument { get; set; } + + public bool IsNestedType { get; set; } + + public IList<TypeReference> Arguments { get; set; } + + public int ArgumentIndex { get; set; } + + public static GenericTypeOptions Empty () + { + return new GenericTypeOptions (); + } + } } -} +}
\ No newline at end of file diff --git a/rocks/Test/Mono.Cecil.Rocks.Tests.csproj b/rocks/Test/Mono.Cecil.Rocks.Tests.csproj index 5bab176..a15fce1 100644 --- a/rocks/Test/Mono.Cecil.Rocks.Tests.csproj +++ b/rocks/Test/Mono.Cecil.Rocks.Tests.csproj @@ -1,7 +1,7 @@ <Project Sdk="Microsoft.NET.Sdk" ToolsVersion="Current"> <Import Project="..\..\Mono.Cecil.Tests.props" /> <PropertyGroup> - <TargetFrameworks>netcoreapp2.1;net40</TargetFrameworks> + <TargetFrameworks>netcoreapp3.1;net40</TargetFrameworks> </PropertyGroup> <ItemGroup> <ProjectReference Include="..\..\Mono.Cecil.csproj"> diff --git a/rocks/Test/Mono.Cecil.Tests/DocCommentIdTests.cs b/rocks/Test/Mono.Cecil.Tests/DocCommentIdTests.cs index 244d5ae..8cfc828 100644 --- a/rocks/Test/Mono.Cecil.Tests/DocCommentIdTests.cs +++ b/rocks/Test/Mono.Cecil.Tests/DocCommentIdTests.cs @@ -6,116 +6,181 @@ using NUnit.Framework; using Mono.Cecil.Rocks; -namespace N -{ +namespace N { - /// <summary> - /// ID string generated is "T:N.X". - /// </summary> - public class X : IX<KVP<string, int>> - { - /// <summary> - /// ID string generated is "M:N.X.#ctor". - /// </summary> - public X() { } + /// <summary> + /// ID string generated is "T:N.X". + /// </summary> + public class X : IX<KVP<string, int>> { + /// <summary> + /// ID string generated is "M:N.X.#ctor". + /// </summary> + public X () { } - /// <summary> - /// ID string generated is "M:N.X.#ctor(System.Int32)". - /// </summary> - /// <param name="i">Describe parameter.</param> - public X(int i) { } + /// <summary> + /// ID string generated is "M:N.X.#ctor(System.Int32)". + /// </summary> + /// <param name="i">Describe parameter.</param> + public X (int i) { } - /// <summary> - /// ID string generated is "F:N.X.q". - /// </summary> - public string q; + /// <summary> + /// ID string generated is "F:N.X.q". + /// </summary> + public string q; - /// <summary> - /// ID string generated is "F:N.X.PI". - /// </summary> - public const double PI = 3.14; + /// <summary> + /// ID string generated is "F:N.X.PI". + /// </summary> + public const double PI = 3.14; - /// <summary> - /// ID string generated is "M:N.X.f". - /// </summary> - public int f() { return 1; } + /// <summary> + /// ID string generated is "M:N.X.f". + /// </summary> + public int f () { return 1; } - /// <summary> - /// ID string generated is "M:N.X.bb(System.String,System.Int32@)". - /// </summary> - public int bb(string s, ref int y) { return 1; } + /// <summary> + /// ID string generated is "M:N.X.bb(System.String,System.Int32@)". + /// </summary> + public int bb (string s, ref int y) { return 1; } - /// <summary> - /// ID string generated is "M:N.X.gg(System.Int16[],System.Int32[0:,0:])". - /// </summary> - public int gg(short[] array1, int[,] array) { return 0; } + /// <summary> + /// ID string generated is "M:N.X.gg(System.Int16[],System.Int32[0:,0:])". + /// </summary> + public int gg (short [] array1, int [,] array) { return 0; } - /// <summary> - /// ID string generated is "M:N.X.op_Addition(N.X,N.X)". - /// </summary> - public static X operator +(X x, X xx) { return x; } + /// <summary> + /// ID string generated is "M:N.X.op_Addition(N.X,N.X)". + /// </summary> + public static X operator + (X x, X xx) { return x; } - /// <summary> - /// ID string generated is "P:N.X.prop". - /// </summary> - public int prop { get { return 1; } set { } } + /// <summary> + /// ID string generated is "P:N.X.prop". + /// </summary> + public int prop { get { return 1; } set { } } - /// <summary> - /// ID string generated is "E:N.X.d". - /// </summary> + /// <summary> + /// ID string generated is "E:N.X.d". + /// </summary> #pragma warning disable 67 - public event D d; + public event D d; #pragma warning restore 67 /// <summary> /// ID string generated is "P:N.X.Item(System.String)". /// </summary> - public int this[string s] { get { return 1; } } + public int this [string s] { get { return 1; } } - /// <summary> - /// ID string generated is "T:N.X.Nested". - /// </summary> - public class Nested { } + /// <summary> + /// ID string generated is "T:N.X.Nested". + /// </summary> + public class Nested { } - /// <summary> - /// ID string generated is "T:N.X.D". - /// </summary> - public delegate void D(int i); + /// <summary> + /// ID string generated is "T:N.X.D". + /// </summary> + public delegate void D (int i); - /// <summary> - /// ID string generated is "M:N.X.op_Explicit(N.X)~System.Int32". - /// </summary> - public static explicit operator int(X x) { return 1; } + /// <summary> + /// ID string generated is "M:N.X.op_Explicit(N.X)~System.Int32". + /// </summary> + public static explicit operator int (X x) { return 1; } - public static void Linq (IEnumerable<string> enumerable, Func<string> selector) - { - } + public static void Linq (IEnumerable<string> enumerable, Func<string> selector) + { + } - /// <summary> + /// <summary> /// ID string generated is "M:N.X.N#IX{N#KVP{System#String,System#Int32}}#IXA(N.KVP{System.String,System.Int32})" - /// </summary> - void IX<KVP<string, int>>.IXA (KVP<string, int> k) { } - } + /// </summary> + void IX<KVP<string, int>>.IXA (KVP<string, int> k) { } + } - public interface IX<K> - { + public interface IX<K> { void IXA (K k); } public class KVP<K, T> { } + + public class GenericMethod { + /// <summary> + /// ID string generated is "M:N.GenericMethod.WithNestedType``1(N.GenericType{``0}.NestedType)". + /// </summary> + public void WithNestedType<T> (GenericType<T>.NestedType nestedType) { } + + + /// <summary> + /// ID string generated is "M:N.GenericMethod.WithIntOfNestedType``1(N.GenericType{System.Int32}.NestedType)". + /// </summary> + public void WithIntOfNestedType<T> (GenericType<int>.NestedType nestedType) { } + + + /// <summary> + /// ID string generated is "M:N.GenericMethod.WithNestedGenericType``1(N.GenericType{``0}.NestedGenericType{``0}.NestedType)". + /// </summary> + public void WithNestedGenericType<T> (GenericType<T>.NestedGenericType<T>.NestedType nestedType) { } + + + /// <summary> + /// ID string generated is "M:N.GenericMethod.WithIntOfNestedGenericType``1(N.GenericType{System.Int32}.NestedGenericType{System.Int32}.NestedType)". + /// </summary> + public void WithIntOfNestedGenericType<T> (GenericType<int>.NestedGenericType<int>.NestedType nestedType) { } + + + /// <summary> + /// ID string generated is "M:N.GenericMethod.WithMultipleTypeParameterAndNestedGenericType``2(N.GenericType{``0}.NestedGenericType{``1}.NestedType)". + /// </summary> + public void WithMultipleTypeParameterAndNestedGenericType<T1, T2> (GenericType<T1>.NestedGenericType<T2>.NestedType nestedType) { } + + + /// <summary> + /// ID string generated is "M:N.GenericMethod.WithMultipleTypeParameterAndIntOfNestedGenericType``2(N.GenericType{System.Int32}.NestedGenericType{System.Int32}.NestedType)". + /// </summary> + public void WithMultipleTypeParameterAndIntOfNestedGenericType<T1, T2> (GenericType<int>.NestedGenericType<int>.NestedType nestedType) { } + } + + public class GenericType<T> { + public class NestedType { } + + public class NestedGenericType<TNested> { + public class NestedType { } + + /// <summary> + /// ID string generated is "M:N.GenericType`1.NestedGenericType`1.WithTypeParameterOfGenericMethod``1(System.Collections.Generic.List{``0})" + /// </summary> + public void WithTypeParameterOfGenericMethod<TMethod> (List<TMethod> list) { } + + + /// <summary> + /// ID string generated is "M:N.GenericType`1.NestedGenericType`1.WithTypeParameterOfGenericType(System.Collections.Generic.Dictionary{`0,`1})" + /// </summary> + public void WithTypeParameterOfGenericType (Dictionary<T, TNested> dict) { } + + + /// <summary> + /// ID string generated is "M:N.GenericType`1.NestedGenericType`1.WithTypeParameterOfGenericType``1(System.Collections.Generic.List{`1})" + /// </summary> + public void WithTypeParameterOfNestedGenericType<TMethod> (List<TNested> list) { } + + + /// <summary> + /// ID string generated is "M:N.GenericType`1.NestedGenericType`1.WithTypeParameterOfGenericTypeAndGenericMethod``1(System.Collections.Generic.Dictionary{`1,``0})" + /// </summary> + public void WithTypeParameterOfGenericTypeAndGenericMethod<TMethod> (Dictionary<TNested, TMethod> dict) { } + } + } } namespace Mono.Cecil.Tests { @@ -182,7 +247,7 @@ namespace Mono.Cecil.Tests { AssertDocumentID ("M:N.X.bb(System.String,System.Int32@)", method); } - + [Test] public void MethodWithArrayParameters () { @@ -192,6 +257,32 @@ namespace Mono.Cecil.Tests { AssertDocumentID ("M:N.X.gg(System.Int16[],System.Int32[0:,0:])", method); } + [TestCase ("WithNestedType", "WithNestedType``1(N.GenericType{``0}.NestedType)")] + [TestCase ("WithIntOfNestedType", "WithIntOfNestedType``1(N.GenericType{System.Int32}.NestedType)")] + [TestCase ("WithNestedGenericType", "WithNestedGenericType``1(N.GenericType{``0}.NestedGenericType{``0}.NestedType)")] + [TestCase ("WithIntOfNestedGenericType", "WithIntOfNestedGenericType``1(N.GenericType{System.Int32}.NestedGenericType{System.Int32}.NestedType)")] + [TestCase ("WithMultipleTypeParameterAndNestedGenericType", "WithMultipleTypeParameterAndNestedGenericType``2(N.GenericType{``0}.NestedGenericType{``1}.NestedType)")] + [TestCase ("WithMultipleTypeParameterAndIntOfNestedGenericType", "WithMultipleTypeParameterAndIntOfNestedGenericType``2(N.GenericType{System.Int32}.NestedGenericType{System.Int32}.NestedType)")] + public void GenericMethodWithNestedTypeParameters (string methodName, string docCommentId) + { + var type = GetTestType (typeof (N.GenericMethod)); + var method = type.Methods.Single (m => m.Name == methodName); + + AssertDocumentID ($"M:N.GenericMethod.{docCommentId}", method); + } + + [TestCase ("WithTypeParameterOfGenericMethod", "WithTypeParameterOfGenericMethod``1(System.Collections.Generic.List{``0})")] + [TestCase ("WithTypeParameterOfGenericType", "WithTypeParameterOfGenericType(System.Collections.Generic.Dictionary{`0,`1})")] + [TestCase ("WithTypeParameterOfNestedGenericType", "WithTypeParameterOfNestedGenericType``1(System.Collections.Generic.List{`1})")] + [TestCase ("WithTypeParameterOfGenericTypeAndGenericMethod", "WithTypeParameterOfGenericTypeAndGenericMethod``1(System.Collections.Generic.Dictionary{`1,``0})")] + public void GenericTypeWithTypeParameters (string methodName, string docCommentId) + { + var type = GetTestType (typeof (N.GenericType<>.NestedGenericType<>)); + var method = type.Methods.Single (m => m.Name == methodName); + + AssertDocumentID ($"M:N.GenericType`1.NestedGenericType`1.{docCommentId}", method); + } + [Test] public void OpAddition () { @@ -268,7 +359,7 @@ namespace Mono.Cecil.Tests { public void EII () { var type = GetTestType (); - var method = type.Methods.Where (m => m.Name.Contains("IXA")).First (); + var method = type.Methods.Where (m => m.Name.Contains ("IXA")).First (); AssertDocumentID ("M:N.X.N#IX{N#KVP{System#String,System#Int32}}#IXA(N.KVP{System.String,System.Int32})", method); } @@ -278,9 +369,14 @@ namespace Mono.Cecil.Tests { return typeof (N.X).ToDefinition (); } + TypeDefinition GetTestType (Type type) + { + return type.ToDefinition (); + } + static void AssertDocumentID (string docId, IMemberDefinition member) { Assert.AreEqual (docId, DocCommentId.GetDocCommentId (member)); } } -} +}
\ No newline at end of file diff --git a/symbols/mdb/Mono.Cecil.Mdb/MdbWriter.cs b/symbols/mdb/Mono.Cecil.Mdb/MdbWriter.cs index 9b56518..4d4ce93 100644 --- a/symbols/mdb/Mono.Cecil.Mdb/MdbWriter.cs +++ b/symbols/mdb/Mono.Cecil.Mdb/MdbWriter.cs @@ -167,6 +167,12 @@ namespace Mono.Cecil.Mdb { return new ImageDebugHeader (); } + public void Write () + { + // Can't write it here since we need the final module MVID - which is only computed + // after the entire image of the assembly is written (since it's computed from the hash of that) + } + public void Dispose () { writer.WriteSymbolFile (module.Mvid); diff --git a/symbols/mdb/Test/Mono.Cecil.Mdb.Tests.csproj b/symbols/mdb/Test/Mono.Cecil.Mdb.Tests.csproj index 8475ec4..92da7f5 100644 --- a/symbols/mdb/Test/Mono.Cecil.Mdb.Tests.csproj +++ b/symbols/mdb/Test/Mono.Cecil.Mdb.Tests.csproj @@ -1,7 +1,7 @@ <Project Sdk="Microsoft.NET.Sdk" ToolsVersion="Current"> <Import Project="..\..\..\Mono.Cecil.Tests.props" /> <PropertyGroup> - <TargetFrameworks>netcoreapp2.1;net40</TargetFrameworks> + <TargetFrameworks>netcoreapp3.1;net40</TargetFrameworks> </PropertyGroup> <ItemGroup> <ProjectReference Include="..\..\..\Mono.Cecil.csproj"> diff --git a/symbols/pdb/Mono.Cecil.Pdb/NativePdbWriter.cs b/symbols/pdb/Mono.Cecil.Pdb/NativePdbWriter.cs index 01b6c7a..7bb9c6f 100644 --- a/symbols/pdb/Mono.Cecil.Pdb/NativePdbWriter.cs +++ b/symbols/pdb/Mono.Cecil.Pdb/NativePdbWriter.cs @@ -28,6 +28,9 @@ namespace Mono.Cecil.Pdb { readonly Dictionary<string, SymDocumentWriter> documents; readonly Dictionary<ImportDebugInformation, MetadataToken> import_info_to_parent; + ImageDebugDirectory debug_directory; + byte [] debug_info; + internal NativePdbWriter (ModuleDefinition module, SymWriter writer) { this.module = module; @@ -44,10 +47,7 @@ namespace Mono.Cecil.Pdb { public ImageDebugHeader GetDebugHeader () { - ImageDebugDirectory directory; - var data = writer.GetDebugInfo (out directory); - directory.TimeDateStamp = (int) module.timestamp; - return new ImageDebugHeader (new ImageDebugHeaderEntry (directory, data)); + return new ImageDebugHeader (new ImageDebugHeaderEntry (debug_directory, debug_info)); } public void Write (MethodDebugInformation info) @@ -253,14 +253,21 @@ namespace Mono.Cecil.Pdb { return doc_writer; } - public void Dispose () + public void Write () { var entry_point = module.EntryPoint; if (entry_point != null) writer.SetUserEntryPoint (entry_point.MetadataToken.ToInt32 ()); + debug_info = writer.GetDebugInfo (out debug_directory); + debug_directory.TimeDateStamp = (int)module.timestamp; + writer.Close (); } + + public void Dispose () + { + } } enum CustomMetadataType : byte { diff --git a/symbols/pdb/Test/Mono.Cecil.Pdb.Tests.csproj b/symbols/pdb/Test/Mono.Cecil.Pdb.Tests.csproj index 38112e2..8baa446 100644 --- a/symbols/pdb/Test/Mono.Cecil.Pdb.Tests.csproj +++ b/symbols/pdb/Test/Mono.Cecil.Pdb.Tests.csproj @@ -1,7 +1,7 @@ <Project Sdk="Microsoft.NET.Sdk" ToolsVersion="Current"> <Import Project="..\..\..\Mono.Cecil.Tests.props" /> <PropertyGroup> - <TargetFrameworks>netcoreapp2.1;net40</TargetFrameworks> + <TargetFrameworks>netcoreapp3.1;net40</TargetFrameworks> </PropertyGroup> <ItemGroup> <ProjectReference Include="..\..\..\Mono.Cecil.csproj"> |