Skip to content

Commit

Permalink
Allow PathAssemblyResolver to use an assembly with different PublicKe…
Browse files Browse the repository at this point in the history
…yToken if reference is Retargetable (#40581)
  • Loading branch information
steveharter authored Aug 26, 2019
1 parent cadc61e commit fca6032
Show file tree
Hide file tree
Showing 7 changed files with 144 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ private RoAssembly LoadFromStreamCore(Stream peStream)
{
pkt = defNameData.PublicKey.ComputePublicKeyToken();
}
RoAssemblyName defName = new RoAssemblyName(defNameData.Name, defNameData.Version, defNameData.CultureName, pkt);
RoAssemblyName defName = new RoAssemblyName(defNameData.Name, defNameData.Version, defNameData.CultureName, pkt, defNameData.Flags);

RoAssembly winner = _loadedAssemblies.GetOrAdd(defName, candidate);
if (winner == candidate)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,10 @@ public override Assembly Resolve(MetadataLoadContext context, AssemblyName assem
candidateWithSamePkt = assemblyFromPath;
}
}
// If assemblyName does not specify a PublicKeyToken, then still consider those with a PublicKeyToken.
else if (candidateWithSamePkt == null && pktFromName.IsEmpty)
// If assemblyName does not specify a PublicKeyToken, or assemblyName is marked 'Retargetable',
// then still consider those with a PublicKeyToken and take the highest version available.
else if ((candidateWithSamePkt == null && pktFromName.IsEmpty) ||
((assemblyName.Flags & AssemblyNameFlags.Retargetable) != 0))
{
// Pick the highest version.
if (candidateIgnoringPkt == null || assemblyNameFromPath.Version > candidateIgnoringPkt.GetName().Version)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,13 @@ public static RoAssemblyName ToRoAssemblyName(this AssemblyReferenceHandle h, Me
string culture = a.Culture.GetStringOrNull(reader);
byte[] pkOrPkt = a.PublicKeyOrToken.GetBlobBytes(reader);
AssemblyFlags flags = a.Flags;
AssemblyNameFlags assemblyNameFlags = Helpers.ConvertAssemblyFlagsToAssemblyNameFlags(flags);
if ((flags & AssemblyFlags.PublicKey) != 0)
{
pkOrPkt = pkOrPkt.ComputePublicKeyToken();
}

return new RoAssemblyName(name, version, culture, pkOrPkt);
return new RoAssemblyName(name, version, culture, pkOrPkt, assemblyNameFlags);
}

public static CoreType ToCoreType(this PrimitiveTypeCode typeCode)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,18 @@ public static byte[] ComputePublicKeyToken(this byte[] pkt)
return an.GetPublicKeyToken();
}

public static AssemblyNameFlags ConvertAssemblyFlagsToAssemblyNameFlags(AssemblyFlags assemblyFlags)
{
AssemblyNameFlags assemblyNameFlags = AssemblyNameFlags.None;

if ((assemblyFlags & AssemblyFlags.Retargetable) != 0)
{
assemblyNameFlags |= AssemblyNameFlags.Retargetable;
}

return assemblyNameFlags;
}

//
// Note that for a top level type, the resulting ns is string.Empty, *not* null.
// This is a concession to the fact that MetadataReader's fast String equal methods
Expand Down Expand Up @@ -349,7 +361,7 @@ public static RoAssemblyName ToRoAssemblyName(this AssemblyName assemblyName)
// as the original is wide open to tampering by anyone.
byte[] pkt = assemblyName.GetPublicKeyToken().CloneArray();

return new RoAssemblyName(assemblyName.Name, assemblyName.Version, assemblyName.CultureName, pkt);
return new RoAssemblyName(assemblyName.Name, assemblyName.Version, assemblyName.CultureName, pkt, assemblyName.Flags);
}

public static byte[] ToUtf8(this string s) => Encoding.UTF8.GetBytes(s);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,14 @@ internal sealed class RoAssemblyName : IEquatable<RoAssemblyName>
public string CultureName { get; }
public byte[] PublicKeyToken;

// We don't need to store the flags. The only flag allowed in an ECMA-335 AssemblyReference is the "PublicKey" bit. Since
// RoAssemblyName always normalizes to the short form public key token, that bit would always be 0, and hence the flag enum as a whole
// would always be zero.
// We store the flags to support "Retargetable".
// The only flag allowed in an ECMA-335 AssemblyReference is the "PublicKey" bit. Since
// RoAssemblyName always normalizes to the short form public key token, that bit would always be 0.
public AssemblyNameFlags Flags { get; }

