< Summary

Line coverage
0%
Covered lines: 0
Uncovered lines: 100
Coverable lines: 100
Total lines: 300
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 40
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.ctor(...)100%110%
.ctor(...)100%110%
FromPortablePdbImage(...)100%110%
FromMetadataImage(...)0%440%
FromPortablePdbImage(...)100%110%
FromMetadataImage(...)0%220%
FromPortablePdbStream(...)100%110%
FromMetadataStream(...)0%14140%
Dispose()0%440%
GetMetadataReader(...)0%440%
CanReuseReader(...)0%660%
GetMetadataBlock()0%660%

File(s)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/MetadataReaderProvider.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Collections.Immutable;
 5using System.Diagnostics;
 6using System.IO;
 7using System.Reflection.Internal;
 8using System.Text;
 9using System.Threading;
 10
 11namespace System.Reflection.Metadata
 12{
 13    /// <summary>
 14    /// Provides a <see cref="MetadataReader"/> metadata stored in an array of bytes, a memory block, or a stream.
 15    /// </summary>
 16    /// <remarks>
 17    /// Supported formats:
 18    /// - ECMA-335 CLI (Common Language Infrastructure) metadata (<see cref="FromMetadataImage(byte*, int)"/>)
 19    /// - Edit and Continue metadata delta (<see cref="FromMetadataImage(byte*, int)"/>)
 20    /// - Portable PDB metadata (<see cref="FromPortablePdbImage(byte*, int)"/>)
 21    /// </remarks>
 22    public sealed class MetadataReaderProvider : IDisposable
 23    {
 24        // Either we have a provider and create metadata block lazily or
 25        // we have no provider and metadata block is created in the ctor.
 26        private MemoryBlockProvider? _blockProviderOpt;
 27        private AbstractMemoryBlock? _lazyMetadataBlock;
 28
 29        // cached reader
 30        private MetadataReader? _lazyMetadataReader;
 031        private readonly object _metadataReaderGuard = new object();
 32
 033        internal MetadataReaderProvider(AbstractMemoryBlock metadataBlock)
 034        {
 035            Debug.Assert(metadataBlock != null);
 036            _lazyMetadataBlock = metadataBlock;
 037        }
 38
 039        private MetadataReaderProvider(MemoryBlockProvider blockProvider)
 040        {
 041            Debug.Assert(blockProvider != null);
 042            _blockProviderOpt = blockProvider;
 043        }
 44
 45        /// <summary>
 46        /// Creates a Portable PDB metadata provider over a blob stored in memory.
 47        /// </summary>
 48        /// <param name="start">Pointer to the start of the Portable PDB blob.</param>
 49        /// <param name="size">The size of the Portable PDB blob.</param>
 50        /// <exception cref="ArgumentNullException"><paramref name="start"/> is <see cref="IntPtr.Zero"/>.</exception>
 51        /// <exception cref="ArgumentOutOfRangeException"><paramref name="size"/> is negative.</exception>
 52        /// <remarks>
 53        /// The memory is owned by the caller and not released on disposal of the <see cref="MetadataReaderProvider"/>.
 54        /// The caller is responsible for keeping the memory alive and unmodified throughout the lifetime of the <see cr
 55        /// The content of the blob is not read during the construction of the <see cref="MetadataReaderProvider"/>
 56        /// </remarks>
 057        public static unsafe MetadataReaderProvider FromPortablePdbImage(byte* start, int size) => FromMetadataImage(sta
 58
 59        /// <summary>
 60        /// Creates a metadata provider over an image stored in memory.
 61        /// </summary>
 62        /// <param name="start">Pointer to the start of the metadata blob.</param>
 63        /// <param name="size">The size of the metadata blob.</param>
 64        /// <exception cref="ArgumentNullException"><paramref name="start"/> is <see cref="IntPtr.Zero"/>.</exception>
 65        /// <exception cref="ArgumentOutOfRangeException"><paramref name="size"/> is negative.</exception>
 66        /// <remarks>
 67        /// The memory is owned by the caller and not released on disposal of the <see cref="MetadataReaderProvider"/>.
 68        /// The caller is responsible for keeping the memory alive and unmodified throughout the lifetime of the <see cr
 69        /// The content of the blob is not read during the construction of the <see cref="MetadataReaderProvider"/>
 70        /// </remarks>
 71        public static unsafe MetadataReaderProvider FromMetadataImage(byte* start, int size)
 072        {
 073            if (start is null)
 074            {
 075                Throw.ArgumentNull(nameof(start));
 76            }
 77
 078            if (size < 0)
 079            {
 080                throw new ArgumentOutOfRangeException(nameof(size));
 81            }
 82
 083            return new MetadataReaderProvider(new ExternalMemoryBlockProvider(start, size));
 084        }
 85
 86        /// <summary>
 87        /// Creates a Portable PDB metadata provider over a byte array.
 88        /// </summary>
 89        /// <param name="image">Portable PDB image.</param>
 90        /// <remarks>
 91        /// The content of the image is not read during the construction of the <see cref="MetadataReaderProvider"/>
 92        /// </remarks>
 93        /// <exception cref="ArgumentNullException"><paramref name="image"/> is null.</exception>
 094        public static MetadataReaderProvider FromPortablePdbImage(ImmutableArray<byte> image) => FromMetadataImage(image
 95
 96        /// <summary>
 97        /// Creates a provider over a byte array.
 98        /// </summary>
 99        /// <param name="image">Metadata image.</param>
 100        /// <remarks>
 101        /// The content of the image is not read during the construction of the <see cref="MetadataReaderProvider"/>
 102        /// </remarks>
 103        /// <exception cref="ArgumentNullException"><paramref name="image"/> is null.</exception>
 104        public static MetadataReaderProvider FromMetadataImage(ImmutableArray<byte> image)
 0105        {
 0106            if (image.IsDefault)
 0107            {
 0108                Throw.ArgumentNull(nameof(image));
 109            }
 110
 0111            return new MetadataReaderProvider(new ByteArrayMemoryProvider(image));
 0112        }
 113
 114        /// <summary>
 115        /// Creates a provider for a stream of the specified size beginning at its current position.
 116        /// </summary>
 117        /// <param name="stream">Stream.</param>
 118        /// <param name="size">Size of the metadata blob in the stream. If not specified the metadata blob is assumed to
 119        /// <param name="options">
 120        /// Options specifying how sections of the image are read from the stream.
 121        ///
 122        /// Unless <see cref="MetadataStreamOptions.LeaveOpen"/> is specified, ownership of the stream is transferred to
 123        /// upon successful argument validation. It will be disposed by the <see cref="MetadataReaderProvider"/> and the
 124        ///
 125        /// Unless <see cref="MetadataStreamOptions.PrefetchMetadata"/> is specified no data
 126        /// is read from the stream during the construction of the <see cref="MetadataReaderProvider"/>. Furthermore, th
 127        /// by caller while the <see cref="MetadataReaderProvider"/> is alive and undisposed.
 128        ///
 129        /// If <see cref="MetadataStreamOptions.PrefetchMetadata"/>, the <see cref="MetadataReaderProvider"/>
 130        /// will have read all of the data requested during construction. As such, if <see cref="MetadataStreamOptions.L
 131        /// specified, the caller retains full ownership of the stream and is assured that it will not be manipulated by
 132        /// after construction.
 133        /// </param>
 134        /// <exception cref="ArgumentNullException"><paramref name="stream"/> is null.</exception>
 135        /// <exception cref="ArgumentException"><paramref name="stream"/> doesn't support read and seek operations.</exc
 136        /// <exception cref="ArgumentOutOfRangeException">Size is negative or extends past the end of the stream.</excep
 0137        public static MetadataReaderProvider FromPortablePdbStream(Stream stream, MetadataStreamOptions options = Metada
 138
 139        /// <summary>
 140        /// Creates a provider for a stream of the specified size beginning at its current position.
 141        /// </summary>
 142        /// <param name="stream">Stream.</param>
 143        /// <param name="size">Size of the metadata blob in the stream. If not specified the metadata blob is assumed to
 144        /// <param name="options">
 145        /// Options specifying how sections of the image are read from the stream.
 146        ///
 147        /// Unless <see cref="MetadataStreamOptions.LeaveOpen"/> is specified, ownership of the stream is transferred to
 148        /// upon successful argument validation. It will be disposed by the <see cref="MetadataReaderProvider"/> and the
 149        ///
 150        /// Unless <see cref="MetadataStreamOptions.PrefetchMetadata"/> is specified no data
 151        /// is read from the stream during the construction of the <see cref="MetadataReaderProvider"/>. Furthermore, th
 152        /// by caller while the <see cref="MetadataReaderProvider"/> is alive and undisposed.
 153        ///
 154        /// If <see cref="MetadataStreamOptions.PrefetchMetadata"/>, the <see cref="MetadataReaderProvider"/>
 155        /// will have read all of the data requested during construction. As such, if <see cref="MetadataStreamOptions.L
 156        /// specified, the caller retains full ownership of the stream and is assured that it will not be manipulated by
 157        /// after construction.
 158        /// </param>
 159        /// <exception cref="ArgumentNullException"><paramref name="stream"/> is null.</exception>
 160        /// <exception cref="ArgumentException"><paramref name="stream"/> doesn't support read and seek operations.</exc
 161        /// <exception cref="ArgumentOutOfRangeException">Size is negative or extends past the end of the stream.</excep
 162        /// <exception cref="IOException">Error reading from the stream (only when <see cref="MetadataStreamOptions.Pref
 163        public static MetadataReaderProvider FromMetadataStream(Stream stream, MetadataStreamOptions options = MetadataS
 0164        {
 0165            if (stream is null)
 0166            {
 0167                Throw.ArgumentNull(nameof(stream));
 168            }
 169
 0170            if (!stream.CanRead || !stream.CanSeek)
 0171            {
 0172                throw new ArgumentException(SR.StreamMustSupportReadAndSeek, nameof(stream));
 173            }
 174
 0175            if (!options.IsValid())
 0176            {
 0177                throw new ArgumentOutOfRangeException(nameof(options));
 178            }
 179
 0180            long start = stream.Position;
 0181            int actualSize = StreamExtensions.GetAndValidateSize(stream, size, nameof(stream));
 182
 183            MetadataReaderProvider result;
 0184            bool closeStream = true;
 185            try
 0186            {
 0187                if ((options & MetadataStreamOptions.PrefetchMetadata) == 0)
 0188                {
 0189                    result = new MetadataReaderProvider(new StreamMemoryBlockProvider(stream, start, actualSize, (option
 0190                    closeStream = false;
 0191                }
 192                else
 0193                {
 194                    // Read in the entire image or metadata blob:
 0195                    result = new MetadataReaderProvider(StreamMemoryBlockProvider.ReadMemoryBlockNoLock(stream, start, a
 196
 197                    // We read all we need, the stream is going to be closed.
 0198                }
 0199            }
 200            finally
 0201            {
 0202                if (closeStream && (options & MetadataStreamOptions.LeaveOpen) == 0)
 0203                {
 0204                    stream.Dispose();
 0205                }
 0206            }
 207
 0208            return result;
 0209        }
 210
 211        /// <summary>
 212        /// Disposes all memory allocated by the reader.
 213        /// </summary>
 214        /// <remarks>
 215        /// <see cref="Dispose"/>  can be called multiple times (but not in parallel).
 216        /// It is not safe to call <see cref="Dispose"/> in parallel with any other operation on the <see cref="Metadata
 217        /// or reading from the underlying memory.
 218        /// </remarks>
 219        public void Dispose()
 0220        {
 0221            _blockProviderOpt?.Dispose();
 0222            _blockProviderOpt = null;
 223
 0224            _lazyMetadataBlock?.Dispose();
 0225            _lazyMetadataBlock = null;
 226
 0227            _lazyMetadataReader = null;
 0228        }
 229
 230        /// <summary>
 231        /// Gets a <see cref="MetadataReader"/> from a <see cref="MetadataReaderProvider"/>.
 232        /// </summary>
 233        /// <remarks>
 234        /// The caller must keep the <see cref="MetadataReaderProvider"/> undisposed throughout the lifetime of the meta
 235        ///
 236        /// If this method is called multiple times each call with arguments equal to the arguments passed to the previo
 237        /// returns the same instance of <see cref="MetadataReader"/> as the previous call.
 238        /// </remarks>
 239        /// <exception cref="ArgumentException">The encoding of <paramref name="utf8Decoder"/> is not <see cref="UTF8Enc
 240        /// <exception cref="PlatformNotSupportedException">The current platform is big-endian.</exception>
 241        /// <exception cref="IOException">IO error while reading from the underlying stream.</exception>
 242        /// <exception cref="ObjectDisposedException">Provider has been disposed.</exception>
 243        public unsafe MetadataReader GetMetadataReader(MetadataReaderOptions options = MetadataReaderOptions.Default, Me
 0244        {
 0245            var cachedReader = _lazyMetadataReader;
 246
 0247            if (CanReuseReader(cachedReader, options, utf8Decoder))
 0248            {
 0249                return cachedReader!;
 250            }
 251
 252            // If multiple threads attempt to open a metadata reader with the same options and decoder
 253            // it's cheaper to wait for the other thread to finish initializing the reader than to open
 254            // two readers and discard one.
 255            // Note that it's rare to reader the same metadata using different options.
 0256            lock (_metadataReaderGuard)
 0257            {
 0258                cachedReader = _lazyMetadataReader;
 259
 0260                if (CanReuseReader(cachedReader, options, utf8Decoder))
 0261                {
 0262                    return cachedReader!;
 263                }
 264
 0265                AbstractMemoryBlock metadata = GetMetadataBlock();
 0266                var newReader = new MetadataReader(metadata.Pointer, metadata.Size, options, utf8Decoder, memoryOwner: t
 0267                _lazyMetadataReader = newReader;
 0268                return newReader;
 269            }
 0270        }
 271
 272        private static bool CanReuseReader(MetadataReader? reader, MetadataReaderOptions options, MetadataStringDecoder?
 0273        {
 0274            return reader != null && reader.Options == options && ReferenceEquals(reader.UTF8Decoder, utf8DecoderOpt ?? 
 0275        }
 276
 277        /// <exception cref="IOException">IO error while reading from the underlying stream.</exception>
 278        /// <exception cref="ObjectDisposedException">Provider has been disposed.</exception>
 279        internal AbstractMemoryBlock GetMetadataBlock()
 0280        {
 0281            if (_lazyMetadataBlock == null)
 0282            {
 0283                if (_blockProviderOpt == null)
 0284                {
 0285                    throw new ObjectDisposedException(nameof(MetadataReaderProvider));
 286                }
 287
 0288                var newBlock = _blockProviderOpt.GetMemoryBlock(0, _blockProviderOpt.Size);
 0289                if (Interlocked.CompareExchange(ref _lazyMetadataBlock, newBlock, null) != null)
 0290                {
 291                    // another thread created the block already, we need to dispose ours:
 0292                    newBlock.Dispose();
 0293                }
 0294            }
 295
 0296            return _lazyMetadataBlock;
 0297        }
 298    }
 299}
 300