C#: Favor DLLs with most recent .NET Core target framework when resolving dependencies in standalone

This commit is contained in:
Tom Hvitved
2023-08-24 10:56:54 +02:00
parent f996fa2f8b
commit 554a2c26c3
3 changed files with 87 additions and 9 deletions

View File

@@ -59,10 +59,13 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
// The OrderBy is used to ensure that we by default select the highest version number.
foreach (var info in assemblyInfoByFileName.Values
.OrderBy(info => info.Name)
.ThenBy(info => info.NetCoreVersion ?? emptyVersion)
.ThenBy(info => info.Version ?? emptyVersion))
{
foreach (var index in info.IndexStrings)
{
assemblyInfoById[index] = info;
}
}
}

View File

@@ -5,13 +5,15 @@ using System.Linq;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using System.Reflection.Metadata;
using System.Text.RegularExpressions;
namespace Semmle.Extraction.CSharp.DependencyFetching
{
/// <summary>
/// Stores information about an assembly file (DLL).
/// </summary>
internal sealed class AssemblyInfo
internal sealed partial class AssemblyInfo
{
/// <summary>
/// The file containing the assembly.
@@ -28,6 +30,17 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
/// </summary>
public System.Version? Version { get; }
/// <summary>
/// The version number of the .NET Core framework that this assembly targets.
///
/// This is extracted from the `TargetFrameworkAttribute` of the assembly, e.g.
/// ```
/// [assembly:TargetFramework(".NETCoreApp,Version=v7.0")]
/// ```
/// yields version 7.0.
/// </summary>
public Version? NetCoreVersion { get; }
/// <summary>
/// The public key token of the assembly.
/// </summary>
@@ -97,13 +110,14 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
Filename = filename;
}
private AssemblyInfo(string filename, string name, Version version, string culture, string publicKeyToken)
private AssemblyInfo(string filename, string name, Version version, string culture, string publicKeyToken, Version? netCoreVersion)
{
Filename = filename;
Name = name;
Version = version;
Culture = culture;
PublicKeyToken = publicKeyToken;
NetCoreVersion = netCoreVersion;
}
/// <summary>
@@ -150,7 +164,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
var metadata = pereader.GetMetadata();
unsafe
{
var reader = new System.Reflection.Metadata.MetadataReader(metadata.Pointer, metadata.Length);
var reader = new MetadataReader(metadata.Pointer, metadata.Length);
var def = reader.GetAssemblyDefinition();
// This is how you compute the public key token from the full public key.
@@ -162,7 +176,39 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
publicKeyString.AppendFormat("{0:x2}", b);
var culture = def.Culture.IsNil ? "neutral" : reader.GetString(def.Culture);
return new AssemblyInfo(filename, reader.GetString(def.Name), def.Version, culture, publicKeyString.ToString());
Version? netCoreVersion = null;
foreach (var attrHandle in def.GetCustomAttributes().Select(reader.GetCustomAttribute))
{
var ctorHandle = attrHandle.Constructor;
if (ctorHandle.Kind != HandleKind.MemberReference)
{
continue;
}
var mHandle = reader.GetMemberReference((MemberReferenceHandle)ctorHandle).Parent;
if (mHandle.Kind != HandleKind.TypeReference)
{
continue;
}
var name = reader.GetString(reader.GetTypeReference((TypeReferenceHandle)mHandle).Name);
if (name is "TargetFrameworkAttribute")
{
var decoded = attrHandle.DecodeValue(new DummyAttributeDecoder());
if (
decoded.FixedArguments.Length > 0 &&
decoded.FixedArguments[0].Value is string value &&
NetCoreAppRegex().Match(value).Groups.TryGetValue("version", out var match))
{
netCoreVersion = new Version(match.Value);
}
break;
}
}
return new AssemblyInfo(filename, reader.GetString(def.Name), def.Version, culture, publicKeyString.ToString(), netCoreVersion);
}
}
catch (BadImageFormatException)
@@ -176,5 +222,33 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
throw new AssemblyLoadException();
}
[GeneratedRegex(@"^\.NETCoreApp,Version=v(?<version>\d+\.\d+)$", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Singleline)]
private static partial Regex NetCoreAppRegex();
private class DummyAttributeDecoder : ICustomAttributeTypeProvider<int>
{
public int GetPrimitiveType(PrimitiveTypeCode typeCode) => 0;
public int GetSystemType() => throw new NotImplementedException();
public int GetSZArrayType(int elementType) =>
throw new NotImplementedException();
public int GetTypeFromDefinition(MetadataReader reader, TypeDefinitionHandle handle, byte rawTypeKind) =>
throw new NotImplementedException();
public int GetTypeFromReference(MetadataReader reader, TypeReferenceHandle handle, byte rawTypeKind) =>
throw new NotImplementedException();
public int GetTypeFromSerializedName(string name) =>
throw new NotImplementedException();
public PrimitiveTypeCode GetUnderlyingEnumType(int type) =>
throw new NotImplementedException();
public bool IsSystemType(int type) => throw new NotImplementedException();
}
}
}

View File

@@ -122,12 +122,12 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
ResolveConflicts();
// Output the findings
foreach (var r in usedReferences.Keys)
foreach (var r in usedReferences.Keys.OrderBy(r => r))
{
progressMonitor.ResolvedReference(r);
}
foreach (var r in unresolvedReferences)
foreach (var r in unresolvedReferences.OrderBy(r => r.Key))
{
progressMonitor.UnresolvedReference(r.Key, r.Value);
}
@@ -232,7 +232,8 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
}
}
sortedReferences = sortedReferences.OrderBy(r => r.Version).ToList();
var emptyVersion = new Version(0, 0);
sortedReferences = sortedReferences.OrderBy(r => r.NetCoreVersion ?? emptyVersion).ThenBy(r => r.Version ?? emptyVersion).ToList();
var finalAssemblyList = new Dictionary<string, AssemblyInfo>();
@@ -253,9 +254,9 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
foreach (var r in sortedReferences)
{
var resolvedInfo = finalAssemblyList[r.Name];
if (resolvedInfo.Version != r.Version)
if (resolvedInfo.Version != r.Version || resolvedInfo.NetCoreVersion != r.NetCoreVersion)
{
progressMonitor.ResolvedConflict(r.Id, resolvedInfo.Id);
progressMonitor.ResolvedConflict(r.Id, resolvedInfo.Id + resolvedInfo.NetCoreVersion is null ? "" : $" (.NET Core {resolvedInfo.NetCoreVersion})");
++conflictedReferences;
}
}