private static readonly Version s_Version0000 = new Version(0, 0, 0, 0);

public RoAssemblyName(string name, Version version, string cultureName, byte[] publicKeyToken)
public RoAssemblyName(string name, Version version, string cultureName, byte[] publicKeyToken, AssemblyNameFlags flags)
{
// We forcefully normalize the representation so that Equality is dependable and fast.
Debug.Assert(name != null);
Expand All @@ -40,6 +41,7 @@ public RoAssemblyName(string name, Version version, string cultureName, byte[] p
Version = version ?? s_Version0000;
CultureName = cultureName ?? string.Empty;
PublicKeyToken = publicKeyToken ?? Array.Empty<byte>();
Flags = flags;
}

public string FullName => ToAssemblyName().FullName;
Expand All @@ -57,6 +59,9 @@ public bool Equals(RoAssemblyName other)
return false;
if (!(((ReadOnlySpan<byte>)PublicKeyToken).SequenceEqual(other.PublicKeyToken)))
return false;

// Do not compare Flags; we do not want to treat AssemblyNames as not being equal due to Flags.

return true;
}

Expand All @@ -71,6 +76,7 @@ public AssemblyName ToAssemblyName()
Name = Name,
Version = Version,
CultureName = CultureName,
Flags = Flags,
};

// We must not hand out our own copy of the PKT to AssemblyName as AssemblyName is amazingly trusting and gives untrusted callers
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3011,5 +3011,69 @@ internal static class TestData
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
"AAAAAAAAAAAAAAAA"
);

