Skip to content

Commit

Permalink
Handle cyclic assembly references
Browse files Browse the repository at this point in the history
  • Loading branch information
sbomer committed Jun 9, 2021
1 parent e83a246 commit 10c3fe7
Showing 1 changed file with 68 additions and 15 deletions.
83 changes: 68 additions & 15 deletions tools/linker/CoreTypeMapStep.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

using System;
using System.Collections.Generic;
using System.Diagnostics;

using Mono.Cecil;
using Mono.Linker.Steps;
Expand All @@ -34,30 +35,82 @@ public class CoreTypeMapStep :

Profile Profile => new Profile (Configuration);

// Get the set of assemblies transitively referenced from the input assembly,
// along with the reversed references for each assembly that's part of the
// transitively referenced set.
Dictionary<AssemblyDefinition, HashSet<AssemblyDefinition>> GetReverseReferenceGraph (AssemblyDefinition start)
{
var references = new Dictionary<AssemblyDefinition, HashSet<AssemblyDefinition>> {
{ start, new HashSet<AssemblyDefinition> () }
};
var toProcess = new Queue<AssemblyDefinition> ();
toProcess.Enqueue (start);
while (toProcess.TryDequeue (out var assembly)) {
foreach (var reference in assembly.MainModule.AssemblyReferences) {
var resolvedReference = Configuration.Context.GetLoadedAssembly (reference.Name);
if (resolvedReference == null)
continue;

if (!references.TryGetValue (resolvedReference, out var referrers)) {
referrers = new HashSet<AssemblyDefinition> ();
references.Add (resolvedReference, referrers);
toProcess.Enqueue (resolvedReference);
}

referrers.Add (assembly);
}
}
return references;
}

Dictionary<AssemblyDefinition, bool> _transitivelyReferencesProduct = new Dictionary<AssemblyDefinition, bool> ();
bool TransitivelyReferencesProduct (AssemblyDefinition assembly)
bool TransitivelyReferencesProduct (AssemblyDefinition start)
{
if (_transitivelyReferencesProduct.TryGetValue (assembly, out bool result))
if (_transitivelyReferencesProduct.TryGetValue (start, out bool result))
return result;

if (Profile.IsProductAssembly (assembly)) {
_transitivelyReferencesProduct.Add (assembly, true);
return true;
}

foreach (var reference in assembly.MainModule.AssemblyReferences) {
var resolvedReference = Configuration.Context.GetLoadedAssembly (reference.Name);
if (resolvedReference == null)
// A depth-first search is insufficient because there are reference cycles, so we
// get the set of transitive references, and do a reverse BFS.
var references = GetReverseReferenceGraph (start);
var referencesProductToProcess = new Queue<AssemblyDefinition> ();

// We start the BFS from the product assembly or any references which are already known
// to reference the product assembly.
foreach (var reference in references.Keys) {
if (_transitivelyReferencesProduct.TryGetValue (reference, out bool referencesProduct)) {
if (referencesProduct)
referencesProductToProcess.Enqueue (reference);
continue;
}

if (Profile.IsProductAssembly (reference)) {
_transitivelyReferencesProduct.Add (reference, true);
referencesProductToProcess.Enqueue (reference);
}
}

if (TransitivelyReferencesProduct (resolvedReference)) {
_transitivelyReferencesProduct.Add (assembly, true);
return true;
// Scan the reverse references to find out which referencing assemblies
// are reachable from the product assembly (that is, transitively reference it).
while (referencesProductToProcess.TryDequeue (out var assembly)) {
foreach (var referrer in references[assembly]) {
if (_transitivelyReferencesProduct.TryGetValue (referrer, out bool referencesProduct)) {
Debug.Assert (referencesProduct);
// Any which were already determined to reference the product assembly
// don't need to be scanned again.
continue;
}

_transitivelyReferencesProduct.Add (referrer, true);
referencesProductToProcess.Enqueue (referrer);
}
}

_transitivelyReferencesProduct.Add (assembly, false);
return false;
// Any remaining references that we didn't discover during the search
// don't reference the product.
foreach (var reference in references.Keys)
_transitivelyReferencesProduct.TryAdd (reference, false);

return _transitivelyReferencesProduct[start];
}

protected override void TryProcessAssembly (AssemblyDefinition assembly)
Expand Down

0 comments on commit 10c3fe7

Please sign in to comment.