|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.IO;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
using System.Threading;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis
{
/// <summary>
/// Represents an immutable snapshot of module CLI metadata.
/// </summary>
/// <remarks>This object may allocate significant resources or lock files depending upon how it is constructed.</remarks>
public sealed partial class ModuleMetadata : Metadata
{
private readonly PEModule _module;
/// <summary>
/// Optional action to invoke when this metadata is disposed.
/// </summary>
private Action? _onDispose;
private bool _isDisposed;
private ModuleMetadata(PEReader peReader, Action? onDispose)
: base(isImageOwner: true, id: MetadataId.CreateNewId())
{
_module = new PEModule(this, peReader: peReader, metadataOpt: IntPtr.Zero, metadataSizeOpt: 0, includeEmbeddedInteropTypes: false, ignoreAssemblyRefs: false);
_onDispose = onDispose;
}
private ModuleMetadata(
IntPtr metadata,
int size,
Action? onDispose,
bool includeEmbeddedInteropTypes,
bool ignoreAssemblyRefs)
: base(isImageOwner: true, id: MetadataId.CreateNewId())
{
_module = new PEModule(this, peReader: null, metadataOpt: metadata, metadataSizeOpt: size, includeEmbeddedInteropTypes: includeEmbeddedInteropTypes, ignoreAssemblyRefs: ignoreAssemblyRefs);
_onDispose = onDispose;
}
// creates a copy
private ModuleMetadata(ModuleMetadata metadata)
: base(isImageOwner: false, id: metadata.Id)
{
_module = metadata.Module;
// note: we intentionally do not pass the _onDispose callback to the copy. Only the owner owns the callback
// and controls calling it. This does mean that the callback (and underlying memory it holds onto) may
// disappear once the owner is disposed or GC'd. But that's ok as that is expected semantics. Once an image
// owner is gone, all copies are no longer in a valid state for use.
}
/// <summary>
/// Create metadata module from a raw memory pointer to metadata directory of a PE image or .cormeta section of an object file.
/// Only manifest modules are currently supported.
/// </summary>
/// <param name="metadata">Pointer to the start of metadata block.</param>
/// <param name="size">The size of the metadata block.</param>
/// <exception cref="ArgumentNullException"><paramref name="metadata"/> is null.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="size"/> is not positive.</exception>
public static ModuleMetadata CreateFromMetadata(nint metadata, int size)
=> CreateFromMetadataWorker(metadata, size, onDispose: null);
/// <summary>
/// Create metadata module from a raw memory pointer to metadata directory of a PE image or .cormeta section of an object file.
/// Only manifest modules are currently supported.
/// </summary>
/// <param name="metadata">Pointer to the start of metadata block.</param>
/// <param name="size">The size of the metadata block.</param>
/// <param name="onDispose">Action to run when the metadata module is disposed. This will only be called then
/// this actual metadata instance is disposed. Any instances created from this using <see
/// cref="Metadata.Copy"/> will not call this when they are disposed.</param>
/// <exception cref="ArgumentNullException"><paramref name="onDispose"/> is null.</exception>
public static unsafe ModuleMetadata CreateFromMetadata(
nint metadata,
int size,
Action onDispose)
{
if (onDispose is null)
throw new ArgumentNullException(nameof(onDispose));
return CreateFromMetadataWorker(metadata, size, onDispose);
}
private static ModuleMetadata CreateFromMetadataWorker(
nint metadata,
int size,
Action? onDispose)
{
if (metadata == 0)
{
throw new ArgumentNullException(nameof(metadata));
}
if (size <= 0)
{
throw new ArgumentOutOfRangeException(CodeAnalysisResources.SizeHasToBePositive, nameof(size));
}
return new ModuleMetadata(metadata, size, onDispose, includeEmbeddedInteropTypes: false, ignoreAssemblyRefs: false);
}
internal static ModuleMetadata CreateFromMetadata(IntPtr metadata, int size, bool includeEmbeddedInteropTypes, bool ignoreAssemblyRefs = false)
{
Debug.Assert(metadata != IntPtr.Zero);
Debug.Assert(size > 0);
return new ModuleMetadata(metadata, size, onDispose: null, includeEmbeddedInteropTypes, ignoreAssemblyRefs);
}
/// <summary>
/// Create metadata module from a raw memory pointer to a PE image or an object file.
/// </summary>
/// <param name="peImage">Pointer to the DOS header ("MZ") of a portable executable image.</param>
/// <param name="size">The size of the image pointed to by <paramref name="peImage"/>.</param>
/// <exception cref="ArgumentNullException"><paramref name="peImage"/> is null.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="size"/> is not positive.</exception>
public static unsafe ModuleMetadata CreateFromImage(nint peImage, int size)
=> CreateFromImage((byte*)peImage, size, onDispose: null);
private static unsafe ModuleMetadata CreateFromImage(byte* peImage, int size, Action? onDispose)
{
if (peImage == null)
{
throw new ArgumentNullException(nameof(peImage));
}
if (size <= 0)
{
throw new ArgumentOutOfRangeException(CodeAnalysisResources.SizeHasToBePositive, nameof(size));
}
return new ModuleMetadata(new PEReader(peImage, size), onDispose);
}
/// <summary>
/// Create metadata module from a sequence of bytes.
/// </summary>
/// <param name="peImage">The portable executable image beginning with the DOS header ("MZ").</param>
/// <exception cref="ArgumentNullException"><paramref name="peImage"/> is null.</exception>
public static ModuleMetadata CreateFromImage(IEnumerable<byte> peImage)
{
if (peImage == null)
{
throw new ArgumentNullException(nameof(peImage));
}
return CreateFromImage(ImmutableArray.CreateRange(peImage));
}
/// <summary>
/// Create metadata module from a byte array.
/// </summary>
/// <param name="peImage">Portable executable image beginning with the DOS header ("MZ").</param>
/// <exception cref="ArgumentNullException"><paramref name="peImage"/> is null.</exception>
public static ModuleMetadata CreateFromImage(ImmutableArray<byte> peImage)
{
if (peImage.IsDefault)
{
throw new ArgumentNullException(nameof(peImage));
}
return new ModuleMetadata(new PEReader(peImage), onDispose: null);
}
/// <summary>
/// Create metadata module from a stream.
/// </summary>
/// <param name="peStream">Stream containing portable executable image. Position zero should contain the first byte of the DOS header ("MZ").</param>
/// <param name="leaveOpen">
/// False to close the stream upon disposal of the metadata (the responsibility for disposal of the stream is transferred upon entry of the constructor
/// unless the arguments given are invalid).
/// </param>
/// <exception cref="ArgumentNullException"><paramref name="peStream"/> is null.</exception>
/// <exception cref="ArgumentException">The stream doesn't support seek operations.</exception>
public static ModuleMetadata CreateFromStream(Stream peStream, bool leaveOpen = false)
{
return CreateFromStream(peStream, leaveOpen ? PEStreamOptions.LeaveOpen : PEStreamOptions.Default);
}
/// <summary>
/// Create metadata module from a stream.
/// </summary>
/// <param name="peStream">Stream containing portable executable image. Position zero should contain the first byte of the DOS header ("MZ").</param>
/// <param name="options">
/// Options specifying how sections of the PE image are read from the stream.
/// Unless <see cref="PEStreamOptions.LeaveOpen"/> is specified, the responsibility for disposal of the stream is transferred upon entry of the constructor
/// unless the arguments given are invalid.
/// </param>
/// <exception cref="ArgumentNullException"><paramref name="peStream"/> is null.</exception>
/// <exception cref="ArgumentException">The stream doesn't support read and seek operations.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="options"/> has an invalid value.</exception>
/// <exception cref="BadImageFormatException">
/// <see cref="PEStreamOptions.PrefetchMetadata"/> or <see cref="PEStreamOptions.PrefetchEntireImage"/> is specified and the PE headers of the image are invalid.
/// </exception>
/// <exception cref="IOException">
/// <see cref="PEStreamOptions.PrefetchMetadata"/> or <see cref="PEStreamOptions.PrefetchEntireImage"/> is specified and an error occurs while reading the stream.
/// </exception>
public static ModuleMetadata CreateFromStream(Stream peStream, PEStreamOptions options)
{
if (peStream == null)
{
throw new ArgumentNullException(nameof(peStream));
}
if (!peStream.CanRead || !peStream.CanSeek)
{
throw new ArgumentException(CodeAnalysisResources.StreamMustSupportReadAndSeek, nameof(peStream));
}
var prefetch = (options & (PEStreamOptions.PrefetchEntireImage | PEStreamOptions.PrefetchMetadata)) != 0;
// If this stream is an UnmanagedMemoryStream, we can heavily optimize creating the metadata by directly
// accessing the underlying memory. Note: we can only do this if the caller asked us not to prefetch the
// metadata from the stream. In that case, we want to fall through below and have the PEReader read
// everything into a copy immediately. If, however, we are allowed to be lazy, we can create an efficient
// metadata that is backed directly by the memory that is backed in, and which will release that memory (if
// requested) once it is done with it.
if (!prefetch && peStream is UnmanagedMemoryStream unmanagedMemoryStream)
{
unsafe
{
Action? onDispose = options.HasFlag(PEStreamOptions.LeaveOpen)
? null
: unmanagedMemoryStream.Dispose;
return CreateFromImage(
unmanagedMemoryStream.PositionPointer,
(int)Math.Min(unmanagedMemoryStream.Length, int.MaxValue),
onDispose);
}
}
// Workaround of issue https://github.com/dotnet/corefx/issues/1815:
if (peStream.Length == 0 && (options & PEStreamOptions.PrefetchEntireImage) != 0 && (options & PEStreamOptions.PrefetchMetadata) != 0)
{
// throws BadImageFormatException:
new PEHeaders(peStream);
}
// ownership of the stream is passed on PEReader:
return new ModuleMetadata(new PEReader(peStream, options), onDispose: null);
}
/// <summary>
/// Creates metadata module from a file containing a portable executable image.
/// </summary>
/// <param name="path">File path.</param>
/// <remarks>
/// The file might remain mapped (and read-locked) until this object is disposed.
/// The memory map is only created for large files. Small files are read into memory.
/// </remarks>
/// <exception cref="ArgumentNullException"><paramref name="path"/> is null.</exception>
/// <exception cref="ArgumentException"><paramref name="path"/> is invalid.</exception>
/// <exception cref="IOException">Error opening file <paramref name="path"/>. See <see cref="Exception.InnerException"/> for details.</exception>
/// <exception cref="FileNotFoundException">File <paramref name="path"/> not found.</exception>
/// <exception cref="NotSupportedException">Reading from a file path is not supported by the platform.</exception>
public static ModuleMetadata CreateFromFile(string path)
{
return CreateFromStream(StandardFileSystem.Instance.OpenFileWithNormalizedException(path, FileMode.Open, FileAccess.Read, FileShare.Read));
}
/// <summary>
/// Creates a shallow copy of this object.
/// </summary>
/// <remarks>
/// The resulting copy shares the metadata image and metadata information read from it with the original.
/// It doesn't own the underlying metadata image and is not responsible for its disposal.
///
/// This is used, for example, when a metadata cache needs to return the cached metadata to its users
/// while keeping the ownership of the cached metadata object.
/// </remarks>
internal new ModuleMetadata Copy()
{
return new ModuleMetadata(this);
}
protected override Metadata CommonCopy()
{
return Copy();
}
/// <summary>
/// Frees memory and handles allocated for the module.
/// </summary>
public override void Dispose()
{
_isDisposed = true;
if (IsImageOwner)
{
_module.Dispose();
var onDispose = Interlocked.Exchange(ref _onDispose, null);
onDispose?.Invoke();
}
}
/// <summary>
/// True if the module has been disposed.
/// </summary>
public bool IsDisposed
{
get { return _isDisposed || _module.IsDisposed; }
}
internal PEModule Module
{
get
{
if (IsDisposed)
{
throw new ObjectDisposedException(nameof(ModuleMetadata));
}
return _module;
}
}
/// <summary>
/// Name of the module.
/// </summary>
/// <exception cref="BadImageFormatException">Invalid metadata.</exception>
/// <exception cref="ObjectDisposedException">Module has been disposed.</exception>
public string Name
{
get { return Module.Name; }
}
/// <summary>
/// Version of the module content.
/// </summary>
/// <exception cref="BadImageFormatException">Invalid metadata.</exception>
/// <exception cref="ObjectDisposedException">Module has been disposed.</exception>
public Guid GetModuleVersionId()
{
return Module.GetModuleVersionIdOrThrow();
}
/// <summary>
/// Returns the <see cref="MetadataImageKind"/> for this instance.
/// </summary>
public override MetadataImageKind Kind
{
get { return MetadataImageKind.Module; }
}
/// <summary>
/// Returns the file names of linked managed modules.
/// </summary>
/// <exception cref="BadImageFormatException">When an invalid module name is encountered.</exception>
/// <exception cref="ObjectDisposedException">Module has been disposed.</exception>
public ImmutableArray<string> GetModuleNames()
{
return Module.GetMetadataModuleNamesOrThrow();
}
/// <summary>
/// Returns the metadata reader.
/// </summary>
/// <exception cref="ObjectDisposedException">Module has been disposed.</exception>
/// <exception cref="BadImageFormatException">When an invalid module name is encountered.</exception>
public MetadataReader GetMetadataReader() => MetadataReader;
internal MetadataReader MetadataReader => Module.MetadataReader;
/// <summary>
/// Creates a reference to the module metadata.
/// </summary>
/// <param name="documentation">Provider of XML documentation comments for the metadata symbols contained in the module.</param>
/// <param name="filePath">Path describing the location of the metadata, or null if the metadata have no location.</param>
/// <param name="display">Display string used in error messages to identity the reference.</param>
/// <returns>A reference to the module metadata.</returns>
public PortableExecutableReference GetReference(DocumentationProvider? documentation = null, string? filePath = null, string? display = null)
{
return new MetadataImageReference(this, MetadataReferenceProperties.Module, documentation, filePath, display);
}
}
}
|