//// Metadata version: v4.0.30319
//.assembly extern retargetable mscorlib
// {
// .publickeytoken = (7C EC 85 D7 BE A7 79 8E ) // |.....y.
// .ver 2:0:5:0
//}
//.assembly SimpleAssembly
// {
// .custom instance void [mscorlib]
// System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( 01 00 08 00 00 00 00 00 )
// .custom instance void [mscorlib]
// System.Runtime.CompilerServices.RuntimeCompatibilityAttribute::.ctor() = ( 01 00 01 00 54 02 16 57 72 61 70 4E 6F 6E 45 78 // ....T..WrapNonEx
// 63 65 70 74 69 6F 6E 54 68 72 6F 77 73 01 ) // ceptionThrows.
// // --- The following custom attribute is added automatically, do not uncomment -------
// // .custom instance void [mscorlib]System.Diagnostics.DebuggableAttribute::.ctor(valuetype [mscorlib]System.Diagnostics.DebuggableAttribute/DebuggingModes) = ( 01 00 07 01 00 00 00 00 )
// .hash algorithm 0x00008004
// .ver 0:0:0:0
//}
//.module SimpleAssembly.dll
//// MVID: {DCD1E0C4-4B2E-4E02-952F-DA6B0600F478}
//.imagebase 0x10000000
//.file alignment 0x00000200
//.stackreserve 0x00100000
//.subsystem 0x0003 // WINDOWS_CUI
//.corflags 0x00000001 // ILONLY
//// Image base: 0x03550000
public static readonly string s_RetargetableAssemblySimpleName = "SimpleAssembly";
public static readonly byte[] s_RetargetableImage = Convert.FromBase64String(
"TVqQAAMAAAAEAAAA//8AALgAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAA4fug4AtAnNIbgBTM0hVGhpcyBwcm9ncmFt" +
"IGNhbm5vdCBiZSBydW4gaW4gRE9TIG1vZGUuDQ0KJAAAAAAAAABQRQAATAEDAP1PYF0AAAAAAAAAAOAAIiALATAAAAQAAAAGAAAAAAAAfiMAAAAgAAAAQAAA" +
"AAAAEAAgAAAAAgAABAAAAAAAAAAEAAAAAAAAAACAAAAAAgAAAAAAAAMAQIUAABAAABAAAAAAEAAAEAAAAAAAABAAAAAAAAAAAAAAACwjAABPAAAAAEAAAMAC" +
"AAAAAAAAAAAAAAAAAAAAAAAAAGAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAACAAAAAAAAAAAAAAA" +
"CCAAAEgAAAAAAAAAAAAAAC50ZXh0AAAAhAMAAAAgAAAABAAAAAIAAAAAAAAAAAAAAAAAACAAAGAucnNyYwAAAMACAAAAQAAAAAQAAAAGAAAAAAAAAAAAAAAA" +
"AABAAABALnJlbG9jAAAMAAAAAGAAAAACAAAACgAAAAAAAAAAAAAAAAAAQAAAQgAAAAAAAAAAAAAAAAAAAABgIwAAAAAAAEgAAAACAAUAXCAAANACAAABAAAA" +
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACICKAQAAAoAKgAAAEJTSkIBAAEAAAAAAAwAAAB2NC4wLjMwMzE5" +
"AAAAAAUAbAAAAPAAAAAjfgAAXAEAABABAAAjU3RyaW5ncwAAAABsAgAABAAAACNVUwBwAgAAEAAAACNHVUlEAAAAgAIAAFAAAAAjQmxvYgAAAAAAAAACAAAB" +
"VxQAAAkAAAAA+gEzABYAAAEAAAAGAAAAAgAAAAEAAAABAAAABAAAAAMAAAABAAAAAQAAAAAAdAABAAAAAAAGADAApwAGAFAApwAGABwAlAAPAMcAAAAGAPEA" +
"hwAGAPgA1gAAAAAAAQAAAAAAAQABAAEAEADpABMAFQABAAEAFgBuABkAUCAAAAAAhhiOAAYAAQAJAI4AAQARAI4ABgAZAI4ACgApAI4ABgAuAAsAHQAuABMA" +
"JgAuABsARQAEgAAAAAAAAAAAAAAAAAAAAAABAQAAAgAAAAUAAAAAAQAAEAAKAAAAAAAAAAA8TW9kdWxlPgBtc2NvcmxpYgBSZWxvY2F0ZQBEZWJ1Z2dhYmxl" +
"QXR0cmlidXRlAENvbXBpbGF0aW9uUmVsYXhhdGlvbnNBdHRyaWJ1dGUAUnVudGltZUNvbXBhdGliaWxpdHlBdHRyaWJ1dGUATXlPYmoAU2ltcGxlQXNzZW1i" +
"bHkuZGxsAFN5c3RlbQAuY3RvcgBTeXN0ZW0uRGlhZ25vc3RpY3MAU3lzdGVtLlJ1bnRpbWUuQ29tcGlsZXJTZXJ2aWNlcwBEZWJ1Z2dpbmdNb2RlcwBTeXN0" +
"ZW0uQ29sbGVjdGlvbnMATXlDbGFzcwBPYmplY3QAQml0QXJyYXkAU2ltcGxlQXNzZW1ibHkAAAAAAMTg0dwuSwJOlS/aawYA9HgABCABAQgDIAABBSABARER" +
"CHzshde+p3mOAwYSGQgBAAgAAAAAAB4BAAEAVAIWV3JhcE5vbkV4Y2VwdGlvblRocm93cwEIAQAHAQAAAAAAAFQjAAAAAAAAAAAAAG4jAAAAIAAAAAAAAAAA" +
"AAAAAAAAAAAAAAAAAABgIwAAAAAAAAAAAAAAAF9Db3JEbGxNYWluAG1zY29yZWUuZGxsAAAAAAD/JQAgABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
"AAAAAAAAAAAAAAAAAAAAAAAAAAABABAAAAAYAACAAAAAAAAAAAAAAAAAAAABAAEAAAAwAACAAAAAAAAAAAAAAAAAAAABAAAAAABIAAAAWEAAAGQCAAAAAAAA" +
"AAAAAGQCNAAAAFYAUwBfAFYARQBSAFMASQBPAE4AXwBJAE4ARgBPAAAAAAC9BO/+AAABAAAAAAAAAAAAAAAAAAAAAAA/AAAAAAAAAAQAAAACAAAAAAAAAAAA" +
"AAAAAAAARAAAAAEAVgBhAHIARgBpAGwAZQBJAG4AZgBvAAAAAAAkAAQAAABUAHIAYQBuAHMAbABhAHQAaQBvAG4AAAAAAAAAsATEAQAAAQBTAHQAcgBpAG4A" +
"ZwBGAGkAbABlAEkAbgBmAG8AAACgAQAAAQAwADAAMAAwADAANABiADAAAAAsAAIAAQBGAGkAbABlAEQAZQBzAGMAcgBpAHAAdABpAG8AbgAAAAAAIAAAADAA" +
"CAABAEYAaQBsAGUAVgBlAHIAcwBpAG8AbgAAAAAAMAAuADAALgAwAC4AMAAAAEYAEwABAEkAbgB0AGUAcgBuAGEAbABOAGEAbQBlAAAAUwBpAG0AcABsAGUA" +
"QQBzAHMAZQBtAGIAbAB5AC4AZABsAGwAAAAAACgAAgABAEwAZQBnAGEAbABDAG8AcAB5AHIAaQBnAGgAdAAAACAAAABOABMAAQBPAHIAaQBnAGkAbgBhAGwA" +
"RgBpAGwAZQBuAGEAbQBlAAAAUwBpAG0AcABsAGUAQQBzAHMAZQBtAGIAbAB5AC4AZABsAGwAAAAAADQACAABAFAAcgBvAGQAdQBjAHQAVgBlAHIAcwBpAG8A" +
"bgAAADAALgAwAC4AMAAuADAAAAA4AAgAAQBBAHMAcwBlAG0AYgBsAHkAIABWAGUAcgBzAGkAbwBuAAAAMAAuADAALgAwAC4AMAAAAAAAAAAAAAAAAAAAAAAA" +
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAMAAAAgDMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
"AAAAAAAAAAAAAAAA");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ public static void DuplicateUnsignedAssembliesSameVersions()
}
}

