diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/AssemblyCache.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/AssemblyCache.cs
index 7adceebc7eb..3a124d13e0e 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/AssemblyCache.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/AssemblyCache.cs
@@ -1,5 +1,4 @@
-using System;
-using System.Collections.Generic;
+using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -20,7 +19,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
/// assembly cache.
///
/// Callback for progress.
- public AssemblyCache(IEnumerable paths, ProgressMonitor progressMonitor)
+ public AssemblyCache(IEnumerable paths, IEnumerable frameworkPaths, ProgressMonitor progressMonitor)
{
foreach (var path in paths)
{
@@ -40,7 +39,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
progressMonitor.LogInfo("AssemblyCache: Path not found: " + path);
}
}
- IndexReferences();
+ IndexReferences(frameworkPaths);
}
///
@@ -57,13 +56,11 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
}
}
- private static readonly Version emptyVersion = new Version(0, 0, 0, 0);
-
///
/// Indexes all DLLs we have located.
/// Because this is a potentially time-consuming operation, it is put into a separate stage.
///
- private void IndexReferences()
+ private void IndexReferences(IEnumerable frameworkPaths)
{
// Read all of the files
foreach (var filename in pendingDllsToIndex)
@@ -71,13 +68,9 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
IndexReference(filename);
}
- // Index "assemblyInfo" by version string
- // 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)
- .ThenBy(info => info.Filename))
+ .OrderAssemblyInfosByPreference(frameworkPaths))
{
foreach (var index in info.IndexStrings)
{
diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/AssemblyCacheExtensions.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/AssemblyCacheExtensions.cs
new file mode 100644
index 00000000000..a4e7723266b
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/AssemblyCacheExtensions.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Semmle.Extraction.CSharp.DependencyFetching
+{
+ internal static class AssemblyCacheExtensions
+ {
+ private static readonly Version emptyVersion = new Version(0, 0, 0, 0);
+
+ ///
+ /// This method orders AssemblyInfos by version numbers (.net core version first, then assembly version). Finally, it orders by filename to make the order deterministic.
+ ///
+ public static IOrderedEnumerable OrderAssemblyInfosByPreference(this IEnumerable assemblies, IEnumerable frameworkPaths)
+ {
+ // prefer framework assemblies over others
+ int initialOrdering(AssemblyInfo info) => frameworkPaths.Any(framework => info.Filename.StartsWith(framework, StringComparison.OrdinalIgnoreCase)) ? 1 : 0;
+
+ var ordered = assemblies is IOrderedEnumerable o
+ ? o.ThenBy(initialOrdering)
+ : assemblies.OrderBy(initialOrdering);
+
+ return ordered
+ .ThenBy(info => info.NetCoreVersion ?? emptyVersion)
+ .ThenBy(info => info.Version ?? emptyVersion)
+ .ThenBy(info => info.Filename);
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/DependencyManager.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/DependencyManager.cs
index 9bb78cc8d7c..04f526eb699 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/DependencyManager.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/DependencyManager.cs
@@ -128,16 +128,18 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
DownloadMissingPackages(allNonBinaryFiles, dllPaths);
}
+ var frameworkLocations = new HashSet();
+
// Find DLLs in the .Net / Asp.Net Framework
// This block needs to come after the nuget restore, because the nuget restore might fetch the .NET Core/Framework reference assemblies.
if (options.ScanNetFrameworkDlls)
{
- AddNetFrameworkDlls(dllPaths);
- AddAspNetCoreFrameworkDlls(dllPaths);
- AddMicrosoftWindowsDesktopDlls(dllPaths);
+ AddNetFrameworkDlls(dllPaths, frameworkLocations);
+ AddAspNetCoreFrameworkDlls(dllPaths, frameworkLocations);
+ AddMicrosoftWindowsDesktopDlls(dllPaths, frameworkLocations);
}
- assemblyCache = new AssemblyCache(dllPaths, progressMonitor);
+ assemblyCache = new AssemblyCache(dllPaths, frameworkLocations, progressMonitor);
AnalyseSolutions(solutions);
foreach (var filename in assemblyCache.AllAssemblies.Select(a => a.Filename))
@@ -146,7 +148,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
}
RemoveNugetAnalyzerReferences();
- ResolveConflicts();
+ ResolveConflicts(frameworkLocations);
// Output the findings
foreach (var r in usedReferences.Keys.OrderBy(r => r))
@@ -228,7 +230,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
}
}
- private void AddNetFrameworkDlls(ISet dllPaths)
+ private void AddNetFrameworkDlls(ISet dllPaths, ISet frameworkLocations)
{
// Multiple dotnet framework packages could be present.
// The order of the packages is important, we're adding the first one that is present in the nuget cache.
@@ -241,6 +243,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
if (frameworkPath.Path is not null)
{
dllPaths.Add(frameworkPath.Path);
+ frameworkLocations.Add(frameworkPath.Path);
progressMonitor.LogInfo($"Found .NET Core/Framework DLLs in NuGet packages at {frameworkPath.Path}. Not adding installation directory.");
for (var i = frameworkPath.Index + 1; i < packagesInPrioOrder.Length; i++)
@@ -270,6 +273,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
progressMonitor.LogInfo($".NET runtime location selected: {runtimeLocation}");
dllPaths.Add(runtimeLocation);
+ frameworkLocations.Add(runtimeLocation);
}
private void RemoveNugetPackageReference(string packagePrefix, ISet dllPaths)
@@ -294,7 +298,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
}
}
- private void AddAspNetCoreFrameworkDlls(ISet dllPaths)
+ private void AddAspNetCoreFrameworkDlls(ISet dllPaths, ISet frameworkLocations)
{
if (!fileContent.IsNewProjectStructureUsed || !fileContent.UseAspNetCoreDlls)
{
@@ -306,20 +310,25 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
{
progressMonitor.LogInfo($"Found ASP.NET Core in NuGet packages. Not adding installation directory.");
dllPaths.Add(aspNetCorePackage);
+ frameworkLocations.Add(aspNetCorePackage);
+ return;
}
- else if (Runtime.AspNetCoreRuntime is string aspNetCoreRuntime)
+
+ if (Runtime.AspNetCoreRuntime is string aspNetCoreRuntime)
{
progressMonitor.LogInfo($"ASP.NET runtime location selected: {aspNetCoreRuntime}");
dllPaths.Add(aspNetCoreRuntime);
+ frameworkLocations.Add(aspNetCoreRuntime);
}
}
- private void AddMicrosoftWindowsDesktopDlls(ISet dllPaths)
+ private void AddMicrosoftWindowsDesktopDlls(ISet dllPaths, ISet frameworkLocations)
{
if (GetPackageDirectory(FrameworkPackageNames.WindowsDesktopFramework) is string windowsDesktopApp)
{
progressMonitor.LogInfo($"Found Windows Desktop App in NuGet packages.");
dllPaths.Add(windowsDesktopApp);
+ frameworkLocations.Add(windowsDesktopApp);
}
}
@@ -472,7 +481,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
/// If the same assembly name is duplicated with different versions,
/// resolve to the higher version number.
///
- private void ResolveConflicts()
+ private void ResolveConflicts(IEnumerable frameworkPaths)
{
var sortedReferences = new List();
foreach (var usedReference in usedReferences)
@@ -488,11 +497,8 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
}
}
- var emptyVersion = new Version(0, 0);
sortedReferences = sortedReferences
- .OrderBy(r => r.NetCoreVersion ?? emptyVersion)
- .ThenBy(r => r.Version ?? emptyVersion)
- .ThenBy(r => r.Filename)
+ .OrderAssemblyInfosByPreference(frameworkPaths)
.ToList();
var finalAssemblyList = new Dictionary();