|
// 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.
//
// Driver.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.IO;
using System.Reflection;
using System.Runtime.Loader;
using System.Text;
using ILLink.Shared;
using Mono.Cecil;
using Mono.Linker.Steps;
namespace Mono.Linker
{
public partial class Driver : IDisposable
{
const string resolvers = "-a|-x";
const string _linker = "IL Linker";
public static int Main (string[] args)
{
LinkerEventSource.Log.LinkerStart (string.Join ("; ", args));
if (args.Length == 0) {
Console.Error.WriteLine ("No parameters specified");
LinkerEventSource.Log.LinkerStop ();
return 1;
}
if (!ProcessResponseFile (args, out var arguments)) {
LinkerEventSource.Log.LinkerStop ();
return 1;
}
try {
using (Driver driver = new Driver (arguments)) {
return driver.Run ();
}
} catch {
Console.Error.WriteLine ("Fatal error in {0}", _linker);
throw;
} finally {
LinkerEventSource.Log.LinkerStop ();
}
}
readonly Queue<string> arguments;
bool _needAddBypassNGenStep;
LinkContext? context;
protected LinkContext Context {
get {
Debug.Assert (context != null);
return context;
}
set {
Debug.Assert (context == null);
context = value;
}
}
private static readonly char[] s_separators = new char[] { ',', ';', ' ' };
public Driver (Queue<string> arguments)
{
this.arguments = arguments;
}
public static bool ProcessResponseFile (string[] args, out Queue<string> result)
{
result = new Queue<string> ();
foreach (string arg in args) {
if (arg.StartsWith ('@')) {
try {
string responseFileName = arg.Substring (1);
using (var responseFileText = new StreamReader (responseFileName))
ParseResponseFile (responseFileText, result);
} catch (Exception e) when (e is IOException or ObjectDisposedException) {
Console.Error.WriteLine ("Cannot read response file due to '{0}'", e.Message);
return false;
}
} else {
result.Enqueue (arg);
}
}
return true;
}
public static void ParseResponseFile (TextReader reader, Queue<string> result)
{
int cur;
while ((cur = reader.Read ()) >= 0) {
// skip whitespace
while (char.IsWhiteSpace ((char) cur)) {
if ((cur = reader.Read ()) < 0)
break;
}
if (cur < 0) // EOF
break;
StringBuilder argBuilder = new StringBuilder ();
bool inquote = false;
// build up an argument one character at a time
while (true) {
bool copyChar = true;
int numBackslash = 0;
// count backslashes
while (cur == '\\') {
numBackslash++;
if ((cur = reader.Read ()) < 0)
break;
}
if (cur == '"') {
if ((numBackslash % 2) == 0) {
if (inquote && (reader.Peek () == '"')) {
// handle "" escape sequence in a quote
cur = reader.Read ();
} else {
// unescaped " begins/ends a quote
copyChar = false;
inquote = !inquote;
}
}
// treat backslashes before " as escapes
numBackslash /= 2;
}
if (numBackslash > 0)
argBuilder.Append (new string ('\\', numBackslash));
if (cur < 0 || (!inquote && char.IsWhiteSpace ((char) cur)))
break;
if (copyChar)
argBuilder.Append ((char) cur);
cur = reader.Read ();
}
result.Enqueue (argBuilder.ToString ());
}
}
void ErrorMissingArgument (string optionName)
{
Context.LogError (null, DiagnosticId.MissingArgumentForCommanLineOptionName, optionName);
}
public enum DependenciesFileFormat
{
Xml,
Dgml
};
// Perform setup of the LinkContext and parse the arguments.
// Return values:
// 0 => successfully set up context with all arguments
// 1 => argument processing stopped early without errors
// -1 => error setting up context
protected int SetupContext (ILogger? customLogger = null)
{
Pipeline p = GetStandardPipeline ();
context = GetDefaultContext (p, customLogger);
var body_substituter_steps = new Stack<string> ();
var xml_custom_attribute_steps = new Stack<string> ();
var custom_steps = new List<string> ();
var set_optimizations = new List<(CodeOptimizations, string?, bool)> ();
bool dumpDependencies = false;
string? dependenciesFileName = null;
context.StripSecurity = true;
bool new_mvid_used = false;
bool deterministic_used = false;
bool keepCompilersResources = false;
MetadataTrimming metadataTrimming = MetadataTrimming.Any;
DependenciesFileFormat fileType = DependenciesFileFormat.Xml;
List<BaseStep> inputs = CreateDefaultResolvers ();
while (arguments.Count > 0) {
string token = arguments.Dequeue ();
if (token.Length < 2) {
context.LogError (null, DiagnosticId.UnrecognizedCommandLineOption, token);
return -1;
}
//
// Handling of --value like options
//
if (token[0] == '-' && token[1] == '-') {
switch (token) {
case "--help":
Usage ();
return 1;
case "--skip-unresolved":
if (!GetBoolParam (token, l => context.IgnoreUnresolved = l))
return -1;
continue;
case "--verbose":
context.LogMessages = true;
continue;
case "--dependencies-file":
if (!GetStringParam (token, out dependenciesFileName))
return -1;
continue;
case "--dump-dependencies": {
dumpDependencies = true;
string? assemblyName = GetNextStringValue ();
if (assemblyName != null) {
if (!IsValidAssemblyName (assemblyName)) {
context.LogError (null, DiagnosticId.InvalidAssemblyName, assemblyName);
return -1;
}
context.TraceAssembly ??= new HashSet<string> ();
context.TraceAssembly.Add (assemblyName);
}
continue;
}
case "--dependencies-file-format":
if (!GetStringParam (token, out var dependenciesFileFormat))
return -1;
if (!Enum.TryParse (dependenciesFileFormat, ignoreCase: true, out fileType)) {
context.LogError (null, DiagnosticId.InvalidDependenciesFileFormat);
return -1;
}
continue;
case "--reduced-tracing":
if (!GetBoolParam (token, l => context.EnableReducedTracing = l))
return -1;
continue;
case "--used-attrs-only":
if (!GetBoolParam (token, l => context.KeepUsedAttributeTypesOnly = l))
return -1;
continue;
case "--strip-security":
if (!GetBoolParam (token, l => context.StripSecurity = l))
return -1;
continue;
case "--strip-descriptors":
if (!GetBoolParam (token, l => set_optimizations.Add ((CodeOptimizations.RemoveDescriptors, null, l))))
return -1;
continue;
case "--strip-substitutions":
if (!GetBoolParam (token, l => set_optimizations.Add ((CodeOptimizations.RemoveSubstitutions, null, l))))
return -1;
continue;
case "--strip-link-attributes":
if (!GetBoolParam (token, l => set_optimizations.Add ((CodeOptimizations.RemoveLinkAttributes, null, l))))
return -1;
continue;
case "--substitutions":
if (arguments.Count < 1) {
ErrorMissingArgument (token);
return -1;
}
if (!GetStringParam (token, out string? substitutionFile))
return -1;
body_substituter_steps.Push (substitutionFile);
continue;
case "--explicit-reflection":
if (!GetBoolParam (token, l => context.AddReflectionAnnotations = l))
return -1;
continue;
case "--action": {
if (!GetStringParam (token, out string? actionString))
return -1;
AssemblyAction? action = ParseAssemblyAction (actionString);
if (action == null)
return -1;
string? assemblyName = GetNextStringValue ();
if (assemblyName == null) {
context.DefaultAction = action.Value;
continue;
}
if (!IsValidAssemblyName (assemblyName)) {
context.LogError (null, DiagnosticId.InvalidAssemblyName, assemblyName);
return -1;
}
context.RegisterAssemblyAction (assemblyName, action.Value);
continue;
}
case "--trim-mode": {
if (!GetStringParam (token, out string? actionString))
return -1;
AssemblyAction? action = ParseAssemblyAction (actionString);
if (action == null)
return -1;
context.TrimAction = action.Value;
continue;
}
case "--custom-step":
if (!GetStringParam (token, out string? custom_step))
return -1;
custom_steps.Add (custom_step);
continue;
case "--custom-data":
if (arguments.Count < 1) {
ErrorMissingArgument (token);
return -1;
}
var arg = arguments.Dequeue ();
string[] values = arg.Split ('=');
if (values?.Length != 2) {
context.LogError (null, DiagnosticId.CustomDataFormatIsInvalid);
return -1;
}
context.SetCustomData (values[0], values[1]);
continue;
case "--keep-com-interfaces":
if (!GetBoolParam (token, l => context.KeepComInterfaces = l))
return -1;
continue;
case "--keep-compilers-resources":
if (!GetBoolParam (token, l => keepCompilersResources = l))
return -1;
continue;
case "--keep-dep-attributes":
if (!GetBoolParam (token, l => set_optimizations.Add ((CodeOptimizations.RemoveDynamicDependencyAttribute, null, !l))))
return -1;
continue;
case "--keep-metadata": {
if (!GetStringParam (token, out string? mname))
return -1;
if (!TryGetMetadataTrimming (mname, out var type))
return -1;
metadataTrimming &= ~type;
continue;
}
case "--enable-serialization-discovery":
if (!GetBoolParam (token, l => context.EnableSerializationDiscovery = l))
return -1;
continue;
case "--disable-operator-discovery":
if (!GetBoolParam (token, l => context.DisableOperatorDiscovery = l))
return -1;
continue;
case "--ignore-descriptors":
if (!GetBoolParam (token, l => context.IgnoreDescriptors = l))
return -1;
continue;
case "--ignore-substitutions":
if (!GetBoolParam (token, l => context.IgnoreSubstitutions = l))
return -1;
continue;
case "--ignore-link-attributes":
if (!GetBoolParam (token, l => context.IgnoreLinkAttributes = l))
return -1;
continue;
case "--disable-opt": {
if (!GetStringParam (token, out string? optName))
return -1;
if (!GetOptimizationName (optName, out var opt))
return -1;
string? assemblyName = GetNextStringValue ();
set_optimizations.Add ((opt, assemblyName, false));
continue;
}
case "--enable-opt": {
if (!GetStringParam (token, out string? optName))
return -1;
if (!GetOptimizationName (optName, out var opt))
return -1;
string? assemblyName = GetNextStringValue ();
set_optimizations.Add ((opt, assemblyName, true));
continue;
}
case "--feature": {
if (!GetStringParam (token, out string? featureName))
return -1;
if (!GetBoolParam (token, value => {
context.SetFeatureValue (featureName, value);
}))
return -1;
continue;
}
case "--new-mvid":
//
// This is not same as --deterministic which calculates MVID
// from stable assembly content. This option creates a new random
// mvid or uses mvid of the source assembly.
//
if (!GetBoolParam (token, l => {
if (!l)
p.RemoveStep (typeof (RegenerateGuidStep));
}))
return -1;
new_mvid_used = true;
continue;
case "--deterministic":
if (!GetBoolParam (token, l => context.DeterministicOutput = l))
return -1;
deterministic_used = true;
continue;
case "--output-assemblylist":
if (!GetStringParam (token, out string? assemblyListFile))
return -1;
context.AssemblyListFile = assemblyListFile;
continue;
case "--output-pinvokes":
if (!GetStringParam (token, out string? pinvokesListFile))
return -1;
context.PInvokesListFile = pinvokesListFile;
continue;
case "--link-attributes":
if (arguments.Count < 1) {
ErrorMissingArgument (token);
return -1;
}
if (!GetStringParam (token, out string? fileList))
return -1;
foreach (string file in GetFiles (fileList))
xml_custom_attribute_steps.Push (file);
continue;
case "--generate-warning-suppressions":
if (!GetStringParam (token, out string? generateWarningSuppressionsArgument))
return -1;
if (!GetWarningSuppressionWriterFileOutputKind (generateWarningSuppressionsArgument, out var fileOutputKind)) {
context.LogError (null, DiagnosticId.InvalidGenerateWarningSuppressionsValue, generateWarningSuppressionsArgument);
return -1;
}
context.WarningSuppressionWriter = new WarningSuppressionWriter (context, fileOutputKind);
continue;
case "--notrimwarn":
context.NoTrimWarn = true;
continue;
case "--nowarn":
if (!GetStringParam (token, out string? noWarnArgument))
return -1;
context.NoWarn.UnionWith (ProcessWarningCodes (noWarnArgument));
continue;
case "--warnaserror":
case "--warnaserror+":
var warningList = GetNextStringValue ();
if (!string.IsNullOrEmpty (warningList)) {
foreach (var warning in ProcessWarningCodes (warningList))
context.WarnAsError[warning] = true;
} else {
context.GeneralWarnAsError = true;
context.WarnAsError.Clear ();
}
continue;
case "--warnaserror-":
warningList = GetNextStringValue ();
if (!string.IsNullOrEmpty (warningList)) {
foreach (var warning in ProcessWarningCodes (warningList))
context.WarnAsError[warning] = false;
} else {
context.GeneralWarnAsError = false;
context.WarnAsError.Clear ();
}
continue;
case "--warn":
if (!GetStringParam (token, out string? warnVersionArgument))
return -1;
if (!GetWarnVersion (warnVersionArgument, out WarnVersion version))
return -1;
context.WarnVersion = version;
continue;
case "--singlewarn":
case "--singlewarn+": {
string? assemblyName = GetNextStringValue ();
if (assemblyName != null) {
if (!IsValidAssemblyName (assemblyName)) {
context.LogError (null, DiagnosticId.InvalidAssemblyName, assemblyName);
return -1;
}
context.SingleWarn[assemblyName] = true;
} else {
context.GeneralSingleWarn = true;
context.SingleWarn.Clear ();
}
continue;
}
case "--singlewarn-": {
string? assemblyName = GetNextStringValue ();
if (assemblyName != null) {
if (!IsValidAssemblyName (assemblyName)) {
context.LogError (null, DiagnosticId.InvalidAssemblyName, assemblyName);
return -1;
}
context.SingleWarn[assemblyName] = false;
} else {
context.GeneralSingleWarn = false;
context.SingleWarn.Clear ();
}
continue;
}
case "--preserve-symbol-paths":
if (!GetBoolParam (token, l => context.PreserveSymbolPaths = l))
return -1;
continue;
case "--version":
Version ();
return 1;
case "--about":
About ();
return 1;
}
}
if (token[0] == '-' || token[1] == '/') {
switch (token.Substring (1)) {
case "d":
if (!GetStringParam (token, out string? directory))
return -1;
DirectoryInfo info = new DirectoryInfo (directory);
context.Resolver.AddSearchDirectory (info.FullName);
continue;
case "o":
case "out":
if (!GetStringParam (token, out string? outputDirectory))
return -1;
context.OutputDirectory = outputDirectory;
continue;
case "x": {
if (!GetStringParam (token, out string? xmlFile))
return -1;
if (!File.Exists (xmlFile)) {
context.LogError (null, DiagnosticId.XmlDescriptorCouldNotBeFound, xmlFile);
return -1;
}
inputs.Add (new ResolveFromXmlStep (File.OpenRead (xmlFile), xmlFile));
continue;
}
case "a": {
if (!GetStringParam (token, out string? assemblyFile))
return -1;
if (!File.Exists (assemblyFile) && assemblyFile.EndsWith (".dll", StringComparison.InvariantCultureIgnoreCase)) {
context.LogError (null, DiagnosticId.RootAssemblyCouldNotBeFound, assemblyFile);
return -1;
}
AssemblyRootMode rmode = AssemblyRootMode.AllMembers;
var rootMode = GetNextStringValue ();
if (rootMode != null) {
var parsed_rmode = ParseAssemblyRootMode (rootMode);
if (parsed_rmode is null)
return -1;
rmode = parsed_rmode.Value;
}
inputs.Add (new RootAssemblyInput (assemblyFile, rmode));
continue;
}
case "b":
if (!GetBoolParam (token, l => context.LinkSymbols = l))
return -1;
continue;
case "g":
if (!GetBoolParam (token, l => context.DeterministicOutput = !l))
return -1;
continue;
case "z":
if (!GetBoolParam (token, l => context.IgnoreDescriptors = !l))
return -1;
continue;
case "?":
case "h":
case "help":
Usage ();
return 1;
case "reference":
if (!GetStringParam (token, out string? reference))
return -1;
context.Resolver.AddReferenceAssembly (reference);
continue;
}
}
context.LogError (null, DiagnosticId.UnrecognizedCommandLineOption, token);
return -1;
}
if (inputs.Count == 0) {
context.LogError (null, DiagnosticId.NoFilesToLinkSpecified, resolvers);
return -1;
}
if (new_mvid_used && deterministic_used) {
context.LogError (null, DiagnosticId.NewMvidAndDeterministicCannotBeUsedAtSameTime);
return -1;
}
context.MetadataTrimming = metadataTrimming;
// Default to deterministic output
if (!new_mvid_used && !deterministic_used) {
context.DeterministicOutput = true;
}
if (dumpDependencies) {
switch (fileType) {
case DependenciesFileFormat.Xml:
AddXmlDependencyRecorder (context, dependenciesFileName);
break;
case DependenciesFileFormat.Dgml:
AddDgmlDependencyRecorder (context, dependenciesFileName);
break;
default:
context.LogError (null, DiagnosticId.InvalidDependenciesFileFormat);
break;
}
}
if (set_optimizations.Count > 0) {
foreach (var (opt, assemblyName, enable) in set_optimizations) {
if (enable)
context.Optimizations.Enable (opt, assemblyName);
else
context.Optimizations.Disable (opt, assemblyName);
}
}
//
// Modify the default pipeline
//
for (int i = inputs.Count; i != 0; --i)
p.PrependStep (inputs[i - 1]);
foreach (var file in xml_custom_attribute_steps)
AddLinkAttributesStep (p, file);
foreach (var file in body_substituter_steps)
AddBodySubstituterStep (p, file);
if (context.DeterministicOutput)
p.RemoveStep (typeof (RegenerateGuidStep));
if (context.AddReflectionAnnotations)
p.AddStepAfter (typeof (MarkStep), new ReflectionBlockedStep ());
if (_needAddBypassNGenStep)
p.AddStepAfter (typeof (SweepStep), new AddBypassNGenStep ());
if (keepCompilersResources) {
p.RemoveStep (typeof (RemoveResourcesStep));
}
p.AddStepBefore (typeof (OutputStep), new SealerStep ());
//
// Pipeline setup with all steps enabled
//
// RootAssemblyInputStep or ResolveFromXmlStep [at least one of them]
// LinkAttributesStep [optional, possibly many]
// BodySubstituterStep [optional]
// MarkStep
// ReflectionBlockedStep [optional]
// RemoveResourcesStep [optional]
// ProcessWarningsStep
// OutputWarningSuppressions
// SweepStep
// AddBypassNGenStep [optional]
// CodeRewriterStep
// CleanStep
// RegenerateGuidStep [optional]
// SealerStep
// OutputStep
if (context.EnableSerializationDiscovery)
p.MarkHandlers.Add (new DiscoverSerializationHandler ());
if (!context.DisableOperatorDiscovery)
p.MarkHandlers.Add (new DiscoverOperatorsHandler ());
foreach (string custom_step in custom_steps) {
if (!AddCustomStep (p, custom_step))
return -1;
}
return 0;
}
// Returns the exit code of the process. 0 indicates success.
// Known non-recoverable errors (LinkerFatalErrorException) set the exit code
// to the error code.
// May propagate exceptions, which will result in the process getting an
// exit code determined by dotnet.
public int Run (ILogger? customLogger = null)
{
int setupStatus = SetupContext (customLogger);
if (setupStatus > 0)
return 0;
if (setupStatus < 0)
return 1;
Pipeline p = Context.Pipeline;
PreProcessPipeline (p);
try {
p.Process (Context);
} catch (Exception e) when (LogFatalError (e)) {
// Unreachable
throw;
}
Context.FlushCachedWarnings ();
Context.Tracer.Finish ();
return Context.ErrorsCount > 0 ? 1 : 0;
}
/// <summary>
/// This method is called in the exception filter for unexpected exceptions.
/// Prints error messages and returns false to avoid catching in the exception filter.
/// </summary>
bool LogFatalError (Exception e)
{
switch (e) {
case LinkerFatalErrorException lex:
Context.LogMessage (lex.MessageContainer);
Debug.Assert (lex.MessageContainer.Category == MessageCategory.Error);
Debug.Assert (lex.MessageContainer.Code != null);
Debug.Assert (lex.MessageContainer.Code.Value != 0);
break;
case ResolutionException re:
Context.LogError (null, DiagnosticId.FailedToResolveMetadataElement, re.Message);
break;
default:
Context.LogError (null, DiagnosticId.LinkerUnexpectedError);
break;
}
return false;
}
partial void PreProcessPipeline (Pipeline pipeline);
private static IEnumerable<int> ProcessWarningCodes (string value)
{
static string Unquote (string arg)
{
if (arg.Length > 1 && arg[0] == '"' && arg[arg.Length - 1] == '"')
return arg.Substring (1, arg.Length - 2);
return arg;
}
value = Unquote (value);
string[] values = value.Split (s_separators, StringSplitOptions.RemoveEmptyEntries);
foreach (string v in values) {
var id = v.Trim ();
if (!id.StartsWith ("IL", StringComparison.Ordinal) || !ushort.TryParse (id.AsSpan (2), out ushort code))
continue;
yield return code;
}
}
Assembly? GetCustomAssembly (string arg)
{
if (Path.IsPathRooted (arg)) {
var assemblyPath = Path.GetFullPath (arg);
if (File.Exists (assemblyPath)) {
// The CLR will return the already-loaded assembly if the same path is requested multiple times
// (or even if a different path specifies the "same" assembly, based on the MVID).
// Ignore warning, since we're just enabling analyzer for dogfooding
#pragma warning disable IL2026
return AssemblyLoadContext.Default.LoadFromAssemblyPath (assemblyPath);
#pragma warning restore IL2026
}
Context.LogError (null, DiagnosticId.AssemblyInCustomStepOptionCouldNotBeFound, arg);
} else
Context.LogError (null, DiagnosticId.AssemblyPathInCustomStepMustBeFullyQualified, arg);
return null;
}
protected virtual void AddResolveFromXmlStep (Pipeline pipeline, string file)
{
pipeline.PrependStep (new ResolveFromXmlStep (File.OpenRead (file), file));
}
protected virtual void AddLinkAttributesStep (Pipeline pipeline, string file)
{
pipeline.AddStepBefore (typeof (MarkStep), new LinkAttributesStep (File.OpenRead (file), file));
}
protected virtual void AddBodySubstituterStep (Pipeline pipeline, string file)
{
pipeline.AddStepBefore (typeof (MarkStep), new BodySubstituterStep (File.OpenRead (file), file));
}
protected virtual void AddXmlDependencyRecorder (LinkContext context, string? fileName)
{
context.Tracer.AddRecorder (new XmlDependencyRecorder (context, fileName));
}
protected virtual void AddDgmlDependencyRecorder (LinkContext context, string? fileName)
{
context.Tracer.AddRecorder (new DgmlDependencyRecorder (context, fileName));
}
protected bool AddMarkHandler (Pipeline pipeline, string arg)
{
if (!TryGetCustomAssembly (ref arg, out Assembly? custom_assembly))
return false;
var step = ResolveStep<IMarkHandler> (arg, custom_assembly);
if (step == null)
return false;
pipeline.AppendMarkHandler (step);
return true;
}
bool TryGetCustomAssembly (ref string arg, [NotNullWhen (true)] out Assembly? assembly)
{
assembly = null;
int pos = arg.IndexOf (',');
if (pos == -1)
return false;
assembly = GetCustomAssembly (arg.Substring (pos + 1));
if (assembly == null)
return false;
arg = arg.Substring (0, pos);
return true;
}
protected bool AddCustomStep (Pipeline pipeline, string arg)
{
if (!TryGetCustomAssembly (ref arg, out Assembly? custom_assembly))
return false;
string customStepName;
string? targetName = null;
bool before = false;
if (!arg.Contains (':')) {
customStepName = arg;
} else {
string[] parts = arg.Split (':');
if (parts.Length != 2) {
Context.LogError (null, DiagnosticId.InvalidArgForCustomStep, arg);
return false;
}
customStepName = parts[1];
if (!parts[0].StartsWith ('-') && !parts[0].StartsWith ('+')) {
Context.LogError (null, DiagnosticId.ExpectedSignToControlNewStepInsertion);
return false;
}
before = parts[0][0] == '-';
targetName = parts[0].Substring (1);
}
var stepType = ResolveStepType (customStepName, custom_assembly);
if (stepType == null)
return false;
if (typeof (IStep).IsAssignableFrom (stepType)) {
var customStep = (IStep?) Activator.CreateInstance (stepType) ?? throw new InvalidOperationException ();
if (targetName == null) {
pipeline.AppendStep (customStep);
return true;
}
IStep? target = FindStep (pipeline, targetName);
if (target == null) {
Context.LogError (null, DiagnosticId.PipelineStepCouldNotBeFound, targetName);
return false;
}
if (before)
pipeline.AddStepBefore (target, customStep);
else
pipeline.AddStepAfter (target, customStep);
return true;
}
if (typeof (IMarkHandler).IsAssignableFrom (stepType)) {
var customStep = (IMarkHandler?) Activator.CreateInstance (stepType) ?? throw new InvalidOperationException ();
if (targetName == null) {
pipeline.AppendMarkHandler (customStep);
return true;
}
IMarkHandler? target = FindMarkHandler (pipeline, targetName);
if (target == null) {
Context.LogError (null, DiagnosticId.PipelineStepCouldNotBeFound, targetName);
return false;
}
if (before)
pipeline.AddMarkHandlerBefore (target, customStep);
else
pipeline.AddMarkHandlerAfter (target, customStep);
return true;
}
Context.LogError (null, DiagnosticId.CustomStepTypeIsIncompatibleWithLinkerVersion, stepType.ToString ());
return false;
}
protected virtual IStep? FindStep (Pipeline pipeline, string name)
{
foreach (IStep step in pipeline.GetSteps ()) {
Type t = step.GetType ();
if (t.Name == name)
return step;
}
return null;
}
static IMarkHandler? FindMarkHandler (Pipeline pipeline, string name)
{
foreach (IMarkHandler step in pipeline.MarkHandlers) {
Type t = step.GetType ();
if (t.Name == name)
return step;
}
return null;
}
Type? ResolveStepType (string type, Assembly assembly)
{
// Ignore warning, since we're just enabling analyzer for dogfooding
#pragma warning disable IL2026
Type? step = assembly != null ? assembly.GetType (type) : Type.GetType (type, false);
#pragma warning restore IL2026
if (step == null) {
Context.LogError (null, DiagnosticId.CustomStepTypeCouldNotBeFound, type);
return null;
}
return step;
}
TStep? ResolveStep<TStep> (string type, Assembly assembly) where TStep : class
{
// Ignore warning, since we're just enabling analyzer for dogfooding
#pragma warning disable IL2026
Type? step = assembly != null ? assembly.GetType (type) : Type.GetType (type, false);
#pragma warning restore IL2026
if (step == null) {
Context.LogError (null, DiagnosticId.CustomStepTypeCouldNotBeFound, type);
return null;
}
if (!typeof (TStep).IsAssignableFrom (step)) {
Context.LogError (null, DiagnosticId.CustomStepTypeIsIncompatibleWithLinkerVersion, type);
return null;
}
return (TStep?) Activator.CreateInstance (step);
}
static string[] GetFiles (string param)
{
if (param.Length < 1 || param[0] != '@')
return new string[] { param };
string file = param.Substring (1);
return ReadLines (file);
}
static string[] ReadLines (string file)
{
var lines = new List<string> ();
using (StreamReader reader = new StreamReader (file)) {
string? line;
while ((line = reader.ReadLine ()) != null)
lines.Add (line);
}
return lines.ToArray ();
}
AssemblyAction? ParseAssemblyAction (string s)
{
switch (s.ToLowerInvariant ()) {
case "copy":
return AssemblyAction.Copy;
case "copyused":
return AssemblyAction.CopyUsed;
case "link":
return AssemblyAction.Link;
case "skip":
return AssemblyAction.Skip;
case "addbypassngen":
_needAddBypassNGenStep = true;
return AssemblyAction.AddBypassNGen;
case "addbypassngenused":
_needAddBypassNGenStep = true;
return AssemblyAction.AddBypassNGenUsed;
}
Context.LogError (null, DiagnosticId.InvalidAssemblyAction, s);
return null;
}
AssemblyRootMode? ParseAssemblyRootMode (string s)
{
switch (s.ToLowerInvariant ()) {
case "all":
return AssemblyRootMode.AllMembers;
case "visible":
return AssemblyRootMode.VisibleMembers;
case "entrypoint":
return AssemblyRootMode.EntryPoint;
case "library":
return AssemblyRootMode.Library;
}
Context.LogError (null, DiagnosticId.InvalidAssemblyRootMode, s);
return null;
}
bool GetWarnVersion (string text, out WarnVersion version)
{
if (int.TryParse (text, out int versionNum)) {
version = (WarnVersion) versionNum;
if (version >= WarnVersion.ILLink0 && version <= WarnVersion.Latest)
return true;
}
Context.LogError (null, DiagnosticId.InvalidWarningVersion, text);
version = 0;
return false;
}
protected bool GetOptimizationName (string text, out CodeOptimizations optimization)
{
switch (text.ToLowerInvariant ()) {
case "beforefieldinit":
optimization = CodeOptimizations.BeforeFieldInit;
return true;
case "overrideremoval":
optimization = CodeOptimizations.OverrideRemoval;
return true;
case "unreachablebodies":
optimization = CodeOptimizations.UnreachableBodies;
return true;
case "unusedinterfaces":
optimization = CodeOptimizations.UnusedInterfaces;
return true;
case "unusedtypechecks":
optimization = CodeOptimizations.UnusedTypeChecks;
return true;
case "ipconstprop":
optimization = CodeOptimizations.IPConstantPropagation;
return true;
case "sealer":
optimization = CodeOptimizations.Sealer;
return true;
case "substitutefeatureguards":
optimization = CodeOptimizations.SubstituteFeatureGuards;
return true;
}
Context.LogError (null, DiagnosticId.InvalidOptimizationValue, text);
optimization = 0;
return false;
}
bool TryGetMetadataTrimming (string text, out MetadataTrimming metadataTrimming)
{
switch (text.ToLowerInvariant ()) {
case "all":
metadataTrimming = MetadataTrimming.Any;
return true;
case "none":
metadataTrimming = MetadataTrimming.None;
return true;
case "parametername":
metadataTrimming = MetadataTrimming.ParameterName;
return true;
}
Context.LogError (null, DiagnosticId.InvalidMetadataOption, text);
metadataTrimming = 0;
return false;
}
protected static bool GetWarningSuppressionWriterFileOutputKind (string text, out WarningSuppressionWriter.FileOutputKind fileOutputKind)
{
switch (text.ToLowerInvariant ()) {
case "cs":
fileOutputKind = WarningSuppressionWriter.FileOutputKind.CSharp;
return true;
case "xml":
fileOutputKind = WarningSuppressionWriter.FileOutputKind.Xml;
return true;
default:
fileOutputKind = WarningSuppressionWriter.FileOutputKind.CSharp;
return false;
}
}
bool GetBoolParam (string token, Action<bool> action)
{
if (arguments.Count == 0) {
action (true);
return true;
}
var arg = arguments.Peek ();
if (bool.TryParse (arg.ToLowerInvariant (), out bool value)) {
arguments.Dequeue ();
action (value);
return true;
}
if (arg.StartsWith ('-') || arg.StartsWith ('/')) {
action (true);
return true;
}
Context.LogError (null, DiagnosticId.InvalidArgumentForTokenOption, token);
return false;
}
bool GetStringParam (string token, [NotNullWhen (true)] out string? value)
{
value = null;
if (arguments.Count < 1) {
ErrorMissingArgument (token);
return false;
}
var arg = arguments.Dequeue ();
if (!string.IsNullOrEmpty (arg)) {
value = arg;
return true;
}
ErrorMissingArgument (token);
return false;
}
string? GetNextStringValue ()
{
if (arguments.Count < 1)
return null;
var arg = arguments.Peek ();
if (arg.StartsWith ('-') || arg.StartsWith ('/'))
return null;
arguments.Dequeue ();
return arg;
}
protected virtual LinkContext GetDefaultContext (Pipeline pipeline, ILogger? logger)
{
return new LinkContext (pipeline, logger ?? new ConsoleLogger (), "output") {
TrimAction = AssemblyAction.Link,
DefaultAction = AssemblyAction.Link,
KeepComInterfaces = true,
};
}
protected virtual List<BaseStep> CreateDefaultResolvers ()
{
return new List<BaseStep> ();
}
static bool IsValidAssemblyName (string value)
{
return !string.IsNullOrEmpty (value);
}
static void Usage ()
{
Console.WriteLine (_linker);
Console.WriteLine ($"illink [options] {resolvers}");
Console.WriteLine (" -a FILE [MODE] Assembly file used as root assembly with optional MODE value to alter default root mode");
Console.WriteLine (" Mode can be one of the following values");
Console.WriteLine (" all: Keep all members in root assembly");
Console.WriteLine (" default: Use entry point for applications and all members for libraries");
Console.WriteLine (" entrypoint: Use assembly entry point as only root in the assembly");
Console.WriteLine (" library: All assembly members and data needed for secondary trimming are retained");
Console.WriteLine (" visible: Keep all members and types visible outside of the assembly");
Console.WriteLine (" -x FILE XML descriptor file with members to be kept");
Console.WriteLine ();
Console.WriteLine ("Options");
Console.WriteLine (" -d PATH Specify additional directory to search in for assembly references");
Console.WriteLine (" -reference FILE Specify additional file location used to resolve assembly references");
Console.WriteLine (" -b Update debug symbols for all modified files. Defaults to false");
Console.WriteLine (" --preserve-symbol-paths Preserve debug header paths to pdb files. Defaults to false");
Console.WriteLine (" -out PATH Specify the output directory. Defaults to 'output'");
Console.WriteLine (" -h Lists all {0} options", _linker);
Console.WriteLine (" @FILE Read response file for more options");
Console.WriteLine ();
Console.WriteLine ("Actions");
Console.WriteLine (" --trim-mode ACTION Sets action for assemblies annotated with IsTrimmable attribute. Defaults to 'link'");
Console.WriteLine (" copy: Analyze whole assembly and save it to the output");
Console.WriteLine (" copyused: Same as copy but only for assemblies which are needed");
Console.WriteLine (" link: Remove any unused IL or metadata and optimizes the assembly");
Console.WriteLine (" skip: Do not process the assembly");
Console.WriteLine (" addbypassngen: Add BypassNGenAttribute to unused methods");
Console.WriteLine (" addbypassngenused: Same as addbypassngen but unused assemblies are removed");
Console.WriteLine (" --action ACTION Sets action for assemblies that have no IsTrimmable attribute. Defaults to 'link'");
Console.WriteLine (" --action ACTION ASM Overrides the default action for specific assembly name");
Console.WriteLine ();
Console.WriteLine ("Advanced Options");
Console.WriteLine (" --about About the {0}", _linker);
Console.WriteLine (" --custom-step CFG Add a custom step <config> to the existing pipeline");
Console.WriteLine (" Step can use one of following configurations");
Console.WriteLine (" TYPE,PATH_TO_ASSEMBLY: Add user defined type as last step to the pipeline");
Console.WriteLine (" -NAME:TYPE,PATH_TO_ASSEMBLY: Inserts step type before existing step with name");
Console.WriteLine (" +NAME:TYPE,PATH_TO_ASSEMBLY: Add step type after existing step");
Console.WriteLine (" --custom-data KEY=VALUE Populates context data set with user specified key-value pair");
Console.WriteLine (" --deterministic Produce a deterministic output for modified assemblies");
Console.WriteLine (" --ignore-descriptors Skips reading embedded descriptors (short -z). Defaults to false");
Console.WriteLine (" --skip-unresolved Ignore unresolved types, methods, and assemblies. Defaults to true");
Console.WriteLine (" --output-pinvokes PATH Output a JSON file with all modules and entry points of the P/Invokes found");
Console.WriteLine (" --verbose Log messages indicating progress and warnings");
Console.WriteLine (" --nowarn WARN Disable specific warning messages");
Console.WriteLine (" --warn VERSION Only print out warnings with version <= VERSION. Defaults to '9999'");
Console.WriteLine (" VERSION is an integer in the range 0-9999.");
Console.WriteLine (" --warnaserror[+|-] Report all warnings as errors");
Console.WriteLine (" --warnaserror[+|-] WARN Report specific warnings as errors");
Console.WriteLine (" --singlewarn[+|-] Show at most one analysis warning per assembly");
Console.WriteLine (" --singlewarn[+|-] ASM Show at most one analysis warning for a specific assembly");
Console.WriteLine (" --version Print the version number of the {0}", _linker);
Console.WriteLine ();
Console.WriteLine ("Trimming");
Console.WriteLine (" --disable-opt NAME [ASM] Disable one of the default optimizations globaly or for a specific assembly name");
Console.WriteLine (" beforefieldinit: Unused static fields are removed if there is no static ctor");
Console.WriteLine (" ipconstprop: Interprocedural constant propagation on return values");
Console.WriteLine (" overrideremoval: Overrides of virtual methods on types that are never instantiated are removed");
Console.WriteLine (" unreachablebodies: Instance methods that are marked but not executed are converted to throws");
Console.WriteLine (" unusedinterfaces: Removes interface types from declaration when not used");
Console.WriteLine (" unusedtypechecks: Inlines never successful type checks");
Console.WriteLine (" substitutefeatureguards: Substitutes properties annotated as FeatureGuard(typeof(RequiresUnreferencedCodeAttribute)) to false");
Console.WriteLine (" --enable-opt NAME [ASM] Enable one of the additional optimizations globaly or for a specific assembly name");
Console.WriteLine (" sealer: Any method or type which does not have override is marked as sealed");
Console.WriteLine (" --explicit-reflection Adds to members never used through reflection DisablePrivateReflection attribute. Defaults to false");
Console.WriteLine (" --feature FEATURE VALUE Apply any optimizations defined when this feature setting is a constant known at link time");
Console.WriteLine (" --keep-com-interfaces Keep COM interfaces implemented by kept types. Defaults to true");
Console.WriteLine (" --keep-compilers-resources Keep assembly resources used for F# compilation resources. Defaults to false");
Console.WriteLine (" --keep-dep-attributes Keep attributes used for manual dependency tracking. Defaults to false");
Console.WriteLine (" --keep-metadata NAME Keep metadata which would otherwise be removed if not used");
Console.WriteLine (" all: Metadata for any member are all kept");
Console.WriteLine (" parametername: All parameter names are kept");
Console.WriteLine (" --new-mvid Generate a new guid for each linked assembly (short -g). Defaults to true");
Console.WriteLine (" --strip-descriptors Remove XML descriptor resources for linked assemblies. Defaults to true");
Console.WriteLine (" --strip-security Remove metadata and code related to Code Access Security. Defaults to true");
Console.WriteLine (" --substitutions FILE Configuration file with field or methods substitution rules");
Console.WriteLine (" --ignore-substitutions Skips reading embedded substitutions. Defaults to false");
Console.WriteLine (" --strip-substitutions Remove XML substitution resources for linked assemblies. Defaults to true");
Console.WriteLine (" --used-attrs-only Attribute usage is removed if the attribute type is not used. Defaults to false");
Console.WriteLine (" --link-attributes FILE Supplementary custom attribute definitions for attributes controlling the trimming behavior.");
Console.WriteLine (" --ignore-link-attributes Skips reading embedded attributes. Defaults to false");
Console.WriteLine (" --strip-link-attributes Remove XML link attributes resources for linked assemblies. Defaults to true");
Console.WriteLine ();
Console.WriteLine ("Analyzer");
Console.WriteLine (" --dependencies-file FILE Specify the dependencies output. Defaults to 'output/linker-dependencies.xml'");
Console.WriteLine (" if 'xml' is file format, 'output/linker-dependencies.dgml if 'dgml' is file format");
Console.WriteLine (" --dump-dependencies Dump dependencies for the ILLink analyzer tool");
Console.WriteLine (" --dependencies-file-format FORMAT Specify output file type. Defaults to 'xml'");
Console.WriteLine (" xml: outputs an .xml file");
Console.WriteLine (" dgml: outputs a .dgml file");
Console.WriteLine (" --reduced-tracing Reduces dependency output related to assemblies that will not be modified");
Console.WriteLine ("");
}
static void Version ()
{
Console.WriteLine ("{0} Version {1}",
_linker,
System.Reflection.Assembly.GetExecutingAssembly ().GetName ().Version);
}
static void About ()
{
Console.WriteLine ("For more information, visit the project Web site");
Console.WriteLine (" https://github.com/dotnet/runtime/tree/main/src/tools/illink");
}
static Pipeline GetStandardPipeline ()
{
Pipeline p = new Pipeline ();
p.AppendStep (new ProcessReferencesStep ());
p.AppendStep (new MarkStep ());
p.AppendStep (new RemoveResourcesStep ());
p.AppendStep (new ValidateVirtualMethodAnnotationsStep ());
p.AppendStep (new ProcessWarningsStep ());
p.AppendStep (new OutputWarningSuppressions ());
p.AppendStep (new SweepStep ());
p.AppendStep (new CheckSuppressionsDispatcher ());
p.AppendStep (new CodeRewriterStep ());
p.AppendStep (new CleanStep ());
p.AppendStep (new RegenerateGuidStep ());
p.AppendStep (new OutputStep ());
return p;
}
public void Dispose ()
{
context?.Dispose ();
}
}
}
|