Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/cecil.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarek Safar <marek.safar@gmail.com>2022-01-20 13:01:02 +0300
committerMarek Safar <marek.safar@gmail.com>2022-01-20 13:01:02 +0300
commitcdc0adc432b275c379f6db3db90a731b58525335 (patch)
treecdd8c3e0471f0fd5d1c131987b01a3c5db131d8e
parent03aabb40d2a5d9b22c94bfc6e4fe5a5b85badb8f (diff)
parent79b43e8e72866f450dee8b59510cb5767b1491ec (diff)
Merge remote-tracking branch 'upstream/master'
-rw-r--r--Directory.Build.props2
-rw-r--r--Mono.Cecil.Cil/MethodBody.cs13
-rw-r--r--Mono.Cecil.Cil/PortablePdb.cs189
-rw-r--r--Mono.Cecil.Cil/Symbols.cs7
-rw-r--r--Mono.Cecil.Metadata/Buffers.cs18
-rw-r--r--Mono.Cecil.PE/ImageReader.cs5
-rw-r--r--Mono.Cecil.PE/ImageWriter.cs14
-rw-r--r--Mono.Cecil/AssemblyWriter.cs61
-rw-r--r--Mono.Security.Cryptography/CryptoService.cs2
-rw-r--r--Test/Mono.Cecil.Tests.csproj4
-rw-r--r--Test/Mono.Cecil.Tests/FieldTests.cs44
-rw-r--r--Test/Mono.Cecil.Tests/ILProcessorTests.cs85
-rw-r--r--Test/Mono.Cecil.Tests/ImageReadTests.cs18
-rw-r--r--Test/Mono.Cecil.Tests/PortablePdbTests.cs330
-rw-r--r--Test/Resources/assemblies/EmbeddedPdbChecksumLib.dllbin0 -> 9728 bytes
-rw-r--r--Test/Resources/assemblies/PdbChecksumLib.dllbin0 -> 4096 bytes
-rw-r--r--Test/Resources/assemblies/PdbChecksumLib.pdbbin0 -> 9576 bytes
-rw-r--r--Test/Resources/il/FieldRVAAlignment.il71
-rw-r--r--rocks/Mono.Cecil.Rocks/DocCommentId.cs220
-rw-r--r--rocks/Test/Mono.Cecil.Rocks.Tests.csproj2
-rw-r--r--rocks/Test/Mono.Cecil.Tests/DocCommentIdTests.cs242
-rw-r--r--symbols/mdb/Mono.Cecil.Mdb/MdbWriter.cs6
-rw-r--r--symbols/mdb/Test/Mono.Cecil.Mdb.Tests.csproj2
-rw-r--r--symbols/pdb/Mono.Cecil.Pdb/NativePdbWriter.cs17
-rw-r--r--symbols/pdb/Test/Mono.Cecil.Pdb.Tests.csproj2
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
new file mode 100644
index 0000000..2de939b
--- /dev/null
+++ b/Test/Resources/assemblies/EmbeddedPdbChecksumLib.dll
Binary files differ
diff --git a/Test/Resources/assemblies/PdbChecksumLib.dll b/Test/Resources/assemblies/PdbChecksumLib.dll
new file mode 100644
index 0000000..9c28db7
--- /dev/null
+++ b/Test/Resources/assemblies/PdbChecksumLib.dll
Binary files differ
diff --git a/Test/Resources/assemblies/PdbChecksumLib.pdb b/Test/Resources/assemblies/PdbChecksumLib.pdb
new file mode 100644
index 0000000..81975e7
--- /dev/null
+++ b/Test/Resources/assemblies/PdbChecksumLib.pdb
Binary files differ
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">