diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx index 2bc7699526df53..c4504f140527a3 100644 --- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx @@ -4316,4 +4316,7 @@ Blocking wait is not supported on the JS interop threads. + + Emitting debug info is not supported for this member. + diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/ILGenerator.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/ILGenerator.cs index b56f69381450fc..826fda1e2c5a04 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/ILGenerator.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/ILGenerator.cs @@ -3,6 +3,7 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; +using System.Diagnostics.SymbolStore; namespace System.Reflection.Emit { @@ -200,6 +201,66 @@ public virtual LocalBuilder DeclareLocal(Type localType) [CLSCompliant(false)] public void Emit(OpCode opcode, sbyte arg) => Emit(opcode, (byte)arg); + /// + /// Marks a sequence point in the Microsoft intermediate language (MSIL) stream. + /// + /// The document for which the sequence point is being defined. + /// The line where the sequence point begins. + /// The column in the line where the sequence point begins. + /// The line where the sequence point ends. + /// The column in the line where the sequence point ends. + /// is . + /// is not valid. + /// + /// is not within range [0, 0x20000000) or + /// is not within range [0, 0x20000000) or lower than or + /// is not within range [0, 0x10000) or + /// is not within range [0, 0x10000) or + /// equal to and it is not hidden sequence point and lower than or equal to . + /// + /// Emitting debug info is not supported." + public void MarkSequencePoint(ISymbolDocumentWriter document, int startLine, int startColumn, int endLine, int endColumn) + { + ArgumentNullException.ThrowIfNull(document); + + if (startLine < 0 || startLine >= 0x20000000) + { + throw new ArgumentOutOfRangeException(nameof(startLine)); + } + + if (endLine < 0 || endLine >= 0x20000000 || startLine > endLine) + { + throw new ArgumentOutOfRangeException(nameof(endLine)); + } + + if (startColumn < 0 || startColumn >= 0x10000) + { + throw new ArgumentOutOfRangeException(nameof(startColumn)); + } + + if (endColumn < 0 || endColumn >= 0x10000 || + (startLine == endLine && startLine != 0xfeefee && startColumn >= endColumn)) + { + throw new ArgumentOutOfRangeException(nameof(endColumn)); + } + + MarkSequencePointCore(document, startLine, startColumn, endLine, endColumn); + } + + /// + /// When overridden in a derived class, marks a sequence point in the Microsoft intermediate language (MSIL) stream. + /// + /// The document for which the sequence point is being defined. + /// The line where the sequence point begins. + /// The column in the line where the sequence point begins. + /// The line where the sequence point ends. + /// The column in the line where the sequence point ends. + /// is not valid. + /// Emitting debug info is not supported." + /// The parameters validated in the caller: . + protected virtual void MarkSequencePointCore(ISymbolDocumentWriter document, int startLine, int startColumn, int endLine, int endColumn) => + throw new NotSupportedException(SR.NotSupported_EmitDebugInfo); + #endregion #endregion diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/LocalBuilder.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/LocalBuilder.cs index 7c38c287bf22b5..e5e86ab883f615 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/LocalBuilder.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/LocalBuilder.cs @@ -12,5 +12,27 @@ public abstract class LocalBuilder : LocalVariableInfo /// This constructor is invoked by derived classes. /// protected LocalBuilder() { } + + /// + /// Sets the name of this local variable. + /// + /// The name of the local variable + /// The is null. + /// The containing type has been created with CreateType() or + /// containing type doesn't support symbol writing." + public void SetLocalSymInfo(string name) + { + ArgumentNullException.ThrowIfNull(name); + + SetLocalSymInfoCore(name); + } + + /// + /// When overridden in a derived class, sets the name of this local variable. + /// + /// The name of the local variable. + /// Emitting debug info is not supported." + /// The containing type has been created with CreateType()." + protected virtual void SetLocalSymInfoCore(string name) => throw new NotSupportedException(SR.NotSupported_EmitDebugInfo); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/ModuleBuilder.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/ModuleBuilder.cs index 21f68439ee3927..f97935b24d7bd9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/ModuleBuilder.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/ModuleBuilder.cs @@ -1,7 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.ComponentModel; using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.SymbolStore; using System.Runtime.InteropServices; namespace System.Reflection.Emit @@ -24,6 +26,45 @@ public EnumBuilder DefineEnum(string name, TypeAttributes visibility, Type under return DefineEnumCore(name, visibility, underlyingType); } + /// + /// Defines a document for source. + /// + /// The URL for the document. + /// The GUID that identifies the document language. This can be Empty + /// The GUID that identifies the document language vendor. This is not used. + /// The GUID that identifies the document language vendor. This is not used. + /// The defined document. + /// is . + /// This method is called on a dynamic module that is not a persisted module. + [EditorBrowsable(EditorBrowsableState.Never)] + public ISymbolDocumentWriter DefineDocument(string url, Guid language, Guid languageVendor, Guid documentType) => + DefineDocument(url, language); + + /// + /// Defines a document for source. + /// + /// The URL for the document. + /// The GUID that identifies the document language. This is optional + /// The defined document. + /// is . + /// This method is called on a dynamic module that is not a persisted module. + public ISymbolDocumentWriter DefineDocument(string url, Guid language = default) + { + ArgumentException.ThrowIfNullOrEmpty(url); + + return DefineDocumentCore(url, language); + } + + /// + /// When override in a derived class, defines a document for source. + /// + /// The URL for the document. + /// The GUID that identifies the document language. This is optional + /// The defined document. + /// This method is called on a dynamic module that is not a debug module. + protected virtual ISymbolDocumentWriter DefineDocumentCore(string url, Guid language = default) => + throw new InvalidOperationException(SR.InvalidOperation_NotADebugModule); + protected abstract EnumBuilder DefineEnumCore(string name, TypeAttributes visibility, Type underlyingType); public MethodBuilder DefineGlobalMethod(string name, MethodAttributes attributes, Type? returnType, Type[]? parameterTypes) diff --git a/src/libraries/System.Reflection.Emit.ILGeneration/ref/System.Reflection.Emit.ILGeneration.cs b/src/libraries/System.Reflection.Emit.ILGeneration/ref/System.Reflection.Emit.ILGeneration.cs index f79ee4288df6ce..c5dcbd65fb1888 100644 --- a/src/libraries/System.Reflection.Emit.ILGeneration/ref/System.Reflection.Emit.ILGeneration.cs +++ b/src/libraries/System.Reflection.Emit.ILGeneration/ref/System.Reflection.Emit.ILGeneration.cs @@ -54,6 +54,8 @@ public virtual void EmitWriteLine(string value) { } public abstract void EndExceptionBlock(); public abstract void EndScope(); public abstract void MarkLabel(System.Reflection.Emit.Label loc); + public void MarkSequencePoint(System.Diagnostics.SymbolStore.ISymbolDocumentWriter document, int startLine, int startColumn, int endLine, int endColumn) { throw null; } + protected virtual void MarkSequencePointCore(System.Diagnostics.SymbolStore.ISymbolDocumentWriter document, int startLine, int startColumn, int endLine, int endColumn) { throw null; } public virtual void ThrowException([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] System.Type excType) { } public abstract void UsingNamespace(string usingNamespace); } @@ -73,6 +75,8 @@ protected LocalBuilder() { } public override bool IsPinned { get { throw null; } } public override int LocalIndex { get { throw null; } } public override System.Type LocalType { get { throw null; } } + public void SetLocalSymInfo(string name) { throw null; } + protected virtual void SetLocalSymInfoCore(string name) { throw null; } } public abstract partial class ParameterBuilder { diff --git a/src/libraries/System.Reflection.Emit.ILGeneration/ref/System.Reflection.Emit.ILGeneration.csproj b/src/libraries/System.Reflection.Emit.ILGeneration/ref/System.Reflection.Emit.ILGeneration.csproj index 6894be6dab8f9f..4364c5c0b56ddf 100644 --- a/src/libraries/System.Reflection.Emit.ILGeneration/ref/System.Reflection.Emit.ILGeneration.csproj +++ b/src/libraries/System.Reflection.Emit.ILGeneration/ref/System.Reflection.Emit.ILGeneration.csproj @@ -9,5 +9,6 @@ + diff --git a/src/libraries/System.Reflection.Emit/ref/System.Reflection.Emit.cs b/src/libraries/System.Reflection.Emit/ref/System.Reflection.Emit.cs index 3690bf72a4c876..995ab5679fc736 100644 --- a/src/libraries/System.Reflection.Emit/ref/System.Reflection.Emit.cs +++ b/src/libraries/System.Reflection.Emit/ref/System.Reflection.Emit.cs @@ -398,6 +398,10 @@ protected ModuleBuilder() { } public override string ScopeName { get { throw null; } } public void CreateGlobalFunctions() { } protected abstract void CreateGlobalFunctionsCore(); + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] + public System.Diagnostics.SymbolStore.ISymbolDocumentWriter DefineDocument(string url, Guid language, Guid languageVendor, Guid documentType) { throw null; } + public System.Diagnostics.SymbolStore.ISymbolDocumentWriter DefineDocument(string url, System.Guid language = default) { throw null; } + protected virtual System.Diagnostics.SymbolStore.ISymbolDocumentWriter DefineDocumentCore(string url, System.Guid language = default) { throw null; } public System.Reflection.Emit.EnumBuilder DefineEnum(string name, System.Reflection.TypeAttributes visibility, System.Type underlyingType) { throw null; } protected abstract System.Reflection.Emit.EnumBuilder DefineEnumCore(string name, System.Reflection.TypeAttributes visibility, System.Type underlyingType); public System.Reflection.Emit.MethodBuilder DefineGlobalMethod(string name, System.Reflection.MethodAttributes attributes, System.Reflection.CallingConventions callingConvention, System.Type? returnType, System.Type[]? parameterTypes) { throw null; } @@ -479,9 +483,11 @@ public PersistedAssemblyBuilder(System.Reflection.AssemblyName name, System.Refl public override System.Reflection.Module ManifestModule { get { throw null; } } [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Defining a dynamic assembly requires dynamic code.")] protected override System.Reflection.Emit.ModuleBuilder DefineDynamicModuleCore(string name) { throw null; } - protected override System.Reflection.Emit.ModuleBuilder? GetDynamicModuleCore(string name) { throw null; } [System.CLSCompliantAttribute(false)] public System.Reflection.Metadata.Ecma335.MetadataBuilder GenerateMetadata(out System.Reflection.Metadata.BlobBuilder ilStream, out System.Reflection.Metadata.BlobBuilder mappedFieldData) { throw null; } + [System.CLSCompliantAttribute(false)] + public System.Reflection.Metadata.Ecma335.MetadataBuilder GenerateMetadata(out System.Reflection.Metadata.BlobBuilder ilStream, out System.Reflection.Metadata.BlobBuilder mappedFieldData, out System.Reflection.Metadata.Ecma335.MetadataBuilder pdbBuilder) { throw null; } + protected override System.Reflection.Emit.ModuleBuilder? GetDynamicModuleCore(string name) { throw null; } public override System.Reflection.AssemblyName GetName(bool copiedName) { throw null; } public void Save(string assemblyFileName) { throw null; } public void Save(System.IO.Stream stream) { throw null; } diff --git a/src/libraries/System.Reflection.Emit/ref/System.Reflection.Emit.csproj b/src/libraries/System.Reflection.Emit/ref/System.Reflection.Emit.csproj index 44f0feefe67db2..89a1505b49ceab 100644 --- a/src/libraries/System.Reflection.Emit/ref/System.Reflection.Emit.csproj +++ b/src/libraries/System.Reflection.Emit/ref/System.Reflection.Emit.csproj @@ -11,5 +11,6 @@ + \ No newline at end of file diff --git a/src/libraries/System.Reflection.Emit/src/Resources/Strings.resx b/src/libraries/System.Reflection.Emit/src/Resources/Strings.resx index 4ab50a83684c01..6103dbef1f159d 100644 --- a/src/libraries/System.Reflection.Emit/src/Resources/Strings.resx +++ b/src/libraries/System.Reflection.Emit/src/Resources/Strings.resx @@ -297,4 +297,10 @@ Not supported in an array method of a type definition that is not complete. + + Invalid source document. + + + Unmatching symbol scope. + \ No newline at end of file diff --git a/src/libraries/System.Reflection.Emit/src/System.Reflection.Emit.csproj b/src/libraries/System.Reflection.Emit/src/System.Reflection.Emit.csproj index 0c34ef45f44862..57b328a0bbde26 100644 --- a/src/libraries/System.Reflection.Emit/src/System.Reflection.Emit.csproj +++ b/src/libraries/System.Reflection.Emit/src/System.Reflection.Emit.csproj @@ -23,6 +23,8 @@ + + diff --git a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ILGeneratorImpl.cs b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ILGeneratorImpl.cs index dcb92654e124b7..d4b40387e30e20 100644 --- a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ILGeneratorImpl.cs +++ b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ILGeneratorImpl.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.SymbolStore; using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; using System.Runtime.InteropServices; @@ -17,6 +18,8 @@ internal sealed class ILGeneratorImpl : ILGenerator private readonly BlobBuilder _builder; private readonly InstructionEncoder _il; private readonly ControlFlowBuilder _cfBuilder; + private readonly Scope _scope; // scope of the entire method body + private Scope _currentScope; private bool _hasDynamicStackAllocation; private int _maxStackDepth; private int _currentStackDepth; // Current stack labelStartDepth @@ -24,10 +27,11 @@ internal sealed class ILGeneratorImpl : ILGenerator // Adjustment to add to _maxStackDepth for incorrect/invalid IL. For example, when branch // instructions branches backward with non zero stack depths targeting the same label. private int _depthAdjustment; - private List _locals = new(); + private int _localCount; private Dictionary _labelTable = new(2); private List> _memberReferences = new(); private List _exceptionStack = new(); + private Dictionary> _documentToSequencePoints = new(); internal ILGeneratorImpl(MethodBuilderImpl methodBuilder, int size) { @@ -37,13 +41,18 @@ internal ILGeneratorImpl(MethodBuilderImpl methodBuilder, int size) _builder = new BlobBuilder(Math.Max(size, DefaultSize)); _cfBuilder = new ControlFlowBuilder(); _il = new InstructionEncoder(_builder, _cfBuilder); + _scope = new Scope(_il.Offset, parent: null); + _currentScope = _scope; } internal int GetMaxStack() => Math.Min(ushort.MaxValue, _maxStackDepth + _depthAdjustment); internal List> GetMemberReferences() => _memberReferences; internal InstructionEncoder Instructions => _il; internal bool HasDynamicStackAllocation => _hasDynamicStackAllocation; - internal List Locals => _locals; + internal List Locals => _scope.GetAllLocals(); + internal int LocalCount => _localCount; + internal Scope Scope => _scope; + internal Dictionary> DocumentToSequencePoints => _documentToSequencePoints; public override int ILOffset => _il.Offset; @@ -198,16 +207,19 @@ public override void BeginFinallyBlock() public override void BeginScope() { - // TODO: No-op, will be implemented wit PDB support + _currentScope._children ??= new List(); + Scope newScope = new Scope(_il.Offset, _currentScope); + _currentScope._children.Add(newScope); + _currentScope = newScope; } public override LocalBuilder DeclareLocal(Type localType, bool pinned) { ArgumentNullException.ThrowIfNull(localType); - LocalBuilder local = new LocalBuilderImpl(_locals.Count, localType, _methodBuilder, pinned); - _locals.Add(local); - + _currentScope._locals ??= new List(); + LocalBuilder local = new LocalBuilderImpl(_localCount++, localType, _methodBuilder, pinned); + _currentScope._locals.Add(local); return local; } @@ -731,7 +743,13 @@ public override void EndExceptionBlock() public override void EndScope() { - // TODO: No-op, will be implemented wit PDB support + if (_currentScope._parent == null) + { + throw new InvalidOperationException(SR.InvalidOperation_UnmatchingSymScope); + } + + _currentScope._endOffset = _il.Offset; + _currentScope = _currentScope._parent; } public override void MarkLabel(Label loc) @@ -777,9 +795,32 @@ public override void MarkLabel(Label loc) } } + protected override void MarkSequencePointCore(ISymbolDocumentWriter document, int startLine, int startColumn, int endLine, int endColumn) + { + if (document is SymbolDocumentWriter symbolDoc) + { + if (_documentToSequencePoints.TryGetValue(symbolDoc, out List? sequencePoints)) + { + sequencePoints.Add(new SequencePoint(_il.Offset, startLine, startColumn, endLine, endColumn)); + } + else + { + sequencePoints = new List { new SequencePoint(_il.Offset, startLine, startColumn, endLine, endColumn) }; + _documentToSequencePoints.Add(symbolDoc, sequencePoints); + } + } + else + { + throw new ArgumentException(SR.InvalidOperation_InvalidDocument, nameof(document)); + } + } + public override void UsingNamespace(string usingNamespace) { - // TODO: No-op, will be implemented wit PDB support + ArgumentException.ThrowIfNullOrEmpty(usingNamespace); + + _currentScope._importNamespaces ??= new List(); + _currentScope._importNamespaces.Add(usingNamespace); } } @@ -817,4 +858,40 @@ internal LabelInfo(LabelHandle metaLabel) internal int _startDepth; // Stack labelStartDepth, with -1 meaning unknown. internal LabelHandle _metaLabel; } + + internal sealed class Scope + { + internal Scope(int offset, Scope? parent) + { + _startOffset = offset; + _parent = parent; + } + + internal Scope? _parent; + internal List? _children; + internal List? _locals; + internal List? _importNamespaces; + internal int _startOffset; + internal int _endOffset; + + internal List GetAllLocals() + { + List locals = new List(); + + if (_locals != null) + { + locals.AddRange(_locals); + } + + if (_children != null) + { + foreach (Scope child in _children) + { + locals.AddRange(child.GetAllLocals()); + } + } + + return locals; + } + } } diff --git a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/LocalBuilderImpl.cs b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/LocalBuilderImpl.cs index fa9bffbec4345c..406d0812047633 100644 --- a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/LocalBuilderImpl.cs +++ b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/LocalBuilderImpl.cs @@ -10,6 +10,7 @@ internal sealed class LocalBuilderImpl : LocalBuilder private readonly Type _localType; private readonly MethodInfo _method; private readonly bool _isPinned; + private string? _name; #endregion #region Constructor @@ -24,6 +25,19 @@ internal LocalBuilderImpl(int index, Type type, MethodInfo method, bool isPinned #region Internal Members internal MethodInfo GetMethodBuilder() => _method; + internal string? Name => _name; + #endregion + + #region LocalBuilder Override + protected override void SetLocalSymInfoCore(string name) + { + if (_method.DeclaringType is TypeBuilder typeBuilder && typeBuilder.IsCreated()) + { + throw new InvalidOperationException(SR.InvalidOperation_TypeHasBeenCreated); + } + + _name = name; + } #endregion #region LocalVariableInfo Override diff --git a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ModuleBuilderImpl.cs b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ModuleBuilderImpl.cs index 0349186b717fef..b78526adda13cd 100644 --- a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ModuleBuilderImpl.cs +++ b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ModuleBuilderImpl.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.SymbolStore; using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; using System.Reflection.PortableExecutable; @@ -25,6 +26,7 @@ internal sealed class ModuleBuilderImpl : ModuleBuilder private readonly Guid _moduleVersionId; private Dictionary? _moduleReferences; private List? _customAttributes; + private Dictionary _docHandles = new(); private int _nextTypeDefRowId = 1; private int _nextMethodDefRowId = 1; private int _nextFieldDefRowId = 1; @@ -34,6 +36,7 @@ internal sealed class ModuleBuilderImpl : ModuleBuilder private bool _coreTypesFullyPopulated; private bool _hasGlobalBeenCreated; private Type?[]? _coreTypes; + private MetadataBuilder _pdbBuilder = new(); private static readonly Type[] s_coreTypes = { typeof(void), typeof(object), typeof(bool), typeof(char), typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(float), typeof(double), typeof(string), typeof(nint), typeof(nuint), typeof(TypedReference) }; @@ -108,7 +111,7 @@ internal Type GetTypeFromCoreAssembly(CoreTypeId typeId) return null; } - internal void AppendMetadata(MethodBodyStreamEncoder methodBodyEncoder, BlobBuilder fieldDataBuilder) + internal void AppendMetadata(MethodBodyStreamEncoder methodBodyEncoder, BlobBuilder fieldDataBuilder, out MetadataBuilder pdbBuilder) { // Add module metadata ModuleDefinitionHandle moduleHandle = _metadataBuilder.AddModule( @@ -190,6 +193,8 @@ internal void AppendMetadata(MethodBodyStreamEncoder methodBodyEncoder, BlobBuil { AddGenericTypeParametersAndConstraintsCustomAttributes(param._parentHandle, param); } + + pdbBuilder = _pdbBuilder; } private void WriteInterfaceImplementations(TypeBuilderImpl typeBuilder, TypeDefinitionHandle typeHandle) @@ -339,7 +344,8 @@ private void PopulateTokensForTypesAndItsMembers() } } - private void WriteMethods(List methods, List genericParams, MethodBodyStreamEncoder methodBodyEncoder) + private void WriteMethods(List methods, List genericParams, + MethodBodyStreamEncoder methodBodyEncoder) { foreach (MethodBuilderImpl method in methods) { @@ -348,9 +354,10 @@ private void WriteMethods(List methods, List methods, List>.Enumerator enumerator = il.DocumentToSequencePoints.GetEnumerator(); + if (il.DocumentToSequencePoints.Count > 1) + { + // sequence points spans multiple docs + _pdbBuilder.AddMethodDebugInformation(default, PopulateMultiDocSequencePointsBlob(enumerator, localSignatureHandle)); + } + else // single document sequence point + { + int previousNonHiddenStartLine = -1; + int previousNonHiddenStartColumn = -1; + enumerator.MoveNext(); + BlobBuilder spBlobBuilder = new BlobBuilder(); + spBlobBuilder.WriteCompressedInteger(MetadataTokens.GetRowNumber(localSignatureHandle)); + PopulateSequencePointsBlob(spBlobBuilder, enumerator.Current.Value, ref previousNonHiddenStartLine, ref previousNonHiddenStartColumn); + _pdbBuilder.AddMethodDebugInformation(GetDocument(enumerator.Current.Key), _pdbBuilder.GetOrAddBlob(spBlobBuilder)); + } + } + + Scope scope = il.Scope; + scope._endOffset = il.ILOffset; // Outer most scope covers the entire method body, so haven't closed by the user. + + AddLocalScope(methodHandle, parentImport: default, MetadataTokens.LocalVariableHandle(_pdbBuilder.GetRowCount(TableIndex.LocalVariable) + 1), scope); + } + + private BlobHandle PopulateMultiDocSequencePointsBlob(Dictionary>.Enumerator enumerator, StandaloneSignatureHandle localSignature) + { + BlobBuilder spBlobBuilder = new BlobBuilder(); + int previousNonHiddenStartLine = -1; + int previousNonHiddenStartColumn = -1; + enumerator.MoveNext(); + KeyValuePair> pair = enumerator.Current; + + // header: + spBlobBuilder.WriteCompressedInteger(MetadataTokens.GetRowNumber(localSignature)); + spBlobBuilder.WriteCompressedInteger(MetadataTokens.GetRowNumber(GetDocument(pair.Key))); + + // First sequence point record + PopulateSequencePointsBlob(spBlobBuilder, pair.Value, ref previousNonHiddenStartLine, ref previousNonHiddenStartColumn); + + while (enumerator.MoveNext()) + { + pair = enumerator.Current; + spBlobBuilder.WriteCompressedInteger(0); + spBlobBuilder.WriteCompressedInteger(MetadataTokens.GetRowNumber(GetDocument(pair.Key))); + PopulateSequencePointsBlob(spBlobBuilder, pair.Value, ref previousNonHiddenStartLine, ref previousNonHiddenStartColumn); + } + + return _pdbBuilder.GetOrAddBlob(spBlobBuilder); + } + + private static void PopulateSequencePointsBlob(BlobBuilder spBlobBuilder, List sequencePoints, ref int previousNonHiddenStartLine, ref int previousNonHiddenStartColumn) + { + for (int i = 0; i < sequencePoints.Count; i++) + { + // IL offset delta: + if (i > 0) + { + spBlobBuilder.WriteCompressedInteger(sequencePoints[i].Offset - sequencePoints[i - 1].Offset); + } + else + { + spBlobBuilder.WriteCompressedInteger(sequencePoints[i].Offset); + } + + if (sequencePoints[i].IsHidden) + { + spBlobBuilder.WriteUInt16(0); + continue; + } + + // Delta Lines & Columns: + SerializeDeltaLinesAndColumns(spBlobBuilder, sequencePoints[i]); + + // delta Start Lines & Columns: + if (previousNonHiddenStartLine < 0) + { + Debug.Assert(previousNonHiddenStartColumn < 0); + spBlobBuilder.WriteCompressedInteger(sequencePoints[i].StartLine); + spBlobBuilder.WriteCompressedInteger(sequencePoints[i].StartColumn); + } + else + { + spBlobBuilder.WriteCompressedSignedInteger(sequencePoints[i].StartLine - previousNonHiddenStartLine); + spBlobBuilder.WriteCompressedSignedInteger(sequencePoints[i].StartColumn - previousNonHiddenStartColumn); + } + + previousNonHiddenStartLine = sequencePoints[i].StartLine; + previousNonHiddenStartColumn = sequencePoints[i].StartColumn; + } + } + + private void AddLocalScope(MethodDefinitionHandle methodHandle, ImportScopeHandle parentImport, LocalVariableHandle firstLocalVariableHandle, Scope scope) + { + parentImport = GetImportScopeHandle(scope._importNamespaces, parentImport); + firstLocalVariableHandle = GetLocalVariableHandle(scope._locals, firstLocalVariableHandle); + _pdbBuilder.AddLocalScope(methodHandle, parentImport, firstLocalVariableHandle, + constantList: MetadataTokens.LocalConstantHandle(1), scope._startOffset, scope._endOffset - scope._startOffset); + + if (scope._children != null) + { + foreach (Scope childScope in scope._children) + { + AddLocalScope(methodHandle, parentImport, MetadataTokens.LocalVariableHandle(_pdbBuilder.GetRowCount(TableIndex.LocalVariable) + 1), childScope); + } + } + } + + private LocalVariableHandle GetLocalVariableHandle(List? locals, LocalVariableHandle firstLocalHandleOfLastScope) + { + if (locals != null) + { + bool firstLocalSet = false; + foreach (LocalBuilderImpl local in locals) + { + if (!string.IsNullOrEmpty(local.Name)) + { + LocalVariableHandle localHandle = _pdbBuilder.AddLocalVariable(LocalVariableAttributes.None, local.LocalIndex, + local.Name == null ? _pdbBuilder.GetOrAddString(string.Empty) : _pdbBuilder.GetOrAddString(local.Name)); + if (!firstLocalSet) + { + firstLocalHandleOfLastScope = localHandle; + firstLocalSet = true; + } + } + } + } + + return firstLocalHandleOfLastScope; + } + + private ImportScopeHandle GetImportScopeHandle(List? importNamespaces, ImportScopeHandle parent) + { + if (importNamespaces == null) + { + return default; + } + + BlobBuilder importBlob = new BlobBuilder(); + + foreach (string importNs in importNamespaces) + { + importBlob.WriteByte((byte)ImportDefinitionKind.ImportNamespace); + importBlob.WriteCompressedInteger(MetadataTokens.GetHeapOffset(_pdbBuilder.GetOrAddBlobUTF8(importNs))); + } + + return _pdbBuilder.AddImportScope(parent, _pdbBuilder.GetOrAddBlob(importBlob)); + } + + private static void SerializeDeltaLinesAndColumns(BlobBuilder spBuilder, SequencePoint sequencePoint) + { + int deltaLines = sequencePoint.EndLine - sequencePoint.StartLine; + int deltaColumns = sequencePoint.EndColumn - sequencePoint.StartColumn; + + // only hidden sequence points have zero width + Debug.Assert(deltaLines != 0 || deltaColumns != 0 || sequencePoint.IsHidden); + + spBuilder.WriteCompressedInteger(deltaLines); + + if (deltaLines == 0) + { + spBuilder.WriteCompressedInteger(deltaColumns); + } + else + { + Debug.Assert(deltaLines > 0); + spBuilder.WriteCompressedSignedInteger(deltaColumns); + } + } + + private DocumentHandle GetDocument(SymbolDocumentWriter docWriter) + { + if (!_docHandles.TryGetValue(docWriter, out DocumentHandle handle)) + { + handle = AddDocument(docWriter.URL, docWriter.Language, docWriter.HashAlgorithm, docWriter.Hash); + _docHandles.Add(docWriter, handle); + } + + return handle; + } + + private DocumentHandle AddDocument(string url, Guid language, Guid hashAlgorithm, byte[]? hash) => + _pdbBuilder.AddDocument( + name: _pdbBuilder.GetOrAddDocumentName(url), + hashAlgorithm: hashAlgorithm == default ? default : _pdbBuilder.GetOrAddGuid(hashAlgorithm), + hash: hash == null ? default : _metadataBuilder.GetOrAddBlob(hash), + language: language == default ? default : _pdbBuilder.GetOrAddGuid(language)); + private void FillMemberReferences(ILGeneratorImpl il) { foreach (KeyValuePair pair in il.GetMemberReferences()) @@ -892,8 +1095,7 @@ private EntityHandle GetHandleForMember(MemberInfo member) } private static bool IsConstructedFromTypeBuilder(Type type) => type.IsConstructedGenericType && - (type.GetGenericTypeDefinition() is TypeBuilderImpl || - ContainsTypeBuilder(type.GetGenericArguments())); + (type.GetGenericTypeDefinition() is TypeBuilderImpl || ContainsTypeBuilder(type.GetGenericArguments())); internal static bool ContainsTypeBuilder(Type[] genericArguments) { @@ -1158,5 +1360,10 @@ private static SignatureCallingConvention GetSignatureConvention(CallingConventi CallingConvention.FastCall => SignatureCallingConvention.FastCall, _ => SignatureCallingConvention.Default, }; + + protected override ISymbolDocumentWriter DefineDocumentCore(string url, Guid language = default) + { + return new SymbolDocumentWriter(url, language); + } } } diff --git a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/Pdb/SequencePoint.cs b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/Pdb/SequencePoint.cs new file mode 100644 index 00000000000000..f82fea8986ff2f --- /dev/null +++ b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/Pdb/SequencePoint.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Reflection.Emit +{ + internal sealed class SequencePoint + { + public const int HiddenLine = 0xfeefee; + + public int Offset { get; } + public int StartLine { get; } + public int EndLine { get; } + public int StartColumn { get; } + public int EndColumn { get; } + + public SequencePoint(int offset, int startLine, int startColumn, int endLine, int endColumn) + { + Offset = offset; + StartLine = startLine; + EndLine = endLine; + StartColumn = startColumn; + EndColumn = endColumn; + } + + public bool IsHidden => StartLine == HiddenLine; + } +} diff --git a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/Pdb/SymbolDocumentWriter.cs b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/Pdb/SymbolDocumentWriter.cs new file mode 100644 index 00000000000000..a07b44620fa85f --- /dev/null +++ b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/Pdb/SymbolDocumentWriter.cs @@ -0,0 +1,39 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.SymbolStore; + +namespace System.Reflection.Emit +{ + internal sealed class SymbolDocumentWriter : ISymbolDocumentWriter + { + internal readonly Guid _language; + internal readonly string _url; + private Guid _hashAlgorithm; + private byte[]? _hash; + private byte[]? _source; + + internal string URL => _url; + internal Guid Language => _language; + internal Guid HashAlgorithm => _hashAlgorithm; + internal byte[]? Hash => _hash; + internal byte[]? Source => _source; + + public SymbolDocumentWriter(string url, Guid language) + { + _language = language; + _url = url; + } + + public void SetCheckSum(Guid algorithmId, byte[] checkSum) + { + _hashAlgorithm = algorithmId; + _hash = checkSum; + } + + public void SetSource(byte[] source) + { + _source = source; + } + } +} diff --git a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/PersistedAssemblyBuilder.cs b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/PersistedAssemblyBuilder.cs index 12fbf4704b7bac..ad127094936ee4 100644 --- a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/PersistedAssemblyBuilder.cs +++ b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/PersistedAssemblyBuilder.cs @@ -98,11 +98,10 @@ private void SaveInternal(Stream stream) { ArgumentNullException.ThrowIfNull(stream); - PopulateAssemblyMetadata(out BlobBuilder ilStream, out BlobBuilder fieldData); + PopulateAssemblyMetadata(out BlobBuilder ilStream, out BlobBuilder fieldData, out _); WritePEImage(stream, ilStream, fieldData); } - /// /// Generates the metadata for the . /// @@ -110,16 +109,33 @@ private void SaveInternal(Stream stream) /// Outputs bytes that includes all field RVA data defined in the assembly. /// A that includes all members defined in the Assembly. /// A module not defined for the assembly. - /// The metadata already populated for the assembly before. + /// The metadata already populated for the assembly previously. [CLSCompliant(false)] public MetadataBuilder GenerateMetadata(out BlobBuilder ilStream, out BlobBuilder mappedFieldData) { - PopulateAssemblyMetadata(out ilStream, out mappedFieldData); + PopulateAssemblyMetadata(out ilStream, out mappedFieldData, out _); + + return _metadataBuilder; + } + + /// + /// Generates the metadata for the . + /// + /// Outputs bytes that includes all method's IL (body) emitted. + /// Outputs bytes that includes all field RVA data defined in the assembly. + /// Outputs that includes PDB metadata. + /// A that includes all members defined in the Assembly. + /// A module not defined for the assembly. + /// The metadata already populated for the assembly previously. + [CLSCompliant(false)] + public MetadataBuilder GenerateMetadata(out BlobBuilder ilStream, out BlobBuilder mappedFieldData, out MetadataBuilder pdbBuilder) + { + PopulateAssemblyMetadata(out ilStream, out mappedFieldData, out pdbBuilder); return _metadataBuilder; } - private void PopulateAssemblyMetadata(out BlobBuilder ilStream, out BlobBuilder fieldData) + private void PopulateAssemblyMetadata(out BlobBuilder ilStream, out BlobBuilder fieldData, out MetadataBuilder pdbBuilder) { if (_module == null) { @@ -147,7 +163,7 @@ private void PopulateAssemblyMetadata(out BlobBuilder ilStream, out BlobBuilder ); _module.WriteCustomAttributes(_customAttributes, assemblyHandle); - _module.AppendMetadata(new MethodBodyStreamEncoder(ilStream), fieldData); + _module.AppendMetadata(new MethodBodyStreamEncoder(ilStream), fieldData, out pdbBuilder); _isMetadataPopulated = true; } diff --git a/src/libraries/System.Reflection.Emit/tests/PortablePdb/ILGeneratorScopesAndSequencePointsTests.cs b/src/libraries/System.Reflection.Emit/tests/PortablePdb/ILGeneratorScopesAndSequencePointsTests.cs new file mode 100644 index 00000000000000..abdbf5e4047b58 --- /dev/null +++ b/src/libraries/System.Reflection.Emit/tests/PortablePdb/ILGeneratorScopesAndSequencePointsTests.cs @@ -0,0 +1,372 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.SymbolStore; +using System.IO; +using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; +using Xunit; + +namespace System.Reflection.Emit.Tests +{ + [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + public class ILGeneratorScopesAndSequencePointsTests + { + [Fact] + public void SetLocalSymInfo_UsingNamespace_Validations() + { + ModuleBuilder mb = new PersistedAssemblyBuilder(new AssemblyName("MyAssembly2"), typeof(object).Assembly).DefineDynamicModule("MyModule2"); + TypeBuilder tb = mb.DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class); + ILGenerator il = tb.DefineMethod("SumMethod", MethodAttributes.Public | MethodAttributes.Static).GetILGenerator(); + LocalBuilder local = il.DeclareLocal(typeof(int)); + Assert.Throws("usingNamespace", () => il.UsingNamespace(null)); + Assert.Throws("usingNamespace", () => il.UsingNamespace(string.Empty)); + Assert.Throws("name", () => local.SetLocalSymInfo(null)); + il.Emit(OpCodes.Ret); + tb.CreateType(); + Assert.Throws(() => local.SetLocalSymInfo("myInt1")); // type created + } + + [Fact] + public void LocalWithoutSymInfoWillNotAddedToLocalVariablesTable() + { + PersistedAssemblyBuilder ab = new PersistedAssemblyBuilder(new AssemblyName("MyAssembly"), typeof(object).Assembly); + ModuleBuilder mb = ab.DefineDynamicModule("MyModule"); + TypeBuilder tb = mb.DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class); + ISymbolDocumentWriter srcDoc = mb.DefineDocument("MySourceFile.cs", SymLanguageType.CSharp); + MethodBuilder method = tb.DefineMethod("SumMethod", MethodAttributes.Public | MethodAttributes.Static, typeof(int), [typeof(int), typeof(int)]); + ILGenerator il = method.GetILGenerator(); + LocalBuilder local = il.DeclareLocal(typeof(int)); + local.SetLocalSymInfo("myInt1"); + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Ldarg_1); + il.Emit(OpCodes.Add); + il.Emit(OpCodes.Stloc_0); + LocalBuilder local2 = il.DeclareLocal(typeof(string)); + il.Emit(OpCodes.Ldstr, "MyAssembly"); + il.Emit(OpCodes.Stloc, local2); + LocalBuilder local3 = il.DeclareLocal(typeof(int)); + local3.SetLocalSymInfo("myInt2"); + il.Emit(OpCodes.Ldc_I4_2); + il.Emit(OpCodes.Stloc_2); + il.Emit(OpCodes.Ldloc_2); + il.Emit(OpCodes.Ldloc_0); + il.Emit(OpCodes.Add); + il.Emit(OpCodes.Ret); + tb.CreateType(); + + MetadataBuilder mdb = ab.GenerateMetadata(out BlobBuilder _, out BlobBuilder _, out MetadataBuilder pdbMetadata); + + BlobBuilder portablePdbBlob = new BlobBuilder(); + PortablePdbBuilder pdbBuilder = new PortablePdbBuilder(pdbMetadata, mdb.GetRowCounts(), default); + pdbBuilder.Serialize(portablePdbBlob); + using TempFile pdbFile = TempFile.Create(); + using var pdbFileStream = new FileStream(pdbFile.Path, FileMode.Create, FileAccess.Write); + portablePdbBlob.WriteContentTo(pdbFileStream); + pdbFileStream.Close(); + + using var fs = new FileStream(pdbFile.Path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + using MetadataReaderProvider provider = MetadataReaderProvider.FromPortablePdbStream(fs); + MetadataReader reader = provider.GetMetadataReader(); + MethodDebugInformation mdi = reader.GetMethodDebugInformation(MetadataTokens.MethodDebugInformationHandle(method.MetadataToken)); + SequencePointCollection.Enumerator spcEnumerator = mdi.GetSequencePoints().GetEnumerator(); + Assert.False(spcEnumerator.MoveNext()); + + LocalScopeHandleCollection.Enumerator localScopes = reader.GetLocalScopes(MetadataTokens.MethodDefinitionHandle(method.MetadataToken)).GetEnumerator(); + Assert.True(localScopes.MoveNext()); + LocalScope localScope = reader.GetLocalScope(localScopes.Current); + LocalVariableHandleCollection.Enumerator localEnumerator = localScope.GetLocalVariables().GetEnumerator(); + Assert.True(localEnumerator.MoveNext()); + Assert.Equal("myInt1", reader.GetString(reader.GetLocalVariable(localEnumerator.Current).Name)); + Assert.True(localEnumerator.MoveNext()); + Assert.Equal("myInt2", reader.GetString(reader.GetLocalVariable(localEnumerator.Current).Name)); + Assert.False(localEnumerator.MoveNext()); + Assert.False(localScopes.MoveNext()); + } + + [Fact] + public void LocalsNamespacesWithinNestedScopes() + { + PersistedAssemblyBuilder ab = new PersistedAssemblyBuilder(new AssemblyName("MyAssembly"), typeof(object).Assembly); + ModuleBuilder mb = ab.DefineDynamicModule("MyModule"); + TypeBuilder tb = mb.DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class); + ISymbolDocumentWriter srcDoc = mb.DefineDocument("MySourceFile.cs", SymLanguageType.CSharp); + MethodBuilder method = tb.DefineMethod("SumMethod", MethodAttributes.Public | MethodAttributes.Static, typeof(int), [typeof(int), typeof(int)]); + ILGenerator il = method.GetILGenerator(); + LocalBuilder local = il.DeclareLocal(typeof(int)); + il.UsingNamespace("System"); + local.SetLocalSymInfo("myInt1"); + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Ldarg_1); + il.Emit(OpCodes.Add); + il.Emit(OpCodes.Stloc_0); + LocalBuilder local2 = il.DeclareLocal(typeof(string)); + local2.SetLocalSymInfo("myString"); + il.Emit(OpCodes.Ldstr, "MyAssembly"); + il.Emit(OpCodes.Stloc, local2); + il.BeginScope(); + il.UsingNamespace("System.Reflection"); + LocalBuilder local3 = il.DeclareLocal(typeof(AssemblyName)); + local3.SetLocalSymInfo("myAssembly"); + il.Emit(OpCodes.Ldloc_1); + il.Emit(OpCodes.Newobj, typeof(AssemblyName).GetConstructor([typeof(string)])); + il.Emit(OpCodes.Stloc_2); + il.BeginScope(); + LocalBuilder local4 = il.DeclareLocal(typeof(int)); + local4.SetLocalSymInfo("myInt2"); + LocalBuilder local5 = il.DeclareLocal(typeof(int)); + local5.SetLocalSymInfo("myInt3"); + il.Emit(OpCodes.Ldc_I4_2); + il.Emit(OpCodes.Stloc_3); + il.Emit(OpCodes.Ldc_I4_5); + il.Emit(OpCodes.Stloc_S, 4); + il.Emit(OpCodes.Ldloc_S, 4); + il.Emit(OpCodes.Ldloc_3); + il.Emit(OpCodes.Add); + il.Emit(OpCodes.Stloc_0); + il.EndScope(); + il.UsingNamespace("System.Reflection.Emit"); + il.EndScope(); + il.Emit(OpCodes.Ldloc_0); + il.Emit(OpCodes.Ret); + tb.CreateType(); + + MetadataBuilder mdb = ab.GenerateMetadata(out BlobBuilder _, out BlobBuilder _, out MetadataBuilder pdbMetadata); + + BlobBuilder portablePdbBlob = new BlobBuilder(); + PortablePdbBuilder pdbBuilder = new PortablePdbBuilder(pdbMetadata, mdb.GetRowCounts(), default); + pdbBuilder.Serialize(portablePdbBlob); + using TempFile pdbFile = TempFile.Create(); + using var pdbFileStream = new FileStream(pdbFile.Path, FileMode.Create, FileAccess.Write); + portablePdbBlob.WriteContentTo(pdbFileStream); + pdbFileStream.Close(); + + using var fs = new FileStream(pdbFile.Path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + using MetadataReaderProvider provider = MetadataReaderProvider.FromPortablePdbStream(fs); + MetadataReader reader = provider.GetMetadataReader(); + MethodDebugInformation mdi = reader.GetMethodDebugInformation(MetadataTokens.MethodDebugInformationHandle(method.MetadataToken)); + SequencePointCollection.Enumerator spcEnumerator = mdi.GetSequencePoints().GetEnumerator(); + Assert.False(spcEnumerator.MoveNext()); + + LocalScopeHandleCollection.Enumerator localScopes = reader.GetLocalScopes(MetadataTokens.MethodDefinitionHandle(method.MetadataToken)).GetEnumerator(); + Assert.True(localScopes.MoveNext()); + LocalScope localScope = reader.GetLocalScope(localScopes.Current); + Assert.Equal(0, localScope.StartOffset); + Assert.Equal(35, localScope.EndOffset); + LocalVariableHandleCollection.Enumerator localEnumerator = localScope.GetLocalVariables().GetEnumerator(); + Assert.True(localEnumerator.MoveNext()); + Assert.Equal("myInt1", reader.GetString(reader.GetLocalVariable(localEnumerator.Current).Name)); + Assert.True(localEnumerator.MoveNext()); + Assert.Equal("myString", reader.GetString(reader.GetLocalVariable(localEnumerator.Current).Name)); + Assert.False(localEnumerator.MoveNext()); + + ImportScope importScope = reader.GetImportScope(localScope.ImportScope); + Assert.True(importScope.Parent.IsNil); + ImportDefinitionCollection.Enumerator importEnumerator = importScope.GetImports().GetEnumerator(); + Assert.True(importEnumerator.MoveNext()); + ImportDefinition importDef = importEnumerator.Current; + Assert.Equal(ImportDefinitionKind.ImportNamespace, importDef.Kind); + BlobReader blobReader = reader.GetBlobReader(importDef.TargetNamespace); + Assert.Equal("System", blobReader.ReadUTF8(blobReader.Length)); + Assert.False(importEnumerator.MoveNext()); + + Assert.True(localScopes.MoveNext()); + LocalScope innerScope = reader.GetLocalScope(localScopes.Current); + Assert.Equal(10, innerScope.StartOffset); + Assert.Equal(33, innerScope.EndOffset); + localEnumerator = innerScope.GetLocalVariables().GetEnumerator(); + Assert.True(localEnumerator.MoveNext()); + Assert.Equal("myAssembly", reader.GetString(reader.GetLocalVariable(localEnumerator.Current).Name)); + Assert.False(localEnumerator.MoveNext()); + + ImportScope innerImport = reader.GetImportScope(innerScope.ImportScope); + Assert.Equal(importScope, reader.GetImportScope(innerImport.Parent)); + importEnumerator = innerImport.GetImports().GetEnumerator(); + Assert.True(importEnumerator.MoveNext()); + importDef = importEnumerator.Current; + Assert.Equal(ImportDefinitionKind.ImportNamespace, importDef.Kind); + blobReader = reader.GetBlobReader(importDef.TargetNamespace); + Assert.Equal("System.Reflection", blobReader.ReadUTF8(blobReader.Length)); + Assert.True(importEnumerator.MoveNext()); + importDef = importEnumerator.Current; + Assert.Equal(ImportDefinitionKind.ImportNamespace, importDef.Kind); + blobReader = reader.GetBlobReader(importDef.TargetNamespace); + Assert.Equal("System.Reflection.Emit", blobReader.ReadUTF8(blobReader.Length)); + Assert.False(importEnumerator.MoveNext()); + + Assert.True(localScopes.MoveNext()); + LocalScope innerMost = reader.GetLocalScope(localScopes.Current); + Assert.Equal(17, innerMost.StartOffset); + Assert.Equal(33, innerMost.EndOffset); + localEnumerator = innerMost.GetLocalVariables().GetEnumerator(); + Assert.True(localEnumerator.MoveNext()); + Assert.Equal("myInt2", reader.GetString(reader.GetLocalVariable(localEnumerator.Current).Name)); + Assert.True(localEnumerator.MoveNext()); + Assert.Equal("myInt3", reader.GetString(reader.GetLocalVariable(localEnumerator.Current).Name)); + Assert.False(localEnumerator.MoveNext()); + Assert.False(localScopes.MoveNext()); + + Assert.True(innerMost.ImportScope.IsNil); + } + + [Fact] + public void DefineDocument_MarkSequencePoint_Validations() + { + ModuleBuilder runtimeModule = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("T"), AssemblyBuilderAccess.Run).DefineDynamicModule("T"); + Assert.Throws(() => runtimeModule.DefineDocument("MySourceFile.cs", SymLanguageType.CSharp)); + + ModuleBuilder mb = new PersistedAssemblyBuilder(new AssemblyName("Assembly"), typeof(object).Assembly).DefineDynamicModule("MyModule"); + TypeBuilder tb = mb.DefineType("Type", TypeAttributes.Public | TypeAttributes.Class); + Assert.Throws("url", () => mb.DefineDocument(null)); + Assert.Throws("url", () => mb.DefineDocument(string.Empty)); + + ILGenerator il = tb.DefineMethod("Method", MethodAttributes.Public | MethodAttributes.Static).GetILGenerator(); + Assert.Throws("document", () => il.MarkSequencePoint(null, 0, 0, 0, 1)); + Assert.Throws("document", () => il.MarkSequencePoint(new TestDocument(), 0, 0, 0, 1)); + Assert.Throws("startLine", () => il.MarkSequencePoint(mb.DefineDocument("MySourceFile.cs"), -1, 1, 1, 1)); + Assert.Throws("startColumn", () => il.MarkSequencePoint(mb.DefineDocument("MySourceFile.cs"), 1, -1, 1, 1)); + Assert.Throws("endLine", () => il.MarkSequencePoint(mb.DefineDocument("MySourceFile.cs"), 1, 1, -1, 1)); + Assert.Throws("endColumn", () => il.MarkSequencePoint(mb.DefineDocument("MySourceFile.cs"), 1, 1, 1, -1)); + Assert.Throws("startLine", () => il.MarkSequencePoint(mb.DefineDocument("MySourceFile.cs"), 0x20000000, 1, 1, 1)); + Assert.Throws("startColumn", () => il.MarkSequencePoint(mb.DefineDocument("MySourceFile.cs"), 1, 0x10000, 1, 1)); + Assert.Throws("endLine", () => il.MarkSequencePoint(mb.DefineDocument("MySourceFile.cs"), 1, 1, 0x20000000, 1)); + Assert.Throws("endColumn", () => il.MarkSequencePoint(mb.DefineDocument("MySourceFile.cs"), 1, 1, 1, 0x10000)); + Assert.Throws("endColumn", () => il.MarkSequencePoint(mb.DefineDocument("MySourceFile.cs"), 1, 1, 1, 1)); + Assert.Throws("endLine", () => il.MarkSequencePoint(mb.DefineDocument("MySourceFile.cs"), 1, 1, 0, 1)); + } + + [Fact] + public void MultipleDocumentsAndSequencePoints() + { + PersistedAssemblyBuilder ab = new PersistedAssemblyBuilder(new AssemblyName("MyAssembly"), typeof(object).Assembly); + ModuleBuilder mb = ab.DefineDynamicModule("MyModule"); + TypeBuilder tb = mb.DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class); + ISymbolDocumentWriter srcDoc1 = mb.DefineDocument("MySource1.cs", SymLanguageType.CSharp); + ISymbolDocumentWriter srcDoc2 = mb.DefineDocument("MySource2.cs", SymLanguageType.CSharp); + ISymbolDocumentWriter srcDoc3 = mb.DefineDocument("MySource3.cs", SymLanguageType.CSharp); + MethodBuilder method = tb.DefineMethod("SumMethod", MethodAttributes.Public | MethodAttributes.Static); + ILGenerator il1 = method.GetILGenerator(); + LocalBuilder local = il1.DeclareLocal(typeof(int)); + local.SetLocalSymInfo("MyInt"); + il1.MarkSequencePoint(srcDoc2, 7, 0, 7, 20); + il1.Emit(OpCodes.Ldarg_0); + il1.Emit(OpCodes.Ldarg_1); + il1.Emit(OpCodes.Add); + il1.Emit(OpCodes.Stloc_0); + il1.MarkSequencePoint(srcDoc1, 8, 0, 9, 18); + il1.Emit(OpCodes.Ldc_I4_2); + il1.Emit(OpCodes.Stloc_1); + il1.MarkSequencePoint(srcDoc1, 0xfeefee, 0, 0xfeefee, 0); // hidden sequence point + il1.Emit(OpCodes.Ldloc_0); + il1.Emit(OpCodes.Ldloc_1); + il1.Emit(OpCodes.Add); + il1.Emit(OpCodes.Stloc_0); + il1.MarkSequencePoint(srcDoc1, 11, 1, 11, 20); + il1.Emit(OpCodes.Ldloc_0); + il1.Emit(OpCodes.Ldloc_1); + il1.Emit(OpCodes.Add); + il1.Emit(OpCodes.Stloc_0); + il1.MarkSequencePoint(srcDoc3, 5, 0, 5, 20); + il1.Emit(OpCodes.Ldloc_0); + il1.Emit(OpCodes.Ret); + + MethodBuilder entryPoint = tb.DefineMethod("Main", MethodAttributes.HideBySig | MethodAttributes.Public | MethodAttributes.Static); + ILGenerator il2 = entryPoint.GetILGenerator(); + il2.Emit(OpCodes.Ldc_I4_S, 10); + il2.Emit(OpCodes.Ldc_I4_1); + il2.Emit(OpCodes.Call, method); + il2.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", [typeof(int)])); + il2.Emit(OpCodes.Ret); + tb.CreateType(); + + MetadataBuilder mdb = ab.GenerateMetadata(out BlobBuilder _, out BlobBuilder _, out MetadataBuilder pdbMetadata); + + BlobBuilder portablePdbBlob = new BlobBuilder(); + PortablePdbBuilder pdbBuilder = new PortablePdbBuilder(pdbMetadata, mdb.GetRowCounts(), default); + pdbBuilder.Serialize(portablePdbBlob); + using TempFile pdbFile = TempFile.Create(); + using var pdbFileStream = new FileStream(pdbFile.Path, FileMode.Create, FileAccess.Write); + portablePdbBlob.WriteContentTo(pdbFileStream); + pdbFileStream.Close(); + + using var fs = new FileStream(pdbFile.Path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + using MetadataReaderProvider provider = MetadataReaderProvider.FromPortablePdbStream(fs); + MetadataReader reader = provider.GetMetadataReader(); + DocumentHandleCollection.Enumerator docEnumerator = reader.Documents.GetEnumerator(); + Assert.Equal(3, reader.Documents.Count); + Assert.True(docEnumerator.MoveNext()); + Document doc1 = reader.GetDocument(docEnumerator.Current); + Assert.Equal("MySource2.cs", reader.GetString(doc1.Name)); + Assert.Equal(SymLanguageType.CSharp, reader.GetGuid(doc1.Language)); + Assert.True(docEnumerator.MoveNext()); + Document doc2 = reader.GetDocument(docEnumerator.Current); + Assert.Equal("MySource1.cs", reader.GetString(doc2.Name)); + Assert.Equal(SymLanguageType.CSharp, reader.GetGuid(doc2.Language)); + Assert.True(docEnumerator.MoveNext()); + Document doc3 = reader.GetDocument(docEnumerator.Current); + Assert.Equal("MySource3.cs", reader.GetString(doc3.Name)); + Assert.Equal(SymLanguageType.CSharp, reader.GetGuid(doc3.Language)); + Assert.False(docEnumerator.MoveNext()); + + MethodDebugInformation mdi1 = reader.GetMethodDebugInformation(MetadataTokens.MethodDebugInformationHandle(method.MetadataToken)); + Assert.True(mdi1.Document.IsNil); + SequencePointCollection.Enumerator spcEnumerator = mdi1.GetSequencePoints().GetEnumerator(); + Assert.True(spcEnumerator.MoveNext()); + SequencePoint sp = spcEnumerator.Current; + Assert.Equal(doc1, reader.GetDocument(sp.Document)); + Assert.Equal(7, sp.StartLine); + Assert.False(sp.IsHidden); + Assert.Equal(0, sp.Offset); + Assert.Equal(0, sp.StartColumn); + Assert.Equal(7, sp.EndLine); + Assert.Equal(20, sp.EndColumn); + Assert.True(spcEnumerator.MoveNext()); + sp = spcEnumerator.Current; + Assert.Equal(doc2, reader.GetDocument(sp.Document)); + Assert.Equal(4, sp.Offset); + Assert.Equal(8, sp.StartLine); + Assert.Equal(0, sp.StartColumn); + Assert.Equal(9, sp.EndLine); + Assert.Equal(18, sp.EndColumn); + Assert.True(spcEnumerator.MoveNext()); + sp = spcEnumerator.Current; + Assert.Equal(doc2, reader.GetDocument(sp.Document)); + Assert.True(sp.IsHidden); + Assert.Equal(6, sp.Offset); + Assert.True(spcEnumerator.MoveNext()); + sp = spcEnumerator.Current; + Assert.Equal(doc2, reader.GetDocument(sp.Document)); + Assert.Equal(10, sp.Offset); + Assert.Equal(11, sp.StartLine); + Assert.Equal(1, sp.StartColumn); + Assert.Equal(11, sp.EndLine); + Assert.Equal(20, sp.EndColumn); + Assert.True(spcEnumerator.MoveNext()); + sp = spcEnumerator.Current; + Assert.Equal(doc3, reader.GetDocument(sp.Document)); + Assert.Equal(24, sp.Offset); + Assert.Equal(5, sp.StartLine); + Assert.Equal(0, sp.StartColumn); + Assert.Equal(5, sp.EndLine); + Assert.Equal(20, sp.EndColumn); + Assert.False(spcEnumerator.MoveNext()); + + LocalScopeHandleCollection.Enumerator localScopes = reader.GetLocalScopes(MetadataTokens.MethodDefinitionHandle(method.MetadataToken)).GetEnumerator(); + Assert.True(localScopes.MoveNext()); + LocalScope locals = reader.GetLocalScope(localScopes.Current); + Assert.Equal(0, locals.StartOffset); + Assert.Equal(16, locals.EndOffset); + LocalVariableHandleCollection.Enumerator localEnumerator = locals.GetLocalVariables().GetEnumerator(); + Assert.True(localEnumerator.MoveNext()); + Assert.Equal("MyInt", reader.GetString(reader.GetLocalVariable(localEnumerator.Current).Name)); + Assert.False(localEnumerator.MoveNext()); + Assert.False(localScopes.MoveNext()); + } + + private class TestDocument : ISymbolDocumentWriter + { + public void SetCheckSum(Guid algorithmId, byte[] checkSum) => throw new NotImplementedException(); + public void SetSource(byte[] source) => throw new NotImplementedException(); + } + } +} diff --git a/src/libraries/System.Reflection.Emit/tests/PortablePdb/PortablePdbStandalonePdbTest.cs b/src/libraries/System.Reflection.Emit/tests/PortablePdb/PortablePdbStandalonePdbTest.cs new file mode 100644 index 00000000000000..e1ce56f151d81c --- /dev/null +++ b/src/libraries/System.Reflection.Emit/tests/PortablePdb/PortablePdbStandalonePdbTest.cs @@ -0,0 +1,234 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Immutable; +using System.Diagnostics.SymbolStore; +using System.IO; +using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; +using System.Reflection.PortableExecutable; +using Xunit; + +namespace System.Reflection.Emit.Tests +{ + [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + public class PortablePdbStandalonePdbTest + { + [Fact] + public void CreateStandalonePDBAndVerifyTest() + { + using (TempFile pdbFile = TempFile.Create()) + using (TempFile file = TempFile.Create()) + { + MethodBuilder method1, entryPoint; + MetadataBuilder metadataBuilder = GenerateAssemblyAndMetadata(out method1, out entryPoint, out BlobBuilder ilStream, out MetadataBuilder pdbMetadata); + MethodDefinitionHandle entryPointHandle = MetadataTokens.MethodDefinitionHandle(entryPoint.MetadataToken); + + BlobBuilder portablePdbBlob = new BlobBuilder(); + PortablePdbBuilder pdbBuilder = new PortablePdbBuilder(pdbMetadata, metadataBuilder.GetRowCounts(), entryPointHandle); + BlobContentId pdbContentId = pdbBuilder.Serialize(portablePdbBlob); + using var pdbFileStream = new FileStream(pdbFile.Path, FileMode.Create, FileAccess.Write); + portablePdbBlob.WriteContentTo(pdbFileStream); + pdbFileStream.Close(); + + DebugDirectoryBuilder debugDirectoryBuilder = new DebugDirectoryBuilder(); + debugDirectoryBuilder.AddCodeViewEntry(pdbFile.Path, pdbContentId, pdbBuilder.FormatVersion); + + ManagedPEBuilder peBuilder = new ManagedPEBuilder( + header: new PEHeaderBuilder(imageCharacteristics: Characteristics.ExecutableImage), + metadataRootBuilder: new MetadataRootBuilder(metadataBuilder), + ilStream: ilStream, + debugDirectoryBuilder: debugDirectoryBuilder, + entryPoint: entryPointHandle); + + BlobBuilder peBlob = new BlobBuilder(); + peBuilder.Serialize(peBlob); + using var assemblyFileStream = new FileStream(file.Path, FileMode.Create, FileAccess.Write); + peBlob.WriteContentTo(assemblyFileStream); + assemblyFileStream.Close(); + + using var fs = new FileStream(pdbFile.Path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + using MetadataReaderProvider provider = MetadataReaderProvider.FromPortablePdbStream(fs); + ValidatePDB(method1, entryPoint, provider.GetMetadataReader()); + } + } + + private static void ValidatePDB(MethodBuilder method, MethodBuilder entryPoint, MetadataReader reader) + { + DocumentHandleCollection.Enumerator docEnumerator = reader.Documents.GetEnumerator(); + Assert.Equal(1, reader.Documents.Count); + Assert.True(docEnumerator.MoveNext()); + Document doc = reader.GetDocument(docEnumerator.Current); + Assert.Equal("MySourceFile.cs", reader.GetString(doc.Name)); + Assert.Equal(SymLanguageType.CSharp, reader.GetGuid(doc.Language)); + Assert.Equal(default, reader.GetGuid(doc.HashAlgorithm)); + Assert.False(docEnumerator.MoveNext()); + + MethodDebugInformation mdi1 = reader.GetMethodDebugInformation(MetadataTokens.MethodDebugInformationHandle(method.MetadataToken)); + Assert.Equal(doc, reader.GetDocument(mdi1.Document)); + SequencePointCollection.Enumerator spce = mdi1.GetSequencePoints().GetEnumerator(); + Assert.True(spce.MoveNext()); + SequencePoint sp = spce.Current; + Assert.False(sp.IsHidden); + Assert.Equal(7, sp.StartLine); + Assert.Equal(0, sp.StartColumn); + Assert.Equal(7, sp.EndLine); + Assert.Equal(20, sp.EndColumn); + Assert.True(spce.MoveNext()); + sp = spce.Current; + Assert.False(sp.IsHidden); + Assert.Equal(8, sp.StartLine); + Assert.Equal(0, sp.StartColumn); + Assert.Equal(9, sp.EndLine); + Assert.Equal(18, sp.EndColumn); + Assert.False(spce.MoveNext()); + + LocalScopeHandleCollection.Enumerator localScopes = reader.GetLocalScopes(MetadataTokens.MethodDefinitionHandle(method.MetadataToken)).GetEnumerator(); + Assert.True(localScopes.MoveNext()); + LocalScope localScope = reader.GetLocalScope(localScopes.Current); + Assert.Equal(0, localScope.StartOffset); + Assert.Equal(12, localScope.EndOffset); + LocalVariableHandleCollection.Enumerator localEnumerator = localScope.GetLocalVariables().GetEnumerator(); + Assert.True(localEnumerator.MoveNext()); + Assert.Equal("MyInt", reader.GetString(reader.GetLocalVariable(localEnumerator.Current).Name)); + Assert.False(localEnumerator.MoveNext()); + + Assert.True(localScope.ImportScope.IsNil); + + Assert.True(localScopes.MoveNext()); + localScope = reader.GetLocalScope(localScopes.Current); + Assert.Equal(4, localScope.StartOffset); + Assert.Equal(10, localScope.EndOffset); + localEnumerator = localScope.GetLocalVariables().GetEnumerator(); + Assert.True(localEnumerator.MoveNext()); + Assert.Equal("MyInt2", reader.GetString(reader.GetLocalVariable(localEnumerator.Current).Name)); + Assert.False(localEnumerator.MoveNext()); + Assert.False(localScopes.MoveNext()); + + ImportScope importScope = reader.GetImportScope(localScope.ImportScope); + Assert.True(importScope.Parent.IsNil); + ImportDefinitionCollection.Enumerator importEnumerator = importScope.GetImports().GetEnumerator(); + Assert.True(importEnumerator.MoveNext()); + ImportDefinition importDef = importEnumerator.Current; + Assert.Equal(ImportDefinitionKind.ImportNamespace, importDef.Kind); + BlobReader blobReader = reader.GetBlobReader(importDef.TargetNamespace); + Assert.Equal("System.Reflection", blobReader.ReadUTF8(blobReader.Length)); + + mdi1 = reader.GetMethodDebugInformation(MetadataTokens.MethodDebugInformationHandle(entryPoint.MetadataToken)); + Assert.Equal(doc, reader.GetDocument(mdi1.Document)); + spce = mdi1.GetSequencePoints().GetEnumerator(); + Assert.True(spce.MoveNext()); + sp = spce.Current; + Assert.False(sp.IsHidden); + Assert.Equal(12, sp.StartLine); + Assert.Equal(0, sp.StartColumn); + Assert.Equal(12, sp.EndLine); + Assert.Equal(37, sp.EndColumn); + Assert.False(spce.MoveNext()); + + localScopes = reader.GetLocalScopes(MetadataTokens.MethodDefinitionHandle(entryPoint.MetadataToken)).GetEnumerator(); + Assert.True(localScopes.MoveNext()); + localScope = reader.GetLocalScope(localScopes.Current); + Assert.Equal(0, localScope.StartOffset); + Assert.Equal(21, localScope.EndOffset); + localEnumerator = localScope.GetLocalVariables().GetEnumerator(); + Assert.True(localEnumerator.MoveNext()); + Assert.Equal("MyLoc1", reader.GetString(reader.GetLocalVariable(localEnumerator.Current).Name)); + Assert.True(localEnumerator.MoveNext()); + Assert.Equal("MyLoc2", reader.GetString(reader.GetLocalVariable(localEnumerator.Current).Name)); + Assert.False(localEnumerator.MoveNext()); + Assert.False(localScopes.MoveNext()); + } + + private static MetadataBuilder GenerateAssemblyAndMetadata(out MethodBuilder method, + out MethodBuilder entryPoint, out BlobBuilder ilStream, out MetadataBuilder pdbMetadata) + { + PersistedAssemblyBuilder ab = new PersistedAssemblyBuilder(new AssemblyName("MyAssembly2"), typeof(object).Assembly); + ModuleBuilder mb = ab.DefineDynamicModule("MyModule2"); + TypeBuilder tb = mb.DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class); + ISymbolDocumentWriter srcdoc = mb.DefineDocument("MySourceFile.cs", SymLanguageType.CSharp); + method = tb.DefineMethod("SumMethod", MethodAttributes.Public | MethodAttributes.Static, typeof(int), [typeof(int), typeof(int)]); + ILGenerator il1 = method.GetILGenerator(); + LocalBuilder local = il1.DeclareLocal(typeof(int)); + local.SetLocalSymInfo("MyInt"); + il1.MarkSequencePoint(srcdoc, 7, 0, 7, 20); + il1.Emit(OpCodes.Ldarg_0); + il1.Emit(OpCodes.Ldarg_1); + il1.Emit(OpCodes.Add); + il1.Emit(OpCodes.Stloc_0); + il1.MarkSequencePoint(srcdoc, 8, 0, 9, 18); + il1.BeginScope(); + il1.UsingNamespace("System.Reflection"); + LocalBuilder local2 = il1.DeclareLocal(typeof(int)); + local2.SetLocalSymInfo("MyInt2"); + il1.Emit(OpCodes.Ldc_I4_2); + il1.Emit(OpCodes.Stloc_1); + il1.Emit(OpCodes.Ldloc_0); + il1.Emit(OpCodes.Ldloc_1); + il1.Emit(OpCodes.Add); + il1.Emit(OpCodes.Stloc_0); + il1.EndScope(); + il1.Emit(OpCodes.Ldloc_0); + il1.Emit(OpCodes.Ret); + + entryPoint = tb.DefineMethod("Mm", MethodAttributes.HideBySig | MethodAttributes.Public | MethodAttributes.Static); + ILGenerator il2 = entryPoint.GetILGenerator(); + local = il2.DeclareLocal(typeof(int)); + local.SetLocalSymInfo("MyLoc1"); + il2.MarkSequencePoint(srcdoc, 12, 0, 12, 37); + local2 = il2.DeclareLocal(typeof(int)); + local2.SetLocalSymInfo("MyLoc2"); + il2.Emit(OpCodes.Ldc_I4_S, 10); + il2.Emit(OpCodes.Stloc_0); + il2.Emit(OpCodes.Ldc_I4_1); + il2.Emit(OpCodes.Stloc_1); + il2.Emit(OpCodes.Ldloc_0); + il2.Emit(OpCodes.Ldloc_1); + il2.Emit(OpCodes.Call, method); + il2.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", [typeof(int)])); + il2.Emit(OpCodes.Ret); + tb.CreateType(); + return ab.GenerateMetadata(out ilStream, out BlobBuilder _, out pdbMetadata); + } + + [Fact] + public void CreateEmbeddedPDBAndVerifyTest() + { + using (TempFile file = TempFile.Create()) + { + MethodBuilder method1, entryPoint; + MetadataBuilder metadataBuilder = GenerateAssemblyAndMetadata(out method1, out entryPoint, out BlobBuilder ilStream, out MetadataBuilder pdbMetadata); + MethodDefinitionHandle entryPointHandle = MetadataTokens.MethodDefinitionHandle(entryPoint.MetadataToken); + + BlobBuilder portablePdbBlob = new BlobBuilder(); + PortablePdbBuilder pdbBuilder = new PortablePdbBuilder(pdbMetadata, metadataBuilder.GetRowCounts(), entryPointHandle); + + BlobContentId pdbContentId = pdbBuilder.Serialize(portablePdbBlob); + DebugDirectoryBuilder debugDirectoryBuilder = new DebugDirectoryBuilder(); + debugDirectoryBuilder.AddEmbeddedPortablePdbEntry(portablePdbBlob, pdbBuilder.FormatVersion); + + ManagedPEBuilder peBuilder = new ManagedPEBuilder( + header: new PEHeaderBuilder(imageCharacteristics: Characteristics.ExecutableImage), + metadataRootBuilder: new MetadataRootBuilder(metadataBuilder), + ilStream: ilStream, + debugDirectoryBuilder: debugDirectoryBuilder, + entryPoint: entryPointHandle); + + BlobBuilder peBlob = new BlobBuilder(); + peBuilder.Serialize(peBlob); + using var fileStream = new FileStream(file.Path, FileMode.Create, FileAccess.Write); + peBlob.WriteContentTo(fileStream); + fileStream.Close(); + + using var fs = new FileStream(file.Path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + using var peReader = new PEReader(fs); + ImmutableArray entries = peReader.ReadDebugDirectory(); + Assert.Equal(1, entries.Length); + Assert.Equal(DebugDirectoryEntryType.EmbeddedPortablePdb, entries[0].Type); + + using MetadataReaderProvider provider = peReader.ReadEmbeddedPortablePdbDebugDirectoryData(entries[0]); + ValidatePDB(method1, entryPoint, provider.GetMetadataReader()); + } + } + } +} diff --git a/src/libraries/System.Reflection.Emit/tests/System.Reflection.Emit.Tests.csproj b/src/libraries/System.Reflection.Emit/tests/System.Reflection.Emit.Tests.csproj index 5317b25145a55d..56d7b3869a1665 100644 --- a/src/libraries/System.Reflection.Emit/tests/System.Reflection.Emit.Tests.csproj +++ b/src/libraries/System.Reflection.Emit/tests/System.Reflection.Emit.Tests.csproj @@ -73,6 +73,8 @@ + +