Skip to content

Commit

Permalink
Make default rooting behavior consistent (dotnet#3124)
Browse files Browse the repository at this point in the history
Now specifying an assembly as a root without any other qualifiers will default to rooting everything.

Commit migrated from dotnet@e775974
  • Loading branch information
sbomer authored and dotnet-bot committed Dec 20, 2022
1 parent 64988c9 commit 63e3eb5
Show file tree
Hide file tree
Showing 11 changed files with 3,199 additions and 0 deletions.
511 changes: 511 additions & 0 deletions src/tools/illink/src/ILLink.Tasks/LinkTask.cs

Large diffs are not rendered by default.

352 changes: 352 additions & 0 deletions src/tools/illink/src/ILLink.Tasks/build/Microsoft.NET.ILLink.targets

Large diffs are not rendered by default.

202 changes: 202 additions & 0 deletions src/tools/illink/src/linker/Linker.Steps/RootAssemblyInputStep.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
// 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.

using System.IO;
using ILLink.Shared;
using Mono.Cecil;

namespace Mono.Linker.Steps
{
public class RootAssemblyInput : BaseStep
{
readonly string fileName;
readonly AssemblyRootMode rootMode;

public RootAssemblyInput (string fileName, AssemblyRootMode rootMode)
{
this.fileName = fileName;
this.rootMode = rootMode;
}

protected override void Process ()
{
AssemblyDefinition? assembly = LoadAssemblyFile ();
if (assembly == null)
return;

var di = new DependencyInfo (DependencyKind.RootAssembly, assembly);
var origin = new MessageOrigin (assembly);

AssemblyAction action = Context.Annotations.GetAction (assembly);
switch (action) {
case AssemblyAction.Copy:
Annotations.Mark (assembly.MainModule, di, origin);
// Mark Step will take care of marking whole assembly
return;
case AssemblyAction.CopyUsed:
case AssemblyAction.Link:
break;
default:
Context.LogError (null, DiagnosticId.RootAssemblyCannotUseAction, assembly.Name.ToString (), action.ToString ());
return;
}

switch (rootMode) {
case AssemblyRootMode.EntryPoint:
var ep = assembly.MainModule.EntryPoint;
if (ep == null) {
Context.LogError (null, DiagnosticId.RootAssemblyDoesNotHaveEntryPoint, assembly.Name.ToString ());
return;
}

Annotations.Mark (ep.DeclaringType, di, origin);
Annotations.AddPreservedMethod (ep.DeclaringType, ep);
break;
case AssemblyRootMode.VisibleMembers:
var preserve_visible = TypePreserveMembers.Visible;
if (MarkInternalsVisibleTo (assembly))
preserve_visible |= TypePreserveMembers.Internal;

MarkAndPreserve (assembly, preserve_visible);
break;

case AssemblyRootMode.Library:
var preserve_library = TypePreserveMembers.Visible | TypePreserveMembers.Library;
if (MarkInternalsVisibleTo (assembly))
preserve_library |= TypePreserveMembers.Internal;

MarkAndPreserve (assembly, preserve_library);

// Assembly root mode wins over any enabled optimization which
// could conflict with library rooting behaviour
Context.Optimizations.Disable (
CodeOptimizations.Sealer |
CodeOptimizations.UnusedTypeChecks |
CodeOptimizations.UnreachableBodies |
CodeOptimizations.UnusedInterfaces |
CodeOptimizations.RemoveDescriptors |
CodeOptimizations.RemoveLinkAttributes |
CodeOptimizations.RemoveSubstitutions |
CodeOptimizations.RemoveDynamicDependencyAttribute |
CodeOptimizations.OptimizeTypeHierarchyAnnotations, assembly.Name.Name);

// Enable EventSource special handling
Context.DisableEventSourceSpecialHandling = false;

// No metadata trimming
Context.MetadataTrimming = MetadataTrimming.None;
break;
case AssemblyRootMode.AllMembers:
Context.Annotations.SetAction (assembly, AssemblyAction.Copy);
return;
}
}

AssemblyDefinition? LoadAssemblyFile ()
{
AssemblyDefinition? assembly;

if (File.Exists (fileName)) {
assembly = Context.Resolver.GetAssembly (fileName);
AssemblyDefinition? loaded = Context.GetLoadedAssembly (assembly.Name.Name);

// The same assembly could be already loaded if there are multiple inputs pointing to same file
if (loaded != null)
return loaded;

Context.Resolver.CacheAssembly (assembly);
return assembly;
}

//
// Quirks mode for netcore to support passing ambiguous assembly name
//
assembly = Context.TryResolve (fileName);
if (assembly == null)
Context.LogError (null, DiagnosticId.RootAssemblyCouldNotBeFound, fileName);

return assembly;
}

void MarkAndPreserve (AssemblyDefinition assembly, TypePreserveMembers preserve)
{
var module = assembly.MainModule;
if (module.HasExportedTypes)
foreach (var type in module.ExportedTypes)
MarkAndPreserve (assembly, type, preserve);

foreach (var type in module.Types)
MarkAndPreserve (type, preserve);
}

void MarkAndPreserve (TypeDefinition type, TypePreserveMembers preserve)
{
TypePreserveMembers preserve_anything = preserve;
if ((preserve & TypePreserveMembers.Visible) != 0 && !IsTypeVisible (type))
preserve_anything &= ~TypePreserveMembers.Visible;

if ((preserve & TypePreserveMembers.Internal) != 0 && IsTypePrivate (type))
preserve_anything &= ~TypePreserveMembers.Internal;

// Keep all interfaces and interface members in library mode
if ((preserve & TypePreserveMembers.Library) != 0 && type.IsInterface) {
Annotations.Mark (type, new DependencyInfo (DependencyKind.RootAssembly, type.Module.Assembly), new MessageOrigin (type.Module.Assembly));
Annotations.SetPreserve (type, TypePreserve.All);
}

switch (preserve_anything) {
case 0:
return;
case TypePreserveMembers.Library:
//
// In library mode private type can have members kept for serialization if
// the type is referenced
//
preserve = preserve_anything;
Annotations.SetMembersPreserve (type, preserve);
break;
default:
Annotations.Mark (type, new DependencyInfo (DependencyKind.RootAssembly, type.Module.Assembly), new MessageOrigin (type.Module.Assembly));
Annotations.SetMembersPreserve (type, preserve);
break;
}

if (!type.HasNestedTypes)
return;

foreach (TypeDefinition nested in type.NestedTypes)
MarkAndPreserve (nested, preserve);
}

void MarkAndPreserve (AssemblyDefinition assembly, ExportedType type, TypePreserveMembers preserve)
{
var di = new DependencyInfo (DependencyKind.RootAssembly, assembly);
var origin = new MessageOrigin (assembly);
Context.Annotations.Mark (type, di, origin);
Context.Annotations.Mark (assembly.MainModule, di, origin);
Annotations.SetMembersPreserve (type, preserve);
}

static bool IsTypeVisible (TypeDefinition type)
{
return type.IsPublic || type.IsNestedPublic || type.IsNestedFamily || type.IsNestedFamilyOrAssembly;
}

static bool IsTypePrivate (TypeDefinition type)
{
return type.IsNestedPrivate;
}

bool MarkInternalsVisibleTo (AssemblyDefinition assembly)
{
foreach (CustomAttribute attribute in assembly.CustomAttributes) {
if (attribute.Constructor.DeclaringType.IsTypeOf ("System.Runtime.CompilerServices", "InternalsVisibleToAttribute")) {
Context.Annotations.Mark (attribute, new DependencyInfo (DependencyKind.RootAssembly, assembly));
return true;
}
}

return false;
}
}
}
13 changes: 13 additions & 0 deletions src/tools/illink/src/linker/Linker/AssemblyRootMode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// 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.

namespace Mono.Linker
{
public enum AssemblyRootMode
{
AllMembers = 0,
EntryPoint,
VisibleMembers,
Library
}
}
Loading

0 comments on commit 63e3eb5

Please sign in to comment.