// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Reflection;
using System.Diagnostics.CodeAnalysis;
#if NET
using System.Runtime.CompilerServices;
#endif
using Microsoft.Build.Execution;
using Microsoft.Build.Framework;
namespace Microsoft.Build.Shared
{
/// <summary>
/// This class packages information about a type loaded from an assembly: for example,
/// the GenerateResource task class type or the ConsoleLogger logger class type.
/// </summary>
internal sealed class LoadedType
{
#region Constructor
/// <summary>
/// Creates an instance of this class for the given type.
/// </summary>
/// <param name="type">The Type to be loaded</param>
/// <param name="assemblyLoadInfo">Information used to load the assembly</param>
/// <param name="loadedAssembly">The assembly which has been loaded, if any</param>
/// <param name="iTaskItemType">type of an ITaskItem</param>
/// <param name="runtime">Assembly runtime based on assembly attributes.</param>
/// <param name="architecture">Assembly architecture extracted from PE flags</param>
/// <param name="loadedViaMetadataLoadContext">Whether this type was loaded via MetadataLoadContext</param>
internal LoadedType(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties)]
Type type,
AssemblyLoadInfo assemblyLoadInfo,
Assembly loadedAssembly,
Type iTaskItemType,
string? runtime = null,
string? architecture = null,
bool loadedViaMetadataLoadContext = false)
{
Assumed.NotNull(type, "We must have the type.");
Assumed.NotNull(assemblyLoadInfo, "We must have the assembly the type was loaded from.");
Assumed.NotNull(loadedAssembly, "The assembly should always be loaded even if only by MetadataLoadContext.");
Type = type;
Assembly = assemblyLoadInfo;
HasSTAThreadAttribute = CheckForHardcodedSTARequirement();
LoadedAssemblyName = loadedAssembly.GetName();
LoadedViaMetadataLoadContext = loadedViaMetadataLoadContext;
Architecture = architecture;
Runtime = runtime;
// Assembly.Location is empty for inline tasks loaded from bytes, and for every assembly in a
// single-file/Native AOT host; in those cases fall back to the original load path. On .NET the
// read is guarded on dynamic-code support so ILC dead-strips it (and its IL3000) under AOT, while
// the JIT still prefers the real loaded location.
#if NET
string loadedAssemblyLocation = RuntimeFeature.IsDynamicCodeSupported ? loadedAssembly.Location : string.Empty;
#else
string loadedAssemblyLocation = loadedAssembly.Location;
#endif
Path = string.IsNullOrEmpty(loadedAssemblyLocation)
? assemblyLoadInfo.AssemblyLocation
: loadedAssemblyLocation;
LoadedAssembly = loadedAssembly;
// This block is reflection only loaded type implementation. Net35 does not support it, and fall backs to former implementation in #else
// Property `Properties` set in this block aren't used by TaskHosts. Properties below are only used on the NodeProvider side to get information about the
// properties and reflect over them without needing them to be fully loaded, so it also isn't need for TaskHosts.
// MetadataLoadContext-loaded Type objects don't support testing for inherited attributes, so we manually walk the BaseType chain.
Type? t = type;
while (t is not null)
{
try
{
if (TypeUtilities.HasAttribute<LoadInSeparateAppDomainAttribute>(t))
{
HasLoadInSeparateAppDomainAttribute = true;
}
if (TypeUtilities.HasAttribute<RunInSTAAttribute>(t))
{
HasSTAThreadAttribute = true;
}
if (t.IsMarshalByRef)
{
IsMarshalByRef = true;
}
}
catch when (loadedViaMetadataLoadContext)
{
// when assembly is loaded via metadata load context we can ignore exception because there is no expectation to have it in proc.
// BUT we should throw for in-proc case and handle it on higher level.
}
t = t.BaseType;
}
PropertyInfo[] props = type.GetProperties(BindingFlags.Instance | BindingFlags.Public);
Properties = new ReflectableTaskPropertyInfo[props.Length];
if (loadedViaMetadataLoadContext)
{
PropertyAssemblyQualifiedNames = new string[props.Length];
}
for (int i = 0; i < props.Length; i++)
{
bool outputAttribute = false;
bool requiredAttribute = false;
foreach (CustomAttributeData attr in CustomAttributeData.GetCustomAttributes(props[i]))
{
try
{
if (attr.AttributeType?.Name.Equals(nameof(OutputAttribute)) == true)
{
outputAttribute = true;
}
else if (attr.AttributeType?.Name.Equals(nameof(RequiredAttribute)) == true)
{
requiredAttribute = true;
}
}
catch (Exception e) when (!ExceptionHandling.IsCriticalException(e))
{
// Skip attributes that can't be loaded
continue;
}
}
// Check whether it's assignable to ITaskItem or ITaskItem[]. Simplify to just checking for ITaskItem.
Type? pt = null;
try
{
pt = props[i].PropertyType;
if (pt.IsArray)
{
pt = pt.GetElementType();
}
}
catch (Exception e) when (!ExceptionHandling.IsCriticalException(e))
{
// Skip properties that can't be loaded
continue;
}
bool isAssignableToITask = false;
try
{
isAssignableToITask = pt != null && iTaskItemType.IsAssignableFrom(pt);
}
catch (Exception e) when (!ExceptionHandling.IsCriticalException(e))
{
// Can't determine assignability, default to false
}
Properties[i] = new ReflectableTaskPropertyInfo(props[i], outputAttribute, requiredAttribute, isAssignableToITask);
if (loadedViaMetadataLoadContext && PropertyAssemblyQualifiedNames != null)
{
try
{
PropertyAssemblyQualifiedNames[i] = Properties[i]?.PropertyType?.AssemblyQualifiedName ?? string.Empty;
}
catch (Exception e) when (!ExceptionHandling.IsCriticalException(e))
{
PropertyAssemblyQualifiedNames[i] = string.Empty;
}
}
}
}
#endregion
/// <summary>
/// Gets whether there's a LoadInSeparateAppDomain attribute on this type.
/// </summary>
public bool HasLoadInSeparateAppDomainAttribute { get; }
/// <summary>
/// Creates an <see cref="ITask"/> instance of this loaded type. When the type declares a constructor
/// that takes a single <see cref="TaskEnvironment"/>, that constructor is invoked with
/// <paramref name="taskEnvironment"/> — falling back to <see cref="TaskEnvironment.Fallback"/> when the
/// caller does not supply one — so the task can compute environment-dependent defaults during
/// construction; otherwise the public parameterless constructor is used. The engine still assigns the
/// TaskEnvironment property separately after construction.
/// </summary>
/// <remarks>
/// Instantiation goes through a cached <c>ConstructorInvoker</c> (or, on frameworks that predate
/// it, the cached <see cref="ConstructorInfo"/>) rather than <see cref="Activator.CreateInstance(Type)"/>
/// / <see cref="Activator.CreateInstance(Type, object[])"/>. This keeps every task-creation path on a
/// single, Native AOT friendly mechanism that generates no dynamic code, while letting repeated
/// instantiations approach the speed of the CLR's cached activator. Constructor discovery is deferred
/// until this first call — see <see cref="GetResolvedConstructor"/>.
/// </remarks>
internal ITask? CreateInstance(TaskEnvironment? taskEnvironment)
{
ResolvedConstructor resolvedConstructor = GetResolvedConstructor();
#if NET
// Neither a parameterless nor a TaskEnvironment constructor exists; surface the same failure
// Activator.CreateInstance would have produced rather than a NullReferenceException.
if (resolvedConstructor.Invoker is null)
{
throw new MissingMethodException(Type.FullName, ".ctor");
}
return resolvedConstructor.NeedsEnvironment
? (ITask?)resolvedConstructor.Invoker.Invoke(taskEnvironment ?? TaskEnvironment.Fallback)
: (ITask?)resolvedConstructor.Invoker.Invoke();
#else
if (resolvedConstructor.Constructor is null)
{
throw new MissingMethodException(Type.FullName, ".ctor");
}
return resolvedConstructor.NeedsEnvironment
? (ITask?)resolvedConstructor.Constructor.Invoke([taskEnvironment ?? TaskEnvironment.Fallback])
: (ITask?)resolvedConstructor.Constructor.Invoke(null);
#endif
}
/// <summary>
/// The public instance constructor <see cref="CreateInstance"/> uses, resolved once by
/// <see cref="GetResolvedConstructor"/>: the TaskEnvironment constructor when the type declares one,
/// otherwise the parameterless constructor. <see cref="NeedsEnvironment"/> records which of the two it
/// is, so no second constructor reference has to be kept around. On .NET only a
/// <c>ConstructorInvoker</c> is retained (the ConstructorInfo it wraps is not, to keep this small);
/// older frameworks that lack ConstructorInvoker invoke the <see cref="ConstructorInfo"/> directly.
/// This is a value type so caching it costs no extra allocation.
/// </summary>
private readonly struct ResolvedConstructor
{
#if NET
internal ResolvedConstructor(ConstructorInvoker? invoker, bool needsEnvironment)
{
Invoker = invoker;
NeedsEnvironment = needsEnvironment;
}
internal ConstructorInvoker? Invoker { get; }
#else
internal ResolvedConstructor(ConstructorInfo? constructor, bool needsEnvironment)
{
Constructor = constructor;
NeedsEnvironment = needsEnvironment;
}
internal ConstructorInfo? Constructor { get; }
#endif
internal bool NeedsEnvironment { get; }
}
private ResolvedConstructor _resolvedConstructor;
private volatile bool _constructorsResolved;
/// <summary>
/// Gets whether there's a STAThread attribute on the Execute method of this type.
/// </summary>
public bool HasSTAThreadAttribute { get; }
/// <summary>
/// Gets whether this type implements MarshalByRefObject.
/// </summary>
public bool IsMarshalByRef { get; }
/// <summary>
/// Gets whether this type was loaded by using MetadataLoadContext.
/// </summary>
public bool LoadedViaMetadataLoadContext { get; }
/// <summary>
/// Determines if the task has a hardcoded requirement for STA thread usage.
/// </summary>
private bool CheckForHardcodedSTARequirement()
{
// Special hard-coded attributes for certain legacy tasks which need to run as STA because they were written before
// we changed to running all tasks in MTA.
if (String.Equals("Microsoft.Build.Tasks.Xaml.PartialClassGenerationTask", Type.FullName, StringComparison.OrdinalIgnoreCase))
{
AssemblyName assemblyName = Type.Assembly.GetName();
Version lastVersionToForce = new Version(3, 5);
if (assemblyName.Version?.CompareTo(lastVersionToForce) > 0)
{
if (String.Equals(assemblyName.Name, "PresentationBuildTasks", StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
}
return false;
}
/// <summary>
/// Returns — resolving on first access and memoizing thereafter — the constructor
/// <see cref="CreateInstance"/> should invoke for this type: the one that takes a single
/// <see cref="TaskEnvironment"/> parameter when the type declares it, otherwise the parameterless
/// constructor (both discovered in a single reflection pass). On frameworks that support it, the result
/// carries a cached <c>ConstructorInvoker</c> for that constructor. The <see cref="TaskEnvironment"/>
/// parameter is matched by full type name so it also works for types loaded via MetadataLoadContext,
/// whose <see cref="TaskEnvironment"/> is a distinct <see cref="Type"/> identity from the one in the
/// current context.
/// </summary>
/// <remarks>
/// Resolution is deferred until first access so that the constructor reflection is only paid for types
/// we actually instantiate — not for the many <see cref="LoadedType"/> instances built solely to
/// marshal property metadata to a task host. A <see cref="LoadedType"/> is cached per task type and
/// shared across threads in multi-threaded builds, so the memoization is intentionally lock-free (rather
/// than, say, a <see cref="System.Lazy{T}"/>, which would add a per-instance allocation this size-tuned
/// type avoids): the worst a race can do is resolve the same (equivalent) constructor on more than one
/// thread, and the volatile <see cref="_constructorsResolved"/> flag guarantees a reader that observes
/// <c>true</c> also observes the fully-written <see cref="_resolvedConstructor"/> that was published
/// before it.
/// </remarks>
private ResolvedConstructor GetResolvedConstructor()
{
if (_constructorsResolved)
{
return _resolvedConstructor;
}
ConstructorInfo? parameterlessConstructor = null;
ConstructorInfo? taskEnvironmentConstructor = null;
try
{
foreach (ConstructorInfo constructor in Type.GetConstructors(BindingFlags.Instance | BindingFlags.Public))
{
ParameterInfo[] parameters = constructor.GetParameters();
if (parameters.Length == 0)
{
parameterlessConstructor = constructor;
}
else if (parameters.Length == 1 &&
string.Equals(parameters[0].ParameterType.FullName, TaskEnvironmentTypeFullName, StringComparison.Ordinal))
{
taskEnvironmentConstructor = constructor;
}
}
}
catch when (LoadedViaMetadataLoadContext)
{
// Reflecting over constructors of a MetadataLoadContext-loaded type can fail; such types are
// executed in a task host rather than instantiated in-proc, so it is safe to report none here.
}
bool needsEnvironment = taskEnvironmentConstructor is not null;
// Prefer the TaskEnvironment constructor when present so a task can compute environment-dependent
// defaults during construction; otherwise use the parameterless constructor.
ConstructorInfo? chosenConstructor = taskEnvironmentConstructor ?? parameterlessConstructor;
#if NET
// Build the cached invoker for the chosen constructor. Types loaded only for metadata inspection
// run in a task host and are never instantiated in-proc, so they never need an invoker (and a
// MetadataLoadContext ConstructorInfo cannot be invoked). ConstructorInvoker caches an optimized,
// Native AOT friendly invocation path so repeated instantiations approach Activator.CreateInstance
// speed without generating dynamic code.
ConstructorInvoker? invoker = chosenConstructor is not null && !LoadedViaMetadataLoadContext
? ConstructorInvoker.Create(chosenConstructor)
: null;
ResolvedConstructor resolvedConstructor = new(invoker, needsEnvironment);
#else
ResolvedConstructor resolvedConstructor = new(chosenConstructor, needsEnvironment);
#endif
_resolvedConstructor = resolvedConstructor;
_constructorsResolved = true;
return resolvedConstructor;
}
private static readonly string TaskEnvironmentTypeFullName = typeof(TaskEnvironment).FullName!;
#region Properties
/// <summary>
/// Gets the type that was loaded from an assembly.
/// </summary>
/// <value>The loaded type.</value>
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties)]
internal Type Type { get; private set; }
internal AssemblyName LoadedAssemblyName { get; private set; }
internal string? Architecture { get; private set; }
internal string? Runtime { get; private set; }
internal string Path { get; private set; }
/// <summary>
/// If we loaded an assembly for this type.
/// We use this information to help created AppDomains to resolve types that it could not load successfully
/// </summary>
internal Assembly LoadedAssembly { get; private set; }
internal ReflectableTaskPropertyInfo[] Properties { get; private set; }
/// <summary>
/// Assembly-qualified names for properties. Only has a value if this type was loaded using MetadataLoadContext.
/// </summary>
internal string[]? PropertyAssemblyQualifiedNames { get; private set; }
/// <summary>
/// Gets the assembly the type was loaded from.
/// </summary>
/// <value>The assembly info for the loaded type.</value>
internal AssemblyLoadInfo Assembly { get; private set; }
#endregion
}
}