|
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
// LinkContext.cs
//
// Author:
// Jb Evain (jbevain@gmail.com)
//
// (C) 2006 Jb Evain
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using ILLink.Shared;
using ILLink.Shared.TypeSystemProxy;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Linker.Dataflow;
using Mono.Linker.Steps;
namespace Mono.Linker
{
public class UnintializedContextFactory
{
public virtual AnnotationStore CreateAnnotationStore(LinkContext context) => new AnnotationStore(context);
public virtual MarkingHelpers CreateMarkingHelpers(LinkContext context) => new MarkingHelpers(context);
public virtual Tracer CreateTracer(LinkContext context) => new Tracer(context);
public virtual EmbeddedXmlInfo CreateEmbeddedXmlInfo() => new();
public virtual AssemblyResolver CreateResolver(LinkContext context) => new AssemblyResolver(context, new ReaderParameters());
}
public static class TargetRuntimeVersion
{
public const int NET5 = 5;
public const int NET6 = 6;
}
public class LinkContext : IMetadataResolver, ITryResolveMetadata, ITryResolveAssemblyName, IDisposable
{
readonly Pipeline _pipeline;
readonly Dictionary<string, AssemblyAction> _actions;
readonly Dictionary<string, string> _parameters;
int? _targetRuntime;
readonly AssemblyResolver _resolver;
TypeNameResolver? _typeNameResolver;
readonly AnnotationStore _annotations;
readonly CustomAttributeSource _customAttributes;
readonly CompilerGeneratedState _compilerGeneratedState;
readonly List<MessageContainer> _cachedWarningMessageContainers;
readonly ILogger _logger;
readonly Dictionary<AssemblyDefinition, bool> _isTrimmable;
readonly UnreachableBlocksOptimizer _unreachableBlocksOptimizer;
public Pipeline Pipeline
{
get { return _pipeline; }
}
public CustomAttributeSource CustomAttributes => _customAttributes;
public CompilerGeneratedState CompilerGeneratedState => _compilerGeneratedState;
public AnnotationStore Annotations => _annotations;
public bool DeterministicOutput { get; set; }
public int ErrorsCount { get; private set; }
public string OutputDirectory { get; set; }
public MetadataTrimming MetadataTrimming { get; set; }
public AssemblyAction TrimAction { get; set; }
public AssemblyAction DefaultAction { get; set; }
public bool LinkSymbols { get; set; }
public bool PreserveSymbolPaths { get; set; }
public bool KeepComInterfaces { get; set; }
public bool KeepMembersForDebugger { get; set; } = true;
public bool IgnoreUnresolved { get; set; } = true;
public bool EnableReducedTracing { get; set; }
public bool KeepUsedAttributeTypesOnly { get; set; }
public bool EnableSerializationDiscovery { get; set; }
public bool DisableOperatorDiscovery { get; set; }
/// <summary>
/// Option to not special case EventSource.
/// Currently, values are hard-coded and does not have a command line option to control
/// </summary>
public bool DisableEventSourceSpecialHandling { get; set; }
public bool IgnoreDescriptors { get; set; }
public bool IgnoreSubstitutions { get; set; }
public bool IgnoreLinkAttributes { get; set; }
public Dictionary<string, bool> FeatureSettings { get; init; }
public List<PInvokeInfo> PInvokes { get; private set; }
public string? PInvokesListFile;
public bool StripSecurity { get; set; }
public Dictionary<string, AssemblyAction> Actions
{
get { return _actions; }
}
public AssemblyResolver Resolver
{
get { return _resolver; }
}
internal TypeNameResolver TypeNameResolver
=> _typeNameResolver ??= new TypeNameResolver(this, this);
public ISymbolReaderProvider SymbolReaderProvider { get; set; }
public bool LogMessages { get; set; }
public MarkingHelpers MarkingHelpers { get; private set; }
public KnownMembers MarkedKnownMembers { get; private set; }
public WarningSuppressionWriter? WarningSuppressionWriter { get; set; }
public HashSet<int> NoWarn { get; set; }
public bool NoTrimWarn { get; set; }
public Dictionary<int, bool> WarnAsError { get; set; }
public bool GeneralWarnAsError { get; set; }
public WarnVersion WarnVersion { get; set; }
public UnconditionalSuppressMessageAttributeState Suppressions { get; set; }
public Tracer Tracer { get; private set; }
internal HashSet<string>? TraceAssembly { get; set; }
public EmbeddedXmlInfo EmbeddedXmlInfo { get; private set; }
public CodeOptimizationsSettings Optimizations { get; set; }
public bool AddReflectionAnnotations { get; set; }
public string? AssemblyListFile { get; set; }
public List<IMarkHandler> MarkHandlers { get; }
public Dictionary<string, bool> SingleWarn { get; set; }
public bool GeneralSingleWarn { get; set; }
public HashSet<string> AssembliesWithGeneratedSingleWarning { get; set; }
public SerializationMarker SerializationMarker { get; }
public LinkContext(Pipeline pipeline, ILogger logger, string outputDirectory)
: this(pipeline, logger, outputDirectory, new UnintializedContextFactory())
{
}
protected LinkContext(Pipeline pipeline, ILogger logger, string outputDirectory, UnintializedContextFactory factory)
{
ArgumentNullException.ThrowIfNull(logger);
_pipeline = pipeline;
_logger = logger;
_resolver = factory.CreateResolver(this);
_actions = new Dictionary<string, AssemblyAction>();
_parameters = new Dictionary<string, string>(StringComparer.Ordinal);
_customAttributes = new CustomAttributeSource(this);
_compilerGeneratedState = new CompilerGeneratedState(this);
_cachedWarningMessageContainers = new List<MessageContainer>();
_isTrimmable = new Dictionary<AssemblyDefinition, bool>();
OutputDirectory = outputDirectory;
FeatureSettings = new Dictionary<string, bool>(StringComparer.Ordinal);
SymbolReaderProvider = new DefaultSymbolReaderProvider(false);
_annotations = factory.CreateAnnotationStore(this);
MarkingHelpers = factory.CreateMarkingHelpers(this);
SerializationMarker = new SerializationMarker(this);
Tracer = factory.CreateTracer(this);
EmbeddedXmlInfo = factory.CreateEmbeddedXmlInfo();
MarkedKnownMembers = new KnownMembers();
PInvokes = new List<PInvokeInfo>();
Suppressions = new UnconditionalSuppressMessageAttributeState(this);
NoWarn = new HashSet<int>();
GeneralWarnAsError = false;
WarnAsError = new Dictionary<int, bool>();
WarnVersion = WarnVersion.Latest;
MarkHandlers = new List<IMarkHandler>();
GeneralSingleWarn = false;
SingleWarn = new Dictionary<string, bool>();
AssembliesWithGeneratedSingleWarning = new HashSet<string>();
_unreachableBlocksOptimizer = new UnreachableBlocksOptimizer(this);
const CodeOptimizations defaultOptimizations =
CodeOptimizations.BeforeFieldInit |
CodeOptimizations.OverrideRemoval |
CodeOptimizations.UnusedInterfaces |
CodeOptimizations.UnusedTypeChecks |
CodeOptimizations.IPConstantPropagation |
CodeOptimizations.UnreachableBodies |
CodeOptimizations.RemoveDescriptors |
CodeOptimizations.RemoveLinkAttributes |
CodeOptimizations.RemoveSubstitutions |
CodeOptimizations.RemoveDynamicDependencyAttribute |
CodeOptimizations.OptimizeTypeHierarchyAnnotations |
CodeOptimizations.SubstituteFeatureGuards;
DisableEventSourceSpecialHandling = true;
Optimizations = new CodeOptimizationsSettings(defaultOptimizations);
}
public void SetFeatureValue(string feature, bool value)
{
Debug.Assert(!string.IsNullOrEmpty(feature));
FeatureSettings[feature] = value;
}
public bool HasFeatureValue(string feature, bool value)
{
return FeatureSettings.TryGetValue(feature, out bool fvalue) && value == fvalue;
}
public TypeDefinition? GetType(string fullName)
{
int pos = fullName.IndexOf(',');
fullName = TypeReferenceExtensions.ToCecilName(fullName);
if (pos == -1)
{
foreach (AssemblyDefinition asm in GetReferencedAssemblies())
{
var type = asm.MainModule.GetType(fullName);
if (type != null)
return type;
}
return null;
}
string asmname = fullName.Substring(pos + 1);
fullName = fullName.Substring(0, pos);
AssemblyDefinition? assembly = Resolve(AssemblyNameReference.Parse(asmname));
return assembly?.MainModule.GetType(fullName);
}
public AssemblyDefinition? TryResolve(string name)
{
return TryResolve(new AssemblyNameReference(name, new Version()));
}
public AssemblyDefinition? TryResolve(AssemblyNameReference name)
{
return _resolver.Resolve(name, probing: true);
}
public AssemblyDefinition? Resolve(IMetadataScope scope)
{
AssemblyNameReference reference = GetReference(scope);
return _resolver.Resolve(reference);
}
public AssemblyDefinition? Resolve(AssemblyNameReference name)
{
return _resolver.Resolve(name);
}
public void RegisterAssembly(AssemblyDefinition assembly)
{
if (SeenFirstTime(assembly))
{
SafeReadSymbols(assembly);
Annotations.SetAction(assembly, CalculateAssemblyAction(assembly));
}
}
protected bool SeenFirstTime(AssemblyDefinition assembly)
{
return !_annotations.HasAction(assembly);
}
public virtual void SafeReadSymbols(AssemblyDefinition assembly)
{
if (assembly.MainModule.HasSymbols)
return;
if (SymbolReaderProvider == null)
throw new InvalidOperationException("Symbol provider is not set");
try
{
var symbolReader = SymbolReaderProvider.GetSymbolReader(
assembly.MainModule,
GetAssemblyLocation(assembly));
if (symbolReader == null)
return;
try
{
assembly.MainModule.ReadSymbols(symbolReader);
}
catch
{
symbolReader.Dispose();
return;
}
// Add symbol reader to annotations only if we have successfully read it
_annotations.AddSymbolReader(assembly, symbolReader);
}
catch { }
}
public virtual ICollection<AssemblyDefinition> ResolveReferences(AssemblyDefinition assembly)
{
List<AssemblyDefinition> references = new List<AssemblyDefinition>();
if (assembly == null)
return references;
foreach (AssemblyNameReference reference in assembly.MainModule.AssemblyReferences)
{
AssemblyDefinition? definition = Resolve(reference);
if (definition != null)
references.Add(definition);
}
return references;
}
static AssemblyNameReference GetReference(IMetadataScope scope)
{
AssemblyNameReference reference;
if (scope is ModuleDefinition moduleDefinition)
{
AssemblyDefinition asm = moduleDefinition.Assembly;
reference = asm.Name;
}
else
reference = (AssemblyNameReference)scope;
return reference;
}
public void RegisterAssemblyAction(string assemblyName, AssemblyAction action)
{
_actions[assemblyName] = action;
}
#if !FEATURE_ILLINK
public void SetAction(AssemblyDefinition assembly, AssemblyAction defaultAction)
{
if (!_actions.TryGetValue(assembly.Name.Name, out AssemblyAction action))
action = defaultAction;
Annotations.SetAction(assembly, action);
}
#endif
public virtual AssemblyAction CalculateAssemblyAction(AssemblyDefinition assembly)
{
if (_actions.TryGetValue(assembly.Name.Name, out AssemblyAction action))
{
if (IsCPPCLIAssembly(assembly.MainModule) && action != AssemblyAction.Copy && action != AssemblyAction.Skip)
{
LogWarning($"Invalid assembly action '{action}' specified for assembly '{assembly.Name.Name}'. C++/CLI assemblies can only be copied or skipped.", 2106, GetAssemblyLocation(assembly));
return AssemblyAction.Copy;
}
return action;
}
if (IsCPPCLIAssembly(assembly.MainModule))
return DefaultAction == AssemblyAction.Skip ? DefaultAction : AssemblyAction.Copy;
if (IsTrimmable(assembly))
return TrimAction;
return DefaultAction;
static bool IsCPPCLIAssembly(ModuleDefinition module)
{
foreach (var type in module.Types)
if (type.Namespace == "<CppImplementationDetails>" ||
type.Namespace == "<CrtImplementationDetails>")
return true;
return false;
}
}
public bool IsTrimmable(AssemblyDefinition assembly)
{
if (_isTrimmable.TryGetValue(assembly, out bool isTrimmable))
return isTrimmable;
if (!assembly.HasCustomAttributes)
{
_isTrimmable.Add(assembly, false);
return false;
}
foreach (var ca in assembly.CustomAttributes)
{
if (!ca.AttributeType.IsTypeOf<AssemblyMetadataAttribute>())
continue;
var args = ca.ConstructorArguments;
if (args.Count != 2)
continue;
if (args[0].Value is not string key || !key.Equals("IsTrimmable", StringComparison.OrdinalIgnoreCase))
continue;
if (args[1].Value is not string value || !value.Equals("True", StringComparison.OrdinalIgnoreCase))
{
LogWarning(GetAssemblyLocation(assembly), DiagnosticId.InvalidIsTrimmableValue, args[1].Value.ToString() ?? "", assembly.Name.Name);
continue;
}
isTrimmable = true;
}
_isTrimmable.Add(assembly, isTrimmable);
return isTrimmable;
}
public virtual AssemblyDefinition[] GetAssemblies()
{
var cache = _resolver.AssemblyCache;
AssemblyDefinition[] asms = new AssemblyDefinition[cache.Count];
cache.Values.CopyTo(asms, 0);
return asms;
}
public AssemblyDefinition? GetLoadedAssembly(string name)
{
if (!string.IsNullOrEmpty(name) && _resolver.AssemblyCache.TryGetValue(name, out var ad))
return ad;
return null;
}
public string GetAssemblyLocation(AssemblyDefinition assembly)
{
return Resolver.GetAssemblyLocation(assembly);
}
public IEnumerable<AssemblyDefinition> GetReferencedAssemblies()
{
var assemblies = GetAssemblies();
foreach (var assembly in assemblies)
yield return assembly;
var loaded = new HashSet<AssemblyDefinition>(assemblies);
var toProcess = new Queue<AssemblyDefinition>(assemblies);
while (toProcess.Count > 0)
{
var assembly = toProcess.Dequeue();
foreach (var reference in ResolveReferences(assembly))
{
if (!loaded.Add(reference))
continue;
yield return reference;
toProcess.Enqueue(reference);
}
}
}
public void SetCustomData(string key, string value)
{
_parameters[key] = value;
}
public bool HasCustomData(string key)
{
return _parameters.ContainsKey(key);
}
public bool TryGetCustomData(string key, [NotNullWhen(true)] out string? value)
{
return _parameters.TryGetValue(key, out value);
}
public void Dispose()
{
_resolver.Dispose();
}
public bool IsOptimizationEnabled(CodeOptimizations optimization, MemberReference context)
{
return Optimizations.IsEnabled(optimization, context?.Module.Assembly);
}
public bool IsOptimizationEnabled(CodeOptimizations optimization, AssemblyDefinition? context)
{
return Optimizations.IsEnabled(optimization, context);
}
public bool CanApplyOptimization(CodeOptimizations optimization, AssemblyDefinition context)
{
return Annotations.GetAction(context) == AssemblyAction.Link &&
IsOptimizationEnabled(optimization, context);
}
public void LogMessage(MessageContainer message)
{
if (message == MessageContainer.Empty)
return;
if ((message.Category == MessageCategory.Diagnostic ||
message.Category == MessageCategory.Info) && !LogMessages)
return;
if (WarningSuppressionWriter != null &&
message.IsWarningMessage(out int? code) &&
message.Origin?.Provider is Mono.Cecil.ICustomAttributeProvider provider)
WarningSuppressionWriter.AddWarning(code.Value, provider);
if (message.Category == MessageCategory.Error || message.Category == MessageCategory.WarningAsError)
ErrorsCount++;
_logger.LogMessage(message);
}
public void LogMessage(string message)
{
if (!LogMessages)
return;
LogMessage(MessageContainer.CreateInfoMessage(message));
}
public void LogDiagnostic(string message)
{
if (!LogMessages)
return;
LogMessage(MessageContainer.CreateDiagnosticMessage(message));
}
/// <summary>
/// Display a warning message to the end user.
/// This API is used for warnings defined in ILLink, not by custom steps. Warning
/// versions are inferred from the code, and every warning that we define is versioned.
/// </summary>
/// <param name="text">Humanly readable message describing the warning</param>
/// <param name="code">Unique warning ID. Please see https://github.com/dotnet/runtime/blob/main/docs/tools/illink/error-codes.md for the list of warnings and possibly add a new one</param>
/// <param name="origin">Filename or member where the warning is coming from</param>
/// <param name="subcategory">Optionally, further categorize this warning</param>
public void LogWarning(string text, int code, MessageOrigin origin, string subcategory = MessageSubCategory.None)
{
WarnVersion version = GetWarningVersion();
MessageContainer warning = MessageContainer.CreateWarningMessage(this, text, code, origin, version, subcategory);
_cachedWarningMessageContainers.Add(warning);
}
/// <summary>
/// Display a warning message to the end user.
/// This API is used for warnings defined in ILLink, not by custom steps. Warning
/// versions are inferred from the code, and every warning that we define is versioned.
/// </summary>
/// <param name="origin">Filename or member where the warning is coming from</param>
/// <param name="id">Unique warning ID. Please see https://github.com/dotnet/runtime/blob/main/docs/tools/illink/error-codes.md for the list of warnings and possibly add a new one</param>
/// <param name="args">Additional arguments to form a humanly readable message describing the warning</param>
public void LogWarning(MessageOrigin origin, DiagnosticId id, params string[] args)
{
WarnVersion version = GetWarningVersion();
MessageContainer warning = MessageContainer.CreateWarningMessage(this, origin, id, version, args);
_cachedWarningMessageContainers.Add(warning);
}
/// <summary>
/// Display a warning message to the end user.
/// This API is used for warnings defined in ILLink, not by custom steps. Warning
/// versions are inferred from the code, and every warning that we define is versioned.
/// </summary>
/// <param name="text">Humanly readable message describing the warning</param>
/// <param name="code">Unique warning ID. Please see https://github.com/dotnet/runtime/blob/main/docs/tools/illink/error-codes.md for the list of warnings and possibly add a new one</param>
/// <param name="origin">Type or member where the warning is coming from</param>
/// <param name="subcategory">Optionally, further categorize this warning</param>
internal void LogWarning(string text, int code, IMemberDefinition origin, int ilOffset = MessageOrigin.UnsetILOffset, string subcategory = MessageSubCategory.None)
{
MessageOrigin _origin = new MessageOrigin(origin, ilOffset);
LogWarning(text, code, _origin, subcategory);
}
/// <summary>
/// Display a warning message to the end user.
/// This API is used for warnings defined in ILLink, not by custom steps. Warning
/// versions are inferred from the code, and every warning that we define is versioned.
/// </summary>
/// <param name="origin">Type or member where the warning is coming from</param>
/// <param name="id">Unique warning ID. Please see https://github.com/dotnet/runtime/blob/main/docs/tools/illink/error-codes.md for the list of warnings and possibly add a new one</param>
/// <param name="args">Additional arguments to form a humanly readable message describing the warning</param>
internal void LogWarning(IMemberDefinition origin, DiagnosticId id, int ilOffset = MessageOrigin.UnsetILOffset, params string[] args)
{
MessageOrigin _origin = new MessageOrigin(origin, ilOffset);
LogWarning(_origin, id, args);
}
/// <summary>
/// Display a warning message to the end user.
/// This API is used for warnings defined in ILLink, not by custom steps. Warning
/// versions are inferred from the code, and every warning that we define is versioned.
/// </summary>
/// <param name="origin">Type or member where the warning is coming from</param>
/// <param name="id">Unique warning ID. Please see https://github.com/dotnet/runtime/blob/main/docs/tools/illink/error-codes.md for the list of warnings and possibly add a new one</param>
/// <param name="args">Additional arguments to form a humanly readable message describing the warning</param>
public void LogWarning(IMemberDefinition origin, DiagnosticId id, params string[] args)
{
MessageOrigin _origin = new MessageOrigin(origin);
LogWarning(_origin, id, args);
}
/// <summary>
/// Display a warning message to the end user.
/// This API is used for warnings defined in ILLink, not by custom steps. Warning
/// versions are inferred from the code, and every warning that we define is versioned.
/// </summary>
/// <param name="text">Humanly readable message describing the warning</param>
/// <param name="code">Unique warning ID. Please see https://github.com/dotnet/runtime/blob/main/docs/tools/illink/error-codes.md for the list of warnings and possibly add a new one</param>
/// <param name="origin">Filename where the warning is coming from</param>
/// <param name="subcategory">Optionally, further categorize this warning</param>
public void LogWarning(string text, int code, string origin, string subcategory = MessageSubCategory.None)
{
MessageOrigin _origin = new MessageOrigin(origin);
LogWarning(text, code, _origin, subcategory);
}
/// <summary>
/// Display a warning message to the end user.
/// This API is used for warnings defined in ILLink, not by custom steps. Warning
/// versions are inferred from the code, and every warning that we define is versioned.
/// </summary>
/// <param name="origin">Filename where the warning is coming from</param>
/// <param name="id">Unique warning ID. Please see https://github.com/dotnet/runtime/blob/main/docs/tools/illink/error-codes.md for the list of warnings and possibly add a new one</param>
/// <param name="args">Additional arguments to form a humanly readable message describing the warning</param>
public void LogWarning(string origin, DiagnosticId id, params string[] args)
{
MessageOrigin _origin = new MessageOrigin(origin);
LogWarning(_origin, id, args);
}
/// <summary>
/// Display an error message to the end user.
/// </summary>
/// <param name="text">Humanly readable message describing the error</param>
/// <param name="code">Unique error ID. Please see https://github.com/dotnet/runtime/blob/main/docs/tools/illink/error-codes.md for the list of errors and possibly add a new one</param>
/// <param name="subcategory">Optionally, further categorize this error</param>
/// <param name="origin">Filename, line, and column where the error was found</param>
public void LogError(string text, int code, string subcategory = MessageSubCategory.None, MessageOrigin? origin = null)
{
var error = MessageContainer.CreateErrorMessage(text, code, subcategory, origin);
LogMessage(error);
}
/// <summary>
/// Display an error message to the end user.
/// </summary>
/// <param name="origin">Filename, line, and column where the error was found</param>
/// <param name="id">Unique error ID. Please see https://github.com/dotnet/runtime/blob/main/docs/tools/illink/error-codes.md and https://github.com/dotnet/runtime/blob/main/src/tools/illink/src/ILLink.Shared/DiagnosticId.cs for the list of errors and possibly add a new one</param>
/// <param name="args">Additional arguments to form a humanly readable message describing the warning</param>
public void LogError(MessageOrigin? origin, DiagnosticId id, params string[] args)
{
var error = MessageContainer.CreateErrorMessage(origin, id, args);
LogMessage(error);
}
/// <summary>
/// Throws a LinkerFatalErrorException
/// </summary>
/// <param name="text">Humanly readable message describing the error</param>
/// <param name="code">Unique error ID. Please see https://github.com/dotnet/runtime/blob/main/docs/tools/illink/error-codes.md
/// for the list of errors and possibly add a new one</param>
/// <param name="subcategory">Optionally, further categorize this error</param>
/// <param name="origin">Filename, line, and column where the error was found</param>
public static void FatalError(string text, int code, string subcategory = MessageSubCategory.None, MessageOrigin? origin = null)
{
throw new LinkerFatalErrorException(MessageContainer.CreateErrorMessage(text, code, subcategory, origin));
}
/// <summary>
/// Throws a LinkerFatalErrorException
/// </summary>
/// <param name="text">Humanly readable message describing the error</param>
/// <param name="code">Unique error ID. Please see https://github.com/dotnet/runtime/blob/main/docs/tools/illink/error-codes.md
/// for the list of errors and possibly add a new one</param>
/// <param name="subcategory">Optionally, further categorize this error</param>
/// <param name="origin">Filename, line, and column where the error was found</param>
/// <param name="innerException">Optional, an inner exception</param>
public static void FatalError(string text, int code, Exception innerException, string subcategory = MessageSubCategory.None, MessageOrigin? origin = null)
{
throw new LinkerFatalErrorException(MessageContainer.CreateErrorMessage(text, code, subcategory, origin), innerException);
}
public void FlushCachedWarnings()
{
_cachedWarningMessageContainers.Sort();
foreach (var warning in _cachedWarningMessageContainers)
LogMessage(warning);
_cachedWarningMessageContainers.Clear();
}
public bool IsWarningSuppressed(int warningCode, string subcategory, MessageOrigin origin)
{
if (subcategory == MessageSubCategory.TrimAnalysis && NoTrimWarn)
return true;
// This warning was turned off by --nowarn.
if (NoWarn.Contains(warningCode))
return true;
if (Suppressions == null)
return false;
return Suppressions.IsSuppressed(warningCode, origin, out _);
}
public bool IsWarningAsError(int warningCode)
{
bool value;
if (GeneralWarnAsError)
return !WarnAsError.TryGetValue(warningCode, out value) || value;
return WarnAsError.TryGetValue(warningCode, out value) && value;
}
public bool IsSingleWarn(string assemblyName)
{
bool value;
if (GeneralSingleWarn)
return !SingleWarn.TryGetValue(assemblyName, out value) || value;
return SingleWarn.TryGetValue(assemblyName, out value) && value;
}
static WarnVersion GetWarningVersion()
{
// This should return an increasing WarnVersion for new warning waves.
return WarnVersion.ILLink5;
}
public int GetTargetRuntimeVersion()
{
if (_targetRuntime != null)
return _targetRuntime.Value;
TypeDefinition? objectType = BCL.FindPredefinedType(WellKnownType.System_Object, this);
_targetRuntime = objectType?.Module.Assembly.Name.Version.Major ?? -1;
return _targetRuntime.Value;
}
readonly Dictionary<MethodReference, MethodDefinition?> methodresolveCache = new();
readonly Dictionary<FieldReference, FieldDefinition?> fieldresolveCache = new();
readonly Dictionary<TypeReference, TypeDefinition?> typeresolveCache = new();
readonly Dictionary<ExportedType, TypeDefinition?> exportedTypeResolveCache = new();
/// <summary>
/// Tries to resolve the MethodReference to a MethodDefinition and logs a warning if it can't
/// </summary>
public MethodDefinition? Resolve(MethodReference methodReference)
{
if (methodReference is MethodDefinition methodDefinition)
return methodDefinition;
if (methodReference is null)
return null;
if (methodresolveCache.TryGetValue(methodReference, out MethodDefinition? md))
{
if (md == null && !IgnoreUnresolved)
ReportUnresolved(methodReference);
return md;
}
#pragma warning disable RS0030 // Cecil's resolve is banned -- this provides the wrapper
md = methodReference.Resolve();
#pragma warning restore RS0030
if (md == null && !IgnoreUnresolved)
ReportUnresolved(methodReference);
methodresolveCache.Add(methodReference, md);
return md;
}
/// <summary>
/// Tries to resolve the MethodReference to a MethodDefinition and returns null if it can't
/// </summary>
public MethodDefinition? TryResolve(MethodReference methodReference)
{
if (methodReference is MethodDefinition methodDefinition)
return methodDefinition;
if (methodReference is null)
return null;
if (methodresolveCache.TryGetValue(methodReference, out MethodDefinition? md))
return md;
#pragma warning disable RS0030 // Cecil's resolve is banned -- this method provides the wrapper
md = methodReference.Resolve();
#pragma warning restore RS0030
methodresolveCache.Add(methodReference, md);
return md;
}
/// <summary>
/// Tries to resolve the FieldReference to a FieldDefinition and logs a warning if it can't
/// </summary>
public FieldDefinition? Resolve(FieldReference fieldReference)
{
if (fieldReference is FieldDefinition fieldDefinition)
return fieldDefinition;
if (fieldReference is null)
return null;
if (fieldresolveCache.TryGetValue(fieldReference, out FieldDefinition? fd))
{
if (fd == null && !IgnoreUnresolved)
ReportUnresolved(fieldReference);
return fd;
}
fd = fieldReference.Resolve();
if (fd == null && !IgnoreUnresolved)
ReportUnresolved(fieldReference);
fieldresolveCache.Add(fieldReference, fd);
return fd;
}
/// <summary>
/// Tries to resolve the FieldReference to a FieldDefinition and returns null if it can't
/// </summary>
public FieldDefinition? TryResolve(FieldReference fieldReference)
{
if (fieldReference is FieldDefinition fieldDefinition)
return fieldDefinition;
if (fieldReference is null)
return null;
if (fieldresolveCache.TryGetValue(fieldReference, out FieldDefinition? fd))
return fd;
fd = fieldReference.Resolve();
fieldresolveCache.Add(fieldReference, fd);
return fd;
}
/// <summary>
/// Tries to resolve the TypeReference to a TypeDefinition and logs a warning if it can't
/// </summary>
public TypeDefinition? Resolve(TypeReference typeReference)
{
if (typeReference is TypeDefinition typeDefinition)
return typeDefinition;
if (typeReference is null)
return null;
if (typeresolveCache.TryGetValue(typeReference, out TypeDefinition? td))
{
if (td == null && !IgnoreUnresolved)
ReportUnresolved(typeReference);
return td;
}
//
// Types which never have TypeDefinition or can have ambiguous definition should not be passed in
//
if (typeReference is GenericParameter || (typeReference is TypeSpecification && typeReference is not GenericInstanceType))
throw new NotSupportedException($"TypeDefinition cannot be resolved from '{typeReference.GetType()}' type");
#pragma warning disable RS0030
td = typeReference.Resolve();
#pragma warning restore RS0030
if (td == null && !IgnoreUnresolved)
ReportUnresolved(typeReference);
typeresolveCache.Add(typeReference, td);
return td;
}
/// <summary>
/// Tries to resolve the TypeReference to a TypeDefinition and returns null if it can't
/// </summary>
public TypeDefinition? TryResolve(TypeReference typeReference)
{
if (typeReference is TypeDefinition typeDefinition)
return typeDefinition;
if (typeReference is null || typeReference is GenericParameter)
return null;
if (typeresolveCache.TryGetValue(typeReference, out TypeDefinition? td))
return td;
if (typeReference is TypeSpecification ts)
{
if (typeReference is FunctionPointerType)
{
td = null;
}
else
{
//
// It returns element-type for arrays and also element type for wrapping types like ByReference, PinnedType, etc
//
td = TryResolve(ts.GetElementType());
}
}
else
{
#pragma warning disable RS0030
td = typeReference.Resolve();
#pragma warning restore RS0030
}
typeresolveCache.Add(typeReference, td);
return td;
}
/// <summary>
/// Tries to resolve the ExportedType to a TypeDefinition and logs a warning if it can't
/// </summary>
public TypeDefinition? Resolve(ExportedType et)
{
if (TryResolve(et) is not TypeDefinition td)
{
ReportUnresolved(et);
return null;
}
return td;
}
/// <summary>
/// Tries to resolve the ExportedType to a TypeDefinition and returns null if it can't
/// </summary>
public TypeDefinition? TryResolve(ExportedType et)
{
if (exportedTypeResolveCache.TryGetValue(et, out var td))
{
return td;
}
#pragma warning disable RS0030 // Cecil's Resolve is banned -- this method provides the wrapper
td = et.Resolve();
#pragma warning restore RS0030
exportedTypeResolveCache.Add(et, td);
return td;
}
public TypeDefinition? TryResolve(AssemblyDefinition assembly, string typeNameString)
{
// It could be cached if it shows up on fast path
return TypeNameResolver.TryResolveTypeName(assembly, typeNameString, out TypeReference? typeReference, out _)
? TryResolve(typeReference)
: null;
}
readonly HashSet<MethodDefinition> _processed_bodies_for_method = new HashSet<MethodDefinition>(2048);
/// <summary>
/// ILLink applies some optimization on method bodies. For example it can remove dead branches of code
/// based on constant propagation. To avoid overmarking, all code which processes the method's IL
/// should only view the IL after it's been optimized.
/// As such typically MethodDefinition.MethodBody should not be accessed directly on the Cecil object model
/// instead all accesses to method body should go through the ILProvider here
/// which will make sure the IL of the method is fully optimized before it's handed out.
/// </summary>
public MethodIL GetMethodIL(Cecil.Cil.MethodBody methodBody)
=> GetMethodIL(methodBody.Method);
public MethodIL GetMethodIL(MethodDefinition method)
{
if (_processed_bodies_for_method.Add(method))
{
_unreachableBlocksOptimizer.ProcessMethod(method);
}
return MethodIL.Create(method.Body);
}
readonly HashSet<MemberReference> unresolved_reported = new();
readonly HashSet<ExportedType> unresolved_exported_types_reported = new();
protected virtual void ReportUnresolved(FieldReference fieldReference)
{
if (unresolved_reported.Add(fieldReference))
LogError(string.Format(SharedStrings.FailedToResolveFieldElementMessage, fieldReference.FullName), (int)DiagnosticId.FailedToResolveMetadataElement);
}
protected virtual void ReportUnresolved(MethodReference methodReference)
{
if (unresolved_reported.Add(methodReference))
LogError(string.Format(SharedStrings.FailedToResolveMethodElementMessage, methodReference.GetDisplayName()), (int)DiagnosticId.FailedToResolveMetadataElement);
}
protected virtual void ReportUnresolved(TypeReference typeReference)
{
if (unresolved_reported.Add(typeReference))
LogError(string.Format(SharedStrings.FailedToResolveTypeElementMessage, typeReference.GetDisplayName()), (int)DiagnosticId.FailedToResolveMetadataElement);
}
protected virtual void ReportUnresolved(ExportedType et)
{
if (unresolved_exported_types_reported.Add(et))
LogError(string.Format(SharedStrings.FailedToResolveTypeElementMessage, et.Name), (int)DiagnosticId.FailedToResolveMetadataElement);
}
}
public class CodeOptimizationsSettings
{
sealed class Pair
{
public Pair(CodeOptimizations set, CodeOptimizations values)
{
this.Set = set;
this.Values = values;
}
public CodeOptimizations Set;
public CodeOptimizations Values;
}
readonly Dictionary<string, Pair> perAssembly = new();
public CodeOptimizationsSettings(CodeOptimizations globalOptimizations)
{
Global = globalOptimizations;
}
public CodeOptimizations Global { get; private set; }
internal bool IsEnabled(CodeOptimizations optimizations, AssemblyDefinition? context)
{
return IsEnabled(optimizations, context?.Name.Name);
}
public bool IsEnabled(CodeOptimizations optimizations, string? assemblyName)
{
// Only one bit is set
Debug.Assert(optimizations != 0 && (optimizations & (optimizations - 1)) == 0);
if (perAssembly.Count > 0 && assemblyName != null &&
perAssembly.TryGetValue(assemblyName, out var assemblySetting) &&
(assemblySetting.Set & optimizations) != 0)
{
return (assemblySetting.Values & optimizations) != 0;
}
return (Global & optimizations) != 0;
}
public void Enable(CodeOptimizations optimizations, string? assemblyContext = null)
{
if (assemblyContext == null)
{
Global |= optimizations;
return;
}
if (!perAssembly.TryGetValue(assemblyContext, out var assemblySetting))
{
perAssembly.Add(assemblyContext, new Pair(optimizations, optimizations));
return;
}
assemblySetting.Set |= optimizations;
assemblySetting.Values |= optimizations;
}
public void Disable(CodeOptimizations optimizations, string? assemblyContext = null)
{
if (assemblyContext == null)
{
Global &= ~optimizations;
return;
}
if (!perAssembly.TryGetValue(assemblyContext, out var assemblySetting))
{
perAssembly.Add(assemblyContext, new Pair(optimizations, 0));
return;
}
assemblySetting.Set |= optimizations;
assemblySetting.Values &= ~optimizations;
}
}
[Flags]
public enum CodeOptimizations
{
BeforeFieldInit = 1 << 0,
/// <summary>
/// Option to disable removal of overrides of virtual methods when a type is never instantiated
///
/// Being able to disable this optimization is helpful when trying to troubleshoot problems caused by types created via reflection or from native
/// that do not get an instance constructor marked.
/// </summary>
OverrideRemoval = 1 << 1,
/// <summary>
/// Option to disable delaying marking of instance methods until an instance of that type could exist
/// </summary>
UnreachableBodies = 1 << 2,
/// <summary>
/// Option to remove .interfaceimpl for interface types that are not used
/// </summary>
UnusedInterfaces = 1 << 3,
/// <summary>
/// Option to do interprocedural constant propagation on return values
/// </summary>
IPConstantPropagation = 1 << 4,
/// <summary>
/// Devirtualizes methods and seals types
/// </summary>
Sealer = 1 << 5,
/// <summary>
/// Option to inline typechecks for never instantiated types
/// </summary>
UnusedTypeChecks = 1 << 6,
RemoveDescriptors = 1 << 20,
RemoveSubstitutions = 1 << 21,
RemoveLinkAttributes = 1 << 22,
RemoveDynamicDependencyAttribute = 1 << 23,
/// <summary>
/// Option to apply annotations to type heirarchy
/// Enable type heirarchy apply in library mode to annotate derived types eagerly
/// Otherwise, type annotation will only be applied with calls to object.GetType()
/// </summary>
OptimizeTypeHierarchyAnnotations = 1 << 24,
/// <summary>
/// Option to substitute properties annotated as FeatureGuard(typeof(RequiresUnreferencedCodeAttribute)) with false
/// </summary>
SubstituteFeatureGuards = 1 << 25,
}
}
|