Skip to content

Commit 763216b

Browse files
authored
Merge pull request github#14045 from hvitved/csharp/standalone-resolve-target-framework
C#: Favor DLLs with most recent .NET Core target framework when resolving dependencies in standalone
2 parents da403c1 + 554a2c2 commit 763216b

File tree

3 files changed

+87
-9
lines changed

3 files changed

+87
-9
lines changed

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: 78 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,39 @@ 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 attrHandle in def.GetCustomAttributes().Select(reader.GetCustomAttribute))
182+
{
183+
var ctorHandle = attrHandle.Constructor;
184+
if (ctorHandle.Kind != HandleKind.MemberReference)
185+
{
186+
continue;
187+
}
188+
189+
var mHandle = reader.GetMemberReference((MemberReferenceHandle)ctorHandle).Parent;
190+
if (mHandle.Kind != HandleKind.TypeReference)
191+
{
192+
continue;
193+
}
194+
195+
var name = reader.GetString(reader.GetTypeReference((TypeReferenceHandle)mHandle).Name);
196+
197+
if (name is "TargetFrameworkAttribute")
198+
{
199+
var decoded = attrHandle.DecodeValue(new DummyAttributeDecoder());
200+
if (
201+
decoded.FixedArguments.Length > 0 &&
202+
decoded.FixedArguments[0].Value is string value &&
203+
NetCoreAppRegex().Match(value).Groups.TryGetValue("version", out var match))
204+
{
205+
netCoreVersion = new Version(match.Value);
206+
}
207+
break;
208+
}
209+
}
210+
211+
return new AssemblyInfo(filename, reader.GetString(def.Name), def.Version, culture, publicKeyString.ToString(), netCoreVersion);
166212
}
167213
}
168214
catch (BadImageFormatException)
@@ -176,5 +222,33 @@ public static AssemblyInfo ReadFromFile(string filename)
176222

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

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)