Skip to content

Commit

Permalink
Copy action behavior (#1941)
Browse files Browse the repository at this point in the history
* Stop rewriting scopes for copy assemblies with removed references

* Keep exported types in copy assemblies
Mark dynamically accessed forwarders
Add tests

* Transitive forwarders are linked

* Keep copyused behavior

* Tests

* Mark type if it's exported

* PR feedback

* Transitively mark forwarders when a facade is dynamically accessed and has copy action

* Transitively mark forwarders

* Fix formatting

* Update MarkExportedType

* Keep mscorlib assert

* Mark forwarders when there's a type ref to an exported type in a copy assembly

* Update DependencyKind

* Keep forwarders when member ref is resolved from a copy assembly

* Add more tests, mark forwarded CAs

* Mark forwarded interfaces

* Simplify logic for typerefs

* Clean

* Keep ProcessInternalsVisibleAttributes in process while-loop

* Fix whitespace formatting

* Remove unused param

* Feedback

* Whitespace formatting

* Remove unnecessary assembly

* Add IL2104 warning

* Remove unnecessary marking

* Update comment

* Remove ExportedTypeExtensions

* Remove formatDiagnostics and revert cecil subm change

* Remove warning

* Comment out check for removed warning

* Update more of existing calls

* Reproduce attribute issue

* Update scopes before sweeping forwarders

* Remove my enum from compilation

* Fix formatting

* Keep same behavior for scopes in copyused assemblies

Co-authored-by: Marek Safar <[email protected]>
Co-authored-by: Sven Boemer <[email protected]>
  • Loading branch information
3 people authored Apr 6, 2021
1 parent 388fef0 commit 2674d7d
Show file tree
Hide file tree
Showing 48 changed files with 848 additions and 157 deletions.
6 changes: 4 additions & 2 deletions src/linker/Linker.Dataflow/ReflectionMethodBodyScanner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -966,14 +966,15 @@ public override bool HandleCall (MethodBody callingMethodBody, MethodReference c
}
foreach (var typeNameValue in methodParams[0].UniqueValues ()) {
if (typeNameValue is KnownStringValue knownStringValue) {
TypeReference foundTypeRef = _context.TypeNameResolver.ResolveTypeName (knownStringValue.Contents);
TypeReference foundTypeRef = _context.TypeNameResolver.ResolveTypeName (knownStringValue.Contents, out AssemblyDefinition typeAssembly);
TypeDefinition foundType = foundTypeRef?.ResolveToMainTypeDefinition ();
if (foundType == null) {
// Intentionally ignore - it's not wrong for code to call Type.GetType on non-existing name, the code might expect null/exception back.
reflectionContext.RecordHandledPattern ();
} else {
reflectionContext.RecordRecognizedPattern (foundType, () => _markStep.MarkTypeVisibleToReflection (foundTypeRef, new DependencyInfo (DependencyKind.AccessedViaReflection, callingMethodDefinition), callingMethodDefinition));
methodReturnValue = MergePointValue.MergeValues (methodReturnValue, new SystemTypeValue (foundType));
_context.MarkingHelpers.MarkMatchingExportedType (foundType, typeAssembly, new DependencyInfo (DependencyKind.AccessedViaReflection, foundType));
}
} else if (typeNameValue == NullValue.Instance) {
reflectionContext.RecordHandledPattern ();
Expand Down Expand Up @@ -1977,14 +1978,15 @@ void RequireDynamicallyAccessedMembers (ref ReflectionPatternContext reflectionC
} else if (uniqueValue is SystemTypeValue systemTypeValue) {
MarkTypeForDynamicallyAccessedMembers (ref reflectionContext, systemTypeValue.TypeRepresented, requiredMemberTypes);
} else if (uniqueValue is KnownStringValue knownStringValue) {
TypeReference typeRef = _context.TypeNameResolver.ResolveTypeName (knownStringValue.Contents);
TypeReference typeRef = _context.TypeNameResolver.ResolveTypeName (knownStringValue.Contents, out AssemblyDefinition typeAssembly);
TypeDefinition foundType = typeRef?.ResolveToMainTypeDefinition ();
if (foundType == null) {
// Intentionally ignore - it's not wrong for code to call Type.GetType on non-existing name, the code might expect null/exception back.
reflectionContext.RecordHandledPattern ();
} else {
MarkType (ref reflectionContext, typeRef);
MarkTypeForDynamicallyAccessedMembers (ref reflectionContext, foundType, requiredMemberTypes);
_context.MarkingHelpers.MarkMatchingExportedType (foundType, typeAssembly, new DependencyInfo (DependencyKind.DynamicallyAccessedMember, foundType));
}
} else if (uniqueValue == NullValue.Instance) {
// Ignore - probably unreachable path as it would fail at runtime anyway.
Expand Down
3 changes: 3 additions & 0 deletions src/linker/Linker.Steps/DescriptorMarker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ protected override void ProcessAssembly (AssemblyDefinition assembly, XPathNavig
if (GetTypePreserve (nav) == TypePreserve.All) {
foreach (var type in assembly.MainModule.Types)
MarkAndPreserveAll (type);

foreach (var exportedType in assembly.MainModule.ExportedTypes)
_context.MarkingHelpers.MarkExportedType (exportedType, assembly.MainModule, new DependencyInfo (DependencyKind.XmlDescriptor, assembly.MainModule));
} else {
ProcessTypes (assembly, nav, warnOnUnresolvedTypes);
ProcessNamespaces (assembly, nav);
Expand Down
4 changes: 2 additions & 2 deletions src/linker/Linker.Steps/LinkAttributesParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ CustomAttributeArgument[] ReadCustomAttributeArguments (XPathNodeIterator iterat
if (!typeref.IsTypeOf ("System", "Type"))
goto default;

TypeReference type = _context.TypeNameResolver.ResolveTypeName (svalue);
TypeReference type = _context.TypeNameResolver.ResolveTypeName (svalue, out _);
if (type == null) {
_context.LogError ($"Could not resolve custom attribute type value '{svalue}'", 1044, _xmlDocumentLocation);
return null;
Expand All @@ -284,7 +284,7 @@ TypeReference ResolveArgumentType (XPathNodeIterator iterator)
if (string.IsNullOrEmpty (typeName))
typeName = "System.String";

TypeReference typeref = _context.TypeNameResolver.ResolveTypeName (typeName);
TypeReference typeref = _context.TypeNameResolver.ResolveTypeName (typeName, out _);
if (typeref == null) {
_context.LogError ($"The type '{typeName}' used with attribute value '{iterator.Current.Value}' could not be found", 1041, _xmlDocumentLocation);
return null;
Expand Down
81 changes: 35 additions & 46 deletions src/linker/Linker.Steps/MarkStep.cs
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ public MarkStep ()
}

public AnnotationStore Annotations => _context.Annotations;
public MarkingHelpers MarkingHelpers => _context.MarkingHelpers;
public Tracer Tracer => _context.Tracer;

public virtual void Process (LinkContext context)
Expand Down Expand Up @@ -365,40 +366,12 @@ private void MarkEntireTypeInternal (TypeDefinition type, bool includeBaseTypes,

void Process ()
{
while (ProcessPrimaryQueue () || ProcessMarkedPending () || ProcessLazyAttributes () || ProcessLateMarkedAttributes () || MarkFullyPreservedAssemblies () || ProcessInternalsVisibleAttributes ()) {

// deal with [TypeForwardedTo] pseudo-attributes

// This marks all exported types that resolve to a marked type. Note that it
// may mark unused exported types that happen to resolve to a type which was
// marked from a different reference. This seems incorrect.
// Note also that we may still remove type forwarder assemblies with marked exports in SweepStep,
// if they don't contain other used code.
// https://github.com/mono/linker/issues/1740
foreach (AssemblyDefinition assembly in _context.GetAssemblies ()) {
if (!assembly.MainModule.HasExportedTypes)
continue;

foreach (var exported in assembly.MainModule.ExportedTypes) {
bool isForwarder = exported.IsForwarder;
var declaringType = exported.DeclaringType;
while (!isForwarder && (declaringType != null)) {
isForwarder = declaringType.IsForwarder;
declaringType = declaringType.DeclaringType;
}

if (!isForwarder)
continue;
TypeDefinition type = exported.Resolve ();
if (type == null)
continue;
if (!Annotations.IsMarked (type))
continue;
var di = new DependencyInfo (DependencyKind.ExportedType, type);
_context.MarkingHelpers.MarkExportedType (exported, assembly.MainModule, di);
}
}
}
while (ProcessPrimaryQueue () ||
ProcessMarkedPending () ||
ProcessLazyAttributes () ||
ProcessLateMarkedAttributes () ||
MarkFullyPreservedAssemblies () ||
ProcessInternalsVisibleAttributes ()) ;

ProcessPendingTypeChecks ();
}
Expand Down Expand Up @@ -858,6 +831,8 @@ void MarkDynamicDependency (DynamicDependency dynamicDependency, IMemberDefiniti
_context.LogWarning ($"Unresolved type '{typeName}' in DynamicDependencyAttribute", 2036, context);
return;
}

MarkingHelpers.MarkMatchingExportedType (type, assembly, new DependencyInfo (DependencyKind.DynamicDependency, type));
} else if (dynamicDependency.Type is TypeReference typeReference) {
type = typeReference.Resolve ();
if (type == null) {
Expand Down Expand Up @@ -947,14 +922,20 @@ protected virtual void MarkUserDependency (IMemberDefinition context, CustomAttr
assembly = null;
}

TypeDefinition td;
TypeDefinition td = null;
if (args.Count >= 2 && args[1].Value is string typeName) {
td = _context.TypeNameResolver.ResolveTypeName (assembly ?? (context as MemberReference).Module.Assembly, typeName)?.Resolve ();
AssemblyDefinition assemblyDef = assembly ?? (context as MemberReference).Module.Assembly;
TypeReference tr = _context.TypeNameResolver.ResolveTypeName (assemblyDef, typeName);
if (tr != null)
td = tr.Resolve ();

if (td == null) {
_context.LogWarning (
$"Could not resolve dependency type '{typeName}' specified in a `PreserveDependency` attribute", 2004, context);
return;
}

MarkingHelpers.MarkMatchingExportedType (td, assemblyDef, new DependencyInfo (DependencyKind.PreservedDependency, ca));
} else {
td = context.DeclaringType.Resolve ();
}
Expand Down Expand Up @@ -1357,15 +1338,20 @@ void MarkEntireAssembly (AssemblyDefinition assembly)
{
Debug.Assert (Annotations.IsProcessed (assembly));

ModuleDefinition module = assembly.MainModule;
MarkCustomAttributes (assembly, new DependencyInfo (DependencyKind.AssemblyOrModuleAttribute, assembly), null);
MarkCustomAttributes (assembly.MainModule, new DependencyInfo (DependencyKind.AssemblyOrModuleAttribute, assembly.MainModule), null);
MarkCustomAttributes (module, new DependencyInfo (DependencyKind.AssemblyOrModuleAttribute, module), null);

if (assembly.MainModule.HasExportedTypes) {
// TODO: This needs more work accross all steps
foreach (TypeDefinition type in module.Types)
MarkEntireType (type, includeBaseTypes: false, includeInterfaceTypes: false, new DependencyInfo (DependencyKind.TypeInAssembly, assembly), null);

foreach (ExportedType exportedType in module.ExportedTypes) {
MarkingHelpers.MarkExportedType (exportedType, module, new DependencyInfo (DependencyKind.ExportedType, assembly));
MarkingHelpers.MarkForwardedScope (new TypeReference (exportedType.Namespace, exportedType.Name, module, exportedType.Scope));
}

foreach (TypeDefinition type in assembly.MainModule.Types)
MarkEntireType (type, includeBaseTypes: false, includeInterfaceTypes: false, new DependencyInfo (DependencyKind.TypeInAssembly, assembly), null);
foreach (TypeReference typeReference in module.GetTypeReferences ())
MarkingHelpers.MarkForwardedScope (typeReference);
}

void ProcessModuleType (AssemblyDefinition assembly)
Expand Down Expand Up @@ -1870,21 +1856,24 @@ protected virtual void MarkTypeConverterLikeDependency (CustomAttribute attribut
if (args.Count < 1)
return;

TypeDefinition tdef = null;
TypeDefinition typeDefinition = null;
switch (attribute.ConstructorArguments[0].Value) {
case string s:
tdef = _context.TypeNameResolver.ResolveTypeName (s)?.Resolve ();
typeDefinition = _context.TypeNameResolver.ResolveTypeName (s, out AssemblyDefinition assemblyDefinition)?.Resolve ();
if (typeDefinition != null)
MarkingHelpers.MarkMatchingExportedType (typeDefinition, assemblyDefinition, new DependencyInfo (DependencyKind.CustomAttribute, provider));

break;
case TypeReference type:
tdef = type.Resolve ();
typeDefinition = type.Resolve ();
break;
}

if (tdef == null)
if (typeDefinition == null)
return;

Tracer.AddDirectDependency (attribute, new DependencyInfo (DependencyKind.CustomAttribute, provider), marked: false);
MarkMethodsIf (tdef.Methods, predicate, new DependencyInfo (DependencyKind.ReferencedBySpecialAttribute, attribute), sourceLocationMember);
MarkMethodsIf (typeDefinition.Methods, predicate, new DependencyInfo (DependencyKind.ReferencedBySpecialAttribute, attribute), sourceLocationMember);
}

void MarkTypeWithDebuggerDisplayAttribute (TypeDefinition type, CustomAttribute attribute)
Expand Down
26 changes: 4 additions & 22 deletions src/linker/Linker.Steps/OutputStep.cs
Original file line number Diff line number Diff line change
Expand Up @@ -271,42 +271,24 @@ FileInfo GetOriginalAssemblyFileInfo (AssemblyDefinition assembly)

protected virtual void CopyAssembly (AssemblyDefinition assembly, string directory)
{
// Special case. When an assembly has embedded pdbs, link symbols is not enabled, and the assembly's action is copy,
// we want to match the behavior of assemblies with the other symbol types and end up with an assembly that does not have symbols.
// In order to do that, we can't simply copy files. We need to write the assembly without symbols
if (assembly.MainModule.HasSymbols && !Context.LinkSymbols && assembly.MainModule.SymbolReader is EmbeddedPortablePdbReader) {
WriteAssembly (assembly, directory, new WriterParameters ());
return;
}

FileInfo fi = GetOriginalAssemblyFileInfo (assembly);
string target = Path.GetFullPath (Path.Combine (directory, fi.Name));
string source = fi.FullName;

if (source == target)
return;

CopyFileAndRemoveReadOnly (source, target);

File.Copy (source, target, true);
if (!Context.LinkSymbols)
return;

var mdb = source + ".mdb";
if (File.Exists (mdb))
CopyFileAndRemoveReadOnly (mdb, target + ".mdb");
File.Copy (mdb, target + ".mdb", true);

var pdb = Path.ChangeExtension (source, "pdb");
if (File.Exists (pdb))
CopyFileAndRemoveReadOnly (pdb, Path.ChangeExtension (target, "pdb"));
}

static void CopyFileAndRemoveReadOnly (string src, string dest)
{
File.Copy (src, dest, true);

System.IO.FileAttributes attrs = File.GetAttributes (dest);

if ((attrs & System.IO.FileAttributes.ReadOnly) == System.IO.FileAttributes.ReadOnly)
File.SetAttributes (dest, attrs & ~System.IO.FileAttributes.ReadOnly);
File.Copy (pdb, Path.ChangeExtension (target, "pdb"), true);
}

protected virtual string GetAssemblyFileName (AssemblyDefinition assembly, string directory)
Expand Down
54 changes: 25 additions & 29 deletions src/linker/Linker.Steps/SweepStep.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,16 @@ protected override void Process ()
foreach (var assembly in assemblies)
UpdateAssemblyReferencesToRemovedAssemblies (assembly);

// Update scopes before removing any type forwarder.
foreach (var assembly in assemblies) {
switch (Annotations.GetAction (assembly)) {
case AssemblyAction.Link:
case AssemblyAction.Save:
SweepAssemblyReferences (assembly);
break;
}
}

foreach (var assembly in assemblies)
ProcessAssemblyAction (assembly);

Expand Down Expand Up @@ -97,13 +107,13 @@ void UpdateAssemblyReferencesToRemovedAssemblies (AssemblyDefinition assembly)
{
var action = Annotations.GetAction (assembly);
switch (action) {
case AssemblyAction.Skip:
case AssemblyAction.Copy:
case AssemblyAction.Delete:
case AssemblyAction.Link:
case AssemblyAction.Save:
case AssemblyAction.Skip:
return;

case AssemblyAction.Copy:
case AssemblyAction.CopyUsed:
case AssemblyAction.AddBypassNGen:
case AssemblyAction.AddBypassNGenUsed:
Expand All @@ -121,7 +131,6 @@ void UpdateAssemblyReferencesToRemovedAssemblies (AssemblyDefinition assembly)

switch (action) {
case AssemblyAction.CopyUsed:
case AssemblyAction.Copy:
//
// 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.
Expand Down Expand Up @@ -172,22 +181,14 @@ protected void ProcessAssemblyAction (AssemblyDefinition assembly)
break;

case AssemblyAction.CopyUsed:
Annotations.SetAction (assembly, AssemblyAction.Copy);
goto case AssemblyAction.Copy;
AssemblyAction assemblyAction = AssemblyAction.Copy;
if (!Context.KeepTypeForwarderOnlyAssemblies && SweepTypeForwarders (assembly))
assemblyAction = AssemblyAction.Save;

case AssemblyAction.Copy:
//
// Facade assemblies can have unused forwarders pointing to
// removed type (when facades are kept)
//
// main.exe -> facade.dll -> lib.dll
// link | copy | link
//
// when main.exe has unused reference to type in lib.dll
//
if (SweepTypeForwarders (assembly))
Annotations.SetAction (assembly, AssemblyAction.Save);
Annotations.SetAction (assembly, assemblyAction);
break;

case AssemblyAction.Copy:
break;

case AssemblyAction.Link:
Expand All @@ -196,12 +197,6 @@ protected void ProcessAssemblyAction (AssemblyDefinition assembly)

case AssemblyAction.Save:
SweepTypeForwarders (assembly);

//
// Save means we need to rewrite the assembly due to removed assembly
// references
//
SweepAssemblyReferences (assembly);
break;
}
}
Expand All @@ -210,11 +205,13 @@ 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;
}

Expand All @@ -230,24 +227,23 @@ protected virtual void SweepAssembly (AssemblyDefinition assembly)
main.Types.Add (type);

SweepResources (assembly);
SweepCustomAttributes (assembly);
updateScopes |= SweepCustomAttributes (assembly);

foreach (var module in assembly.Modules)
SweepCustomAttributes (module);
updateScopes |= SweepCustomAttributes (module);

//
// MainModule module references are used by pinvoke
//
if (main.HasModuleReferences)
SweepCollectionMetadata (main.ModuleReferences);
updateScopes |= SweepCollectionMetadata (main.ModuleReferences);

if (main.EntryPoint != null && !Annotations.IsMarked (main.EntryPoint)) {
main.EntryPoint = null;
}

SweepTypeForwarders (assembly);

SweepAssemblyReferences (assembly);
if (SweepTypeForwarders (assembly) || updateScopes)
SweepAssemblyReferences (assembly);
}

static void SweepAssemblyReferences (AssemblyDefinition assembly)
Expand Down
Loading

0 comments on commit 2674d7d

Please sign in to comment.