|
// 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.
//
// SweepStep.cs
//
// Author:
// Jb Evain (jbevain@gmail.com)
//
// (C) 2006 Jb Evain
// (C) 2007 Novell, Inc.
//
// 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.Linq;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Collections.Generic;
namespace Mono.Linker.Steps
{
public class SweepStep : BaseStep
{
readonly bool sweepSymbols;
public SweepStep(bool sweepSymbols = true)
{
this.sweepSymbols = sweepSymbols;
}
protected override void Process()
{
var assemblies = Annotations.GetAssemblies().ToArray();
// Ensure that any assemblies which need to be removed are marked for deletion,
// including assemblies which are not referenced by any others.
foreach (var assembly in assemblies)
RemoveUnmarkedAssembly(assembly);
// Look for references (including to previously unresolved assemblies) marked for deletion
foreach (var assembly in assemblies)
UpdateAssemblyReferencesToRemovedAssemblies(assembly);
// Update scopes before removing any type forwarder.
foreach (var assembly in assemblies)
{
var action = Annotations.GetAction(assembly);
switch (action)
{
case AssemblyAction.CopyUsed:
case AssemblyAction.Link:
case AssemblyAction.Save:
bool changed = SweepAssemblyReferences(assembly);
if (changed && action == AssemblyAction.CopyUsed)
Annotations.SetAction(assembly, AssemblyAction.Save);
break;
}
}
foreach (var assembly in assemblies)
ProcessAssemblyAction(assembly);
// Ensure that we remove any assemblies which were resolved while sweeping references
foreach (var assembly in Annotations.GetAssemblies().ToArray())
{
if (!assemblies.Any(processedAssembly => processedAssembly == assembly))
{
Debug.Assert(!IsMarkedAssembly(assembly));
Annotations.SetAction(assembly, AssemblyAction.Delete);
}
}
}
void RemoveUnmarkedAssembly(AssemblyDefinition assembly)
{
// Check if unmarked whole assembly can be turned into full
// assembly removal (AssemblyAction.Delete)
switch (Annotations.GetAction(assembly))
{
case AssemblyAction.AddBypassNGenUsed:
case AssemblyAction.CopyUsed:
case AssemblyAction.Link:
if (!IsMarkedAssembly(assembly))
RemoveAssembly(assembly);
break;
}
}
void UpdateAssemblyReferencesToRemovedAssemblies(AssemblyDefinition assembly)
{
var action = Annotations.GetAction(assembly);
switch (action)
{
case AssemblyAction.Copy:
case AssemblyAction.Delete:
case AssemblyAction.Link:
case AssemblyAction.Save:
case AssemblyAction.Skip:
return;
case AssemblyAction.CopyUsed:
case AssemblyAction.AddBypassNGen:
case AssemblyAction.AddBypassNGenUsed:
foreach (var reference in assembly.MainModule.AssemblyReferences)
{
AssemblyDefinition? ad = Context.Resolver.Resolve(reference);
if (ad == null)
continue;
RemoveUnmarkedAssembly(ad);
if (Annotations.GetAction(ad) != AssemblyAction.Delete)
continue;
// Assembly was removed in the output but it's referenced by
// other assembly with action which does not update references
switch (action)
{
case AssemblyAction.CopyUsed:
//
// Assembly has a reference to another assembly which has been fully removed. This can
// happen when for example the reference assembly is 'copy-used' and it's not needed.
//
// or
//
// Assembly can contain type references with
// type forwarders to deleted assembly (facade) when
// facade assemblies are not kept. For that reason we need to
// rewrite the copy to save to update the scopes not to point
// forwarding assembly (facade).
//
// foo.dll -> facade.dll -> lib.dll
// copy | copy (delete) | link
//
Annotations.SetAction(assembly, AssemblyAction.Save);
continue;
case AssemblyAction.AddBypassNGenUsed:
Annotations.SetAction(assembly, AssemblyAction.AddBypassNGen);
goto case AssemblyAction.AddBypassNGen;
case AssemblyAction.AddBypassNGen:
continue;
}
}
break;
default:
throw new ArgumentOutOfRangeException(action.ToString());
}
}
protected void ProcessAssemblyAction(AssemblyDefinition assembly)
{
switch (Annotations.GetAction(assembly))
{
case AssemblyAction.AddBypassNGenUsed:
Annotations.SetAction(assembly, AssemblyAction.AddBypassNGen);
goto case AssemblyAction.AddBypassNGen;
case AssemblyAction.CopyUsed:
AssemblyAction assemblyAction = AssemblyAction.Copy;
if (SweepTypeForwarders(assembly))
{
// Need to sweep references, in case sweeping type forwarders removed any
SweepAssemblyReferences(assembly);
assemblyAction = AssemblyAction.Save;
}
Annotations.SetAction(assembly, assemblyAction);
break;
case AssemblyAction.Copy:
break;
case AssemblyAction.Link:
SweepAssembly(assembly);
break;
case AssemblyAction.AddBypassNGen:
// FIXME: AddBypassNGen is just wrong, it should not be action as we need to
// turn it to Action.Save here to e.g. correctly update debug symbols
case AssemblyAction.Save:
if (SweepTypeForwarders(assembly))
{
// Need to sweep references, in case sweeping type forwarders removed any
SweepAssemblyReferences(assembly);
}
break;
}
}
protected virtual void SweepAssembly(AssemblyDefinition assembly)
{
var types = new List<TypeDefinition>();
ModuleDefinition main = assembly.MainModule;
bool updateScopes = false;
foreach (TypeDefinition type in main.Types)
{
if (!ShouldRemove(type))
{
SweepType(type);
types.Add(type);
updateScopes = true;
continue;
}
// Is <Module> type.
if (type.MetadataToken.RID == 1)
types.Add(type);
else
ElementRemoved(type);
}
main.Types.Clear();
foreach (TypeDefinition type in types)
main.Types.Add(type);
SweepResources(assembly);
updateScopes |= SweepCustomAttributes(assembly);
foreach (var module in assembly.Modules)
updateScopes |= SweepCustomAttributes(module);
//
// MainModule module references are used by pinvoke
//
if (main.HasModuleReferences)
updateScopes |= SweepCollectionMetadata(main.ModuleReferences);
if (main.EntryPoint != null && !Annotations.IsMarked(main.EntryPoint))
{
main.EntryPoint = null;
}
if (SweepTypeForwarders(assembly) || updateScopes)
SweepAssemblyReferences(assembly);
}
bool IsMarkedAssembly(AssemblyDefinition assembly)
{
return Annotations.IsMarked(assembly.MainModule);
}
bool CanSweepNamesForMember(IMemberDefinition member, MetadataTrimming metadataTrimming)
{
return (Context.MetadataTrimming & metadataTrimming) != 0 && !Annotations.IsReflectionUsed(member);
}
protected virtual void RemoveAssembly(AssemblyDefinition assembly)
{
Annotations.SetAction(assembly, AssemblyAction.Delete);
}
void SweepResources(AssemblyDefinition assembly)
{
var resourcesToRemove = Annotations.GetResourcesToRemove(assembly);
if (resourcesToRemove == null)
return;
var resources = assembly.MainModule.Resources;
foreach (var resource in resourcesToRemove)
resources.Remove(resource);
}
bool SweepTypeForwarders(AssemblyDefinition assembly)
{
return assembly.MainModule.HasExportedTypes &&
SweepCollectionMetadata(assembly.MainModule.ExportedTypes);
}
protected virtual void SweepType(TypeDefinition type)
{
if (type.HasFields)
SweepCollectionWithCustomAttributes(type.Fields);
if (type.HasMethods)
SweepMethods(type.Methods);
if (type.HasNestedTypes)
SweepNestedTypes(type);
if (type.HasInterfaces)
SweepInterfaces(type);
if (type.HasCustomAttributes)
SweepCustomAttributes(type);
if (type.HasGenericParameters)
SweepGenericParameters(type.GenericParameters);
if (type.HasProperties)
SweepCustomAttributeCollection(type.Properties);
if (type.HasEvents)
SweepCustomAttributeCollection(type.Events);
if (type.HasFields && !type.IsBeforeFieldInit && !Annotations.HasPreservedStaticCtor(type) && !type.IsEnum)
type.IsBeforeFieldInit = true;
}
protected void SweepNestedTypes(TypeDefinition type)
{
for (int i = 0; i < type.NestedTypes.Count; i++)
{
var nested = type.NestedTypes[i];
if (ShouldRemove(nested))
{
ElementRemoved(type.NestedTypes[i]);
type.NestedTypes.RemoveAt(i--);
}
else
{
SweepType(nested);
}
}
}
protected void SweepInterfaces(TypeDefinition type)
{
for (int i = type.Interfaces.Count - 1; i >= 0; i--)
{
var iface = type.Interfaces[i];
if (ShouldRemove(iface))
{
InterfaceRemoved(type, iface);
type.Interfaces.RemoveAt(i);
}
else
{
SweepCustomAttributes(iface);
}
}
}
protected void SweepGenericParameters(Collection<GenericParameter> genericParameters)
{
foreach (var gp in genericParameters)
{
SweepCustomAttributes(gp);
if (gp.HasConstraints)
SweepCustomAttributeCollection(gp.Constraints);
}
}
protected void SweepCustomAttributes(TypeDefinition type)
{
bool removed = SweepCustomAttributes(type as ICustomAttributeProvider);
if (removed && type.HasSecurity && ShouldSetHasSecurityToFalse(type, type))
type.HasSecurity = false;
}
protected void SweepCustomAttributes(MethodDefinition method)
{
bool removed = SweepCustomAttributes(method as ICustomAttributeProvider);
if (removed && method.HasSecurity && ShouldSetHasSecurityToFalse(method, method))
method.HasSecurity = false;
}
bool ShouldSetHasSecurityToFalse(ISecurityDeclarationProvider providerAsSecurity, ICustomAttributeProvider provider)
{
if (!providerAsSecurity.HasSecurityDeclarations)
{
// If the method or type had security before and all attributes were removed, or no remaining attributes are security attributes,
// then we need to set HasSecurity to false
if (!provider.HasCustomAttributes || provider.CustomAttributes.All(attr =>
{
TypeDefinition? attributeType = Context.TryResolve(attr.AttributeType);
return attributeType == null || !IsSecurityAttributeType(attributeType);
}))
return true;
}
return false;
}
bool IsSecurityAttributeType(TypeDefinition definition)
{
if (definition == null)
return false;
if (definition.Namespace == "System.Security")
{
return definition.FullName switch
{
// This seems to be one attribute in the System.Security namespace that doesn't count
// as an attribute that requires HasSecurity to be true
"System.Security.SecurityCriticalAttribute" => false,
_ => true,
};
}
var baseDefinition = Context.TryResolve(definition.BaseType);
if (baseDefinition == null)
return false;
return IsSecurityAttributeType(baseDefinition);
}
protected bool SweepCustomAttributes(ICustomAttributeProvider provider)
{
bool removed = false;
for (int i = provider.CustomAttributes.Count - 1; i >= 0; i--)
{
var attribute = provider.CustomAttributes[i];
if (!Annotations.IsMarked(attribute))
{
CustomAttributeUsageRemoved(provider, attribute);
provider.CustomAttributes.RemoveAt(i);
removed = true;
}
}
return removed;
}
protected void SweepCustomAttributeCollection<T>(Collection<T> providers) where T : ICustomAttributeProvider
{
foreach (var provider in providers)
SweepCustomAttributes(provider);
}
protected virtual void SweepMethods(Collection<MethodDefinition> methods)
{
SweepCollectionWithCustomAttributes(methods);
if (sweepSymbols)
SweepDebugInfo(methods);
foreach (var method in methods)
{
if (method.HasGenericParameters)
SweepGenericParameters(method.GenericParameters);
SweepCustomAttributes(method.MethodReturnType);
SweepOverrides(method);
if (!method.HasMetadataParameters())
continue;
bool sweepNames = CanSweepNamesForMember(method, MetadataTrimming.ParameterName);
#pragma warning disable RS0030 // MethodReference.Parameters is banned. It makes sense to use when directly working with the Cecil type system though.
foreach (var parameter in method.Parameters)
{
if (sweepNames)
parameter.Name = null;
SweepCustomAttributes(parameter);
}
#pragma warning restore RS0030
}
}
void SweepOverrides(MethodDefinition method)
{
for (int i = 0; i < method.Overrides.Count;)
{
// We can't rely on the context resolution cache anymore, since it may remember methods which are already removed
// So call the direct Resolve here and avoid the cache.
// We want to remove a method from the list of Overrides if:
// Resolve() is null
// This can happen for a couple of reasons, but it indicates the method isn't in the final assembly.
// Resolve also may return a removed value if method.Overrides[i] is a MethodDefinition. In this case, Resolve short circuits and returns `this`.
// OR
// ov.DeclaringType is null
// ov.DeclaringType may be null if Resolve short circuited and returned a removed method. In this case, we want to remove the override.
// OR
// ov is in a `link` scope and is unmarked
// ShouldRemove returns true if the method is unmarked, but we also We need to make sure the override is in a link scope.
// Only things in a link scope are marked, so ShouldRemove is only valid for items in a `link` scope.
// OR
// ov is an interface method and the interface is not implemented by the type
#pragma warning disable RS0030 // Cecil's Resolve is banned - it's necessary when the metadata graph isn't stable
if (method.Overrides[i].Resolve() is not MethodDefinition ov
|| ov.DeclaringType is null
|| (IsLinkScope(ov.DeclaringType.Scope) && ShouldRemove(ov))
|| (ov.DeclaringType.IsInterface && !MarkStep.IsInterfaceImplementationMarkedRecursively(method.DeclaringType, ov.DeclaringType, Context)))
method.Overrides.RemoveAt(i);
else
i++;
#pragma warning restore RS0030
}
}
/// <summary>
/// Returns true if the assembly of the <paramref name="scope"></paramref> is set to link
/// </summary>
private bool IsLinkScope(IMetadataScope scope)
{
AssemblyDefinition? assembly = Context.Resolve(scope);
return assembly != null && Annotations.GetAction(assembly) == AssemblyAction.Link;
}
void SweepDebugInfo(Collection<MethodDefinition> methods)
{
List<ScopeDebugInformation>? sweptScopes = null;
foreach (var m in methods)
{
if (m.DebugInformation == null)
continue;
var scope = m.DebugInformation.Scope;
if (scope == null)
continue;
if (sweptScopes == null)
{
sweptScopes = new List<ScopeDebugInformation>();
}
else if (sweptScopes.Contains(scope))
{
continue;
}
sweptScopes.Add(scope);
if (scope.HasConstants)
{
var constants = scope.Constants;
for (int i = 0; i < constants.Count; ++i)
{
if (!Annotations.IsMarked(constants[i].ConstantType))
constants.RemoveAt(i--);
}
}
var import = scope.Import;
while (import != null)
{
if (import.HasTargets)
{
var targets = import.Targets;
for (int i = 0; i < targets.Count; ++i)
{
var ttype = targets[i].Type;
if (ttype != null && !Annotations.IsMarked(ttype))
targets.RemoveAt(i--);
// TODO: Clear also AssemblyReference and Namespace when not marked
}
}
import = import.Parent;
}
}
}
protected void SweepCollectionWithCustomAttributes<T>(IList<T> list) where T : ICustomAttributeProvider
{
for (int i = 0; i < list.Count; i++)
if (ShouldRemove(list[i]))
{
ElementRemoved(list[i]);
list.RemoveAt(i--);
}
else
{
SweepCustomAttributes(list[i]);
}
}
protected bool SweepCollectionMetadata<T>(IList<T> list) where T : IMetadataTokenProvider
{
bool removed = false;
for (int i = 0; i < list.Count; i++)
{
if (ShouldRemove(list[i]))
{
ElementRemoved(list[i]);
list.RemoveAt(i--);
removed = true;
}
}
return removed;
}
protected virtual bool ShouldRemove<T>(T element) where T : IMetadataTokenProvider
{
return !Annotations.IsMarked(element);
}
protected virtual void ElementRemoved(IMetadataTokenProvider element)
{
}
protected virtual void InterfaceRemoved(TypeDefinition type, InterfaceImplementation iface)
{
}
protected virtual void CustomAttributeUsageRemoved(ICustomAttributeProvider provider, CustomAttribute attribute)
{
}
bool SweepAssemblyReferences(AssemblyDefinition assembly)
{
//
// We used to run over list returned by GetTypeReferences but
// that returns typeref(s) of original assembly and we don't track
// which types are needed for which assembly which left us
// with dangling assembly references
//
assembly.MainModule.AssemblyReferences.Clear();
var arc = new AssemblyReferencesCorrector(assembly, walkSymbols: Context.LinkSymbols);
arc.Process();
return arc.ChangedAnyScopes;
}
sealed class AssemblyReferencesCorrector : TypeReferenceWalker
{
readonly DefaultMetadataImporter importer;
public bool ChangedAnyScopes { get; private set; }
public AssemblyReferencesCorrector(AssemblyDefinition assembly, bool walkSymbols) : base(assembly, walkSymbols)
{
this.importer = new DefaultMetadataImporter(assembly.MainModule);
ChangedAnyScopes = false;
}
protected override void ProcessTypeReference(TypeReference type)
{
//
// Resolve to type definition to remove any type forwarding imports
//
// Workaround for https://github.com/dotnet/linker/issues/2260
// Context has a cache which stores ref->def mapping. This code runs during sweeping
// which can remove the type-def from its assembly, effectively making the ref unresolvable.
// But the cache doesn't know that, it would still "resolve" the type-ref to now defunct type-def.
// For this reason we can't use the context resolution here, and must force Cecil to perform
// real type resolution again (since it can fail, and that's OK).
#pragma warning disable RS0030 // Cecil's Resolve is banned -- it's necessary when the metadata graph isn't stable
TypeDefinition td = type.Resolve();
#pragma warning restore RS0030
if (td == null)
{
//
// This can happen when not all assembly refences were provided and we
// run in `--skip-unresolved` mode. We cannot fully sweep and keep the
// original assembly reference
//
var anr = (AssemblyNameReference)type.Scope;
type.Scope = importer.ImportReference(anr);
return;
}
var tr = assembly.MainModule.ImportReference(td);
if (type.Scope == tr.Scope)
return;
type.Scope = tr.Scope;
ChangedAnyScopes = true;
}
protected override void ProcessExportedType(ExportedType exportedType)
{
#pragma warning disable RS0030 // Cecil's Resolve is banned -- it's necessary when the metadata graph is unstable
TypeDefinition? td = exportedType.Resolve();
#pragma warning restore RS0030
if (td == null)
{
// Forwarded type cannot be resolved but it was marked
// ILLink is running in --skip-unresolved true mode
var anr = (AssemblyNameReference)exportedType.Scope;
exportedType.Scope = importer.ImportReference(anr);
return;
}
var tr = assembly.MainModule.ImportReference(td);
if (exportedType.Scope == tr.Scope)
return;
exportedType.Scope = tr.Scope;
ChangedAnyScopes = true;
}
}
}
}
|