[Fact]
[Fact]
public static void DuplicateUnsignedAssembliesSameVersionsDifferentLocale()
{
using (TempDirectory dir = new TempDirectory())
Expand Down Expand Up @@ -324,5 +324,54 @@ public static void DuplicateSignedAndUnsignedAssemblies()
}
}
}

[Fact]
public static void RelocatableAssembly()
{
string coreAssemblyPath = TestUtils.GetPathToCoreAssembly();
string coreAssemblyName = Path.GetFileNameWithoutExtension(coreAssemblyPath);

// Ensure mscorlib is specified since we want to relocate an older mscorlib later.
string coreDirectory = Path.GetDirectoryName(coreAssemblyPath);
string mscorLibPath = Path.Combine(coreDirectory, "mscorlib.dll");

using (TempDirectory dir = new TempDirectory())
using (TempFile relocatableAsmFile = new TempFile(Path.Combine(dir.Path, TestData.s_RetargetableAssemblySimpleName), TestData.s_RetargetableImage))
{
var resolver = new PathAssemblyResolver(new string[] { coreAssemblyPath, mscorLibPath, relocatableAsmFile.Path });

using (MetadataLoadContext lc = new MetadataLoadContext(resolver, coreAssemblyName))
{
Assembly retargetableAssembly = lc.LoadFromAssemblyName(TestData.s_RetargetableAssemblySimpleName);
Assert.NotNull(retargetableAssembly);

// The assembly only contains a reference to an older, retargetable mscorlib.
AssemblyName[] assemblyNames = retargetableAssembly.GetReferencedAssemblies();
AssemblyName retargetableAssemblyName = assemblyNames[0];
Assert.Equal(AssemblyNameFlags.Retargetable, retargetableAssemblyName.Flags);
Assert.Equal(new Version(2,0,5,0), retargetableAssemblyName.Version);

// Trigger PathAssemblyResolver.Resolve for the older mscorlib.
Type myType = retargetableAssembly.GetType("Relocate.MyClass");
FieldInfo[] fields = myType.GetFields();
Assert.Equal(1, fields.Length);
FieldInfo field = fields[0];
Assert.Equal("MyObj", field.Name);

// Verify that LoadFromAssemblyName also finds the newer mscorlib.
Assembly mscorlib = lc.LoadFromAssemblyName(retargetableAssemblyName);
Assert.True(mscorlib.GetName().Version > retargetableAssemblyName.Version);

// The older reference has a different public key token, which requires AssemblyNameFlags.Retargetable to find the newer assembly.
byte[] newerPKT = mscorlib.GetName().GetPublicKeyToken();
Assert.NotEmpty(newerPKT);

byte[] olderPKT = retargetableAssemblyName.GetPublicKeyToken();
Assert.NotEmpty(olderPKT);

Assert.False(Enumerable.SequenceEqual(newerPKT, olderPKT));
}
}
}
}
}

0 comments on commit fca6032

Please sign in to comment.