Skip to content

Commit a0cf467

Browse files
committed
C#: Favor DLLs with most recent .NET Core target framework when resolving dependencies in standalone
1 parent f996fa2 commit a0cf467

3 files changed

Lines changed: 88 additions & 9 deletions

File tree

csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/AssemblyCache.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,13 @@ private void IndexReferences()
5959
// The OrderBy is used to ensure that we by default select the highest version number.
6060
foreach (var info in assemblyInfoByFileName.Values
6161
.OrderBy(info => info.Name)
62+
.ThenBy(info => info.NetCoreVersion ?? emptyVersion)
6263
.ThenBy(info => info.Version ?? emptyVersion))
6364
{
6465
foreach (var index in info.IndexStrings)
66+
{
6567
assemblyInfoById[index] = info;
68+
}
6669
}
6770
}
6871

csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/AssemblyInfo.cs

Lines changed: 79 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@
55
using System.Reflection;
66
using System.Security.Cryptography;
77
using System.Text;
8+
using System.Reflection.Metadata;
9+
using System.Text.RegularExpressions;
810

911
namespace Semmle.Extraction.CSharp.DependencyFetching
1012
{
1113
/// <summary>
1214
/// Stores information about an assembly file (DLL).
1315
/// </summary>
14-
internal sealed class AssemblyInfo
16+
internal sealed partial class AssemblyInfo
1517
{
1618
/// <summary>
1719
/// The file containing the assembly.
@@ -28,6 +30,17 @@ internal sealed class AssemblyInfo
2830
/// </summary>
2931
public System.Version? Version { get; }
3032

33+
/// <summary>
34+
/// The version number of the .NET Core framework that this assembly targets.
35+
///
36+
/// This is extracted from the `TargetFrameworkAttribute` of the assembly, e.g.
37+
/// ```
38+
/// [assembly:TargetFramework(".NETCoreApp,Version=v7.0")]
39+
/// ```
40+
/// yields version 7.0.
41+
/// </summary>
42+
public Version? NetCoreVersion { get; }
43+
3144
/// <summary>
3245
/// The public key token of the assembly.
3346
/// </summary>
@@ -97,13 +110,14 @@ private AssemblyInfo(string id, string filename)
97110
Filename = filename;
98111
}
99112

100-
private AssemblyInfo(string filename, string name, Version version, string culture, string publicKeyToken)
113+
private AssemblyInfo(string filename, string name, Version version, string culture, string publicKeyToken, Version? netCoreVersion)
101114
{
102115
Filename = filename;
103116
Name = name;
104117
Version = version;
105118
Culture = culture;
106119
PublicKeyToken = publicKeyToken;
120+
NetCoreVersion = netCoreVersion;
107121
}
108122

109123
/// <summary>
@@ -150,7 +164,7 @@ public static AssemblyInfo ReadFromFile(string filename)
150164
var metadata = pereader.GetMetadata();
151165
unsafe
152166
{
153-
var reader = new System.Reflection.Metadata.MetadataReader(metadata.Pointer, metadata.Length);
167+
var reader = new MetadataReader(metadata.Pointer, metadata.Length);
154168
var def = reader.GetAssemblyDefinition();
155169

156170
// This is how you compute the public key token from the full public key.
@@ -162,7 +176,40 @@ public static AssemblyInfo ReadFromFile(string filename)
162176
publicKeyString.AppendFormat("{0:x2}", b);
163177

164178
var culture = def.Culture.IsNil ? "neutral" : reader.GetString(def.Culture);
165-
return new AssemblyInfo(filename, reader.GetString(def.Name), def.Version, culture, publicKeyString.ToString());
179+
Version? netCoreVersion = null;
180+
181+
foreach (var attrsHandle in def.GetCustomAttributes())
182+
{
183+
var attrHandle = reader.GetCustomAttribute(attrsHandle);
184+
var ctorHandle = attrHandle.Constructor;
185+
if (ctorHandle.Kind != HandleKind.MemberReference)
186+
{
187+
continue;
188+
}
189+
190+
var mHandle = reader.GetMemberReference((MemberReferenceHandle)ctorHandle).Parent;
191+
if (mHandle.Kind != HandleKind.TypeReference)
192+
{
193+
continue;
194+
}
195+
196+
var name = reader.GetString(reader.GetTypeReference((TypeReferenceHandle)mHandle).Name);
197+
198+
if (name is "TargetFrameworkAttribute")
199+
{
200+
var decoded = attrHandle.DecodeValue(new DummyAttributeDecoder());
201+
if (decoded.FixedArguments.Length > 0 && decoded.FixedArguments[0].Value is string value)
202+
{
203+
if (NetCoreAppRegex().Match(value).Groups.TryGetValue("version", out var match))
204+
{
205+
netCoreVersion = new Version(match.Value);
206+
}
207+
}
208+
break;
209+
}
210+
}
211+
212+
return new AssemblyInfo(filename, reader.GetString(def.Name), def.Version, culture, publicKeyString.ToString(), netCoreVersion);
166213
}
167214
}
168215
catch (BadImageFormatException)
@@ -176,5 +223,33 @@ public static AssemblyInfo ReadFromFile(string filename)
176223

177224
throw new AssemblyLoadException();
178225
}
226+
227+
[GeneratedRegex(@"^\.NETCoreApp,Version=v(?<version>\d+\.\d+)$", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Singleline)]
228+
private static partial Regex NetCoreAppRegex();
229+
230+
private class DummyAttributeDecoder : ICustomAttributeTypeProvider<int>
231+
{
232+
public int GetPrimitiveType(PrimitiveTypeCode typeCode) => 0;
233+
234+
public int GetSystemType() => throw new NotImplementedException();
235+
236+
public int GetSZArrayType(int elementType) =>
237+
throw new NotImplementedException();
238+
239+
public int GetTypeFromDefinition(MetadataReader reader, TypeDefinitionHandle handle, byte rawTypeKind) =>
240+
throw new NotImplementedException();
241+
242+
public int GetTypeFromReference(MetadataReader reader, TypeReferenceHandle handle, byte rawTypeKind) =>
243+
throw new NotImplementedException();
244+
245+
public int GetTypeFromSerializedName(string name) =>
246+
throw new NotImplementedException();
247+
248+
public PrimitiveTypeCode GetUnderlyingEnumType(int type) =>
249+
throw new NotImplementedException();
250+
251+
public bool IsSystemType(int type) => throw new NotImplementedException();
252+
253+
}
179254
}
180255
}

csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/DependencyManager.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -122,12 +122,12 @@ public DependencyManager(string srcDir, IDependencyOptions options, ILogger logg
122122
ResolveConflicts();
123123

124124
// Output the findings
125-
foreach (var r in usedReferences.Keys)
125+
foreach (var r in usedReferences.Keys.OrderBy(r => r))
126126
{
127127
progressMonitor.ResolvedReference(r);
128128
}
129129

130-
foreach (var r in unresolvedReferences)
130+
foreach (var r in unresolvedReferences.OrderBy(r => r.Key))
131131
{
132132
progressMonitor.UnresolvedReference(r.Key, r.Value);
133133
}
@@ -232,7 +232,8 @@ private void ResolveConflicts()
232232
}
233233
}
234234

235-
sortedReferences = sortedReferences.OrderBy(r => r.Version).ToList();
235+
var emptyVersion = new Version(0, 0);
236+
sortedReferences = sortedReferences.OrderBy(r => r.NetCoreVersion ?? emptyVersion).ThenBy(r => r.Version ?? emptyVersion).ToList();
236237

237238
var finalAssemblyList = new Dictionary<string, AssemblyInfo>();
238239

@@ -253,9 +254,9 @@ private void ResolveConflicts()
253254
foreach (var r in sortedReferences)
254255
{
255256
var resolvedInfo = finalAssemblyList[r.Name];
256-
if (resolvedInfo.Version != r.Version)
257+
if (resolvedInfo.Version != r.Version || resolvedInfo.NetCoreVersion != r.NetCoreVersion)
257258
{
258-
progressMonitor.ResolvedConflict(r.Id, resolvedInfo.Id);
259+
progressMonitor.ResolvedConflict(r.Id, resolvedInfo.Id + resolvedInfo.NetCoreVersion is null ? "" : $" (.NET Core {resolvedInfo.NetCoreVersion})");
259260
++conflictedReferences;
260261
}
261262
}

0 commit comments

Comments
 (0)