Skip to content

Commit d8e7c9c

Browse files
authored
Merge pull request github#14767 from michaelnebel/csharp/projectframeworkassets
C#: Framework dependency detection.
2 parents 7263d4d + b7b10ce commit d8e7c9c

File tree

5 files changed

+1628
-95
lines changed

5 files changed

+1628
-95
lines changed

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

Lines changed: 80 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,6 @@ internal class Assets
1515
{
1616
private readonly ProgressMonitor progressMonitor;
1717

18-
private static readonly string[] netFrameworks = new[] {
19-
"microsoft.aspnetcore.app.ref",
20-
"microsoft.netcore.app.ref",
21-
"microsoft.netframework.referenceassemblies",
22-
"microsoft.windowsdesktop.app.ref",
23-
"netstandard.library.ref"
24-
};
25-
2618
internal Assets(ProgressMonitor progressMonitor)
2719
{
2820
this.progressMonitor = progressMonitor;
@@ -69,19 +61,19 @@ private record class ReferenceInfo(string? Type, Dictionary<string, object>? Com
6961
/// }
7062
/// }
7163
///
72-
/// Returns dependencies
73-
/// RequiredPaths = {
64+
/// Adds the following dependencies
65+
/// Paths: {
7466
/// "castle.core/4.4.1/lib/netstandard1.5/Castle.Core.dll",
7567
/// "json.net/1.0.33/lib/netstandard2.0/Json.Net.dll"
7668
/// }
77-
/// UsedPackages = {
69+
/// Packages: {
7870
/// "castle.core",
7971
/// "json.net"
8072
/// }
8173
/// </summary>
82-
private DependencyContainer AddPackageDependencies(JObject json, DependencyContainer dependencies)
74+
private void AddPackageDependencies(JObject json, DependencyContainer dependencies)
8375
{
84-
// If there are more than one framework we need to pick just one.
76+
// If there is more than one framework we need to pick just one.
8577
// To ensure stability we pick one based on the lexicographic order of
8678
// the framework names.
8779
var references = json
@@ -94,7 +86,7 @@ private DependencyContainer AddPackageDependencies(JObject json, DependencyConta
9486
if (references is null)
9587
{
9688
progressMonitor.LogDebug("No references found in the targets section in the assets file.");
97-
return dependencies;
89+
return;
9890
}
9991

10092
// Find all the compile dependencies for each reference and
@@ -109,19 +101,83 @@ private DependencyContainer AddPackageDependencies(JObject json, DependencyConta
109101
return;
110102
}
111103

112-
// If this is a .NET framework reference then include everything.
113-
if (netFrameworks.Any(framework => name.StartsWith(framework)))
114-
{
115-
dependencies.Add(name);
116-
}
117-
else
104+
if (info.Compile is null || !info.Compile.Any())
118105
{
119-
info.Compile?
120-
.ForEach(r => dependencies.Add(name, r.Key));
106+
// If this is a framework reference then include everything.
107+
if (FrameworkPackageNames.AllFrameworks.Any(framework => name.StartsWith(framework)))
108+
{
109+
dependencies.AddFramework(name);
110+
}
111+
return;
121112
}
113+
114+
info.Compile
115+
.ForEach(r => dependencies.Add(name, r.Key));
122116
});
123117

124-
return dependencies;
118+
return;
119+
}
120+
121+
/// <summary>
122+
/// Add the framework dependencies from the assets file to dependencies.
123+
///
124+
/// Example:
125+
/// "project": {
126+
// "version": "1.0.0",
127+
// "frameworks": {
128+
// "net7.0": {
129+
// "frameworkReferences": {
130+
// "Microsoft.AspNetCore.App": {
131+
// "privateAssets": "none"
132+
// },
133+
// "Microsoft.NETCore.App": {
134+
// "privateAssets": "all"
135+
// }
136+
// }
137+
// }
138+
// }
139+
// }
140+
//
141+
/// Adds the following dependencies
142+
/// Paths: {
143+
/// "microsoft.aspnetcore.app.ref",
144+
/// "microsoft.netcore.app.ref"
145+
/// }
146+
/// Packages: {
147+
/// "microsoft.aspnetcore.app.ref",
148+
/// "microsoft.netcore.app.ref"
149+
/// }
150+
/// </summary>
151+
private void AddFrameworkDependencies(JObject json, DependencyContainer dependencies)
152+
{
153+
154+
var frameworks = json
155+
.GetProperty("project")?
156+
.GetProperty("frameworks");
157+
158+
if (frameworks is null)
159+
{
160+
progressMonitor.LogDebug("No framework section in assets.json.");
161+
return;
162+
}
163+
164+
// If there is more than one framework we need to pick just one.
165+
// To ensure stability we pick one based on the lexicographic order of
166+
// the framework names.
167+
var references = frameworks
168+
.Properties()?
169+
.MaxBy(p => p.Name)?
170+
.Value["frameworkReferences"] as JObject;
171+
172+
if (references is null)
173+
{
174+
progressMonitor.LogDebug("No framework references in assets.json.");
175+
return;
176+
}
177+
178+
references
179+
.Properties()
180+
.ForEach(f => dependencies.AddFramework($"{f.Name}.Ref".ToLowerInvariant()));
125181
}
126182

127183
/// <summary>
@@ -135,6 +191,7 @@ public bool TryParse(string json, DependencyContainer dependencies)
135191
{
136192
var obj = JObject.Parse(json);
137193
AddPackageDependencies(obj, dependencies);
194+
AddFrameworkDependencies(obj, dependencies);
138195
return true;
139196
}
140197
catch (Exception e)

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

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,19 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
99
/// </summary>
1010
internal class DependencyContainer
1111
{
12-
private readonly List<string> requiredPaths = new();
13-
private readonly HashSet<string> usedPackages = new();
12+
/// <summary>
13+
/// Paths to dependencies required for compilation.
14+
/// </summary>
15+
public List<string> Paths { get; } = new();
16+
17+
/// <summary>
18+
/// Packages that are used as a part of the required dependencies.
19+
/// </summary>
20+
public HashSet<string> Packages { get; } = new();
1421

1522
/// <summary>
16-
/// In most cases paths in asset files point to dll's or the empty _._ file, which
17-
/// is sometimes there to avoid the directory being empty.
18-
/// That is, if the path specifically adds a .dll we use that, otherwise we as a fallback
19-
/// add the entire directory (which should be fine in case of _._ as well).
23+
/// If the path specifically adds a .dll we use that, otherwise we as a fallback
24+
/// add the entire directory.
2025
/// </summary>
2126
private static string ParseFilePath(string path)
2227
{
@@ -32,16 +37,6 @@ private static string GetPackageName(string package) =>
3237
.Split(Path.DirectorySeparatorChar)
3338
.First();
3439

35-
/// <summary>
36-
/// Paths to dependencies required for compilation.
37-
/// </summary>
38-
public IEnumerable<string> RequiredPaths => requiredPaths;
39-
40-
/// <summary>
41-
/// Packages that are used as a part of the required dependencies.
42-
/// </summary>
43-
public HashSet<string> UsedPackages => usedPackages;
44-
4540
/// <summary>
4641
/// Add a dependency inside a package.
4742
/// </summary>
@@ -50,20 +45,27 @@ public void Add(string package, string dependency)
5045
var p = package.Replace('/', Path.DirectorySeparatorChar);
5146
var d = dependency.Replace('/', Path.DirectorySeparatorChar);
5247

48+
// In most cases paths in asset files point to dll's or the empty _._ file.
49+
// That is, for _._ we don't need to add anything.
50+
if (Path.GetFileName(d) == "_._")
51+
{
52+
return;
53+
}
54+
5355
var path = Path.Combine(p, ParseFilePath(d));
54-
requiredPaths.Add(path);
55-
usedPackages.Add(GetPackageName(p));
56+
Paths.Add(path);
57+
Packages.Add(GetPackageName(p));
5658
}
5759

5860
/// <summary>
59-
/// Add a dependency to an entire package
61+
/// Add a dependency to an entire framework package.
6062
/// </summary>
61-
public void Add(string package)
63+
public void AddFramework(string framework)
6264
{
63-
var p = package.Replace('/', Path.DirectorySeparatorChar);
65+
var p = framework.Replace('/', Path.DirectorySeparatorChar);
6466

65-
requiredPaths.Add(p);
66-
usedPackages.Add(GetPackageName(p));
67+
Paths.Add(p);
68+
Packages.Add(GetPackageName(p));
6769
}
6870
}
6971
}

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

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ public DependencyManager(string srcDir, IDependencyOptions options, ILogger logg
119119
var dependencies = Assets.GetCompilationDependencies(progressMonitor, assets1.Union(assets2));
120120

121121
var paths = dependencies
122-
.RequiredPaths
122+
.Paths
123123
.Select(d => Path.Combine(packageDirectory.DirInfo.FullName, d))
124124
.ToList();
125125
dllPaths.UnionWith(paths);
@@ -232,13 +232,7 @@ private void AddNetFrameworkDlls(ISet<string> dllPaths)
232232
{
233233
// Multiple dotnet framework packages could be present.
234234
// The order of the packages is important, we're adding the first one that is present in the nuget cache.
235-
var packagesInPrioOrder = new string[]
236-
{
237-
"microsoft.netcore.app.ref", // net7.0, ... net5.0, netcoreapp3.1, netcoreapp3.0
238-
"microsoft.netframework.referenceassemblies.", // net48, ..., net20
239-
"netstandard.library.ref", // netstandard2.1
240-
"netstandard.library" // netstandard2.0
241-
};
235+
var packagesInPrioOrder = FrameworkPackageNames.NetFrameworks;
242236

243237
var frameworkPath = packagesInPrioOrder
244238
.Select((s, index) => (Index: index, Path: GetPackageDirectory(s)))
@@ -308,7 +302,7 @@ private void AddAspNetCoreFrameworkDlls(ISet<string> dllPaths)
308302
}
309303

310304
// First try to find ASP.NET Core assemblies in the NuGet packages
311-
if (GetPackageDirectory("microsoft.aspnetcore.app.ref") is string aspNetCorePackage)
305+
if (GetPackageDirectory(FrameworkPackageNames.AspNetCoreFramework) is string aspNetCorePackage)
312306
{
313307
progressMonitor.LogInfo($"Found ASP.NET Core in NuGet packages. Not adding installation directory.");
314308
dllPaths.Add(aspNetCorePackage);
@@ -322,7 +316,7 @@ private void AddAspNetCoreFrameworkDlls(ISet<string> dllPaths)
322316

323317
private void AddMicrosoftWindowsDesktopDlls(ISet<string> dllPaths)
324318
{
325-
if (GetPackageDirectory("microsoft.windowsdesktop.app.ref") is string windowsDesktopApp)
319+
if (GetPackageDirectory(FrameworkPackageNames.WindowsDesktopFramework) is string windowsDesktopApp)
326320
{
327321
progressMonitor.LogInfo($"Found Windows Desktop App in NuGet packages.");
328322
dllPaths.Add(windowsDesktopApp);
@@ -356,7 +350,7 @@ private IEnumerable<string> GetAllPackageDirectories()
356350

357351
private void LogAllUnusedPackages(DependencyContainer dependencies) =>
358352
GetAllPackageDirectories()
359-
.Where(package => !dependencies.UsedPackages.Contains(package))
353+
.Where(package => !dependencies.Packages.Contains(package))
360354
.ForEach(package => progressMonitor.LogInfo($"Unused package: {package}"));
361355

362356
private void GenerateSourceFileFromImplicitUsings()
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
4+
namespace Semmle.Extraction.CSharp.DependencyFetching
5+
{
6+
internal static class FrameworkPackageNames
7+
{
8+
public static string AspNetCoreFramework { get; } = "microsoft.aspnetcore.app.ref";
9+
10+
public static string WindowsDesktopFramework { get; } = "microsoft.windowsdesktop.app.ref";
11+
12+
// The order of the packages is important.
13+
public static string[] NetFrameworks { get; } = new string[]
14+
{
15+
"microsoft.netcore.app.ref", // net7.0, ... net5.0, netcoreapp3.1, netcoreapp3.0
16+
"microsoft.netframework.referenceassemblies.", // net48, ..., net20
17+
"netstandard.library.ref", // netstandard2.1
18+
"netstandard.library" // netstandard2.0
19+
};
20+
21+
public static IEnumerable<string> AllFrameworks { get; } =
22+
NetFrameworks
23+
.Union(new string[] { AspNetCoreFramework, WindowsDesktopFramework });
24+
}
25+
}

0 commit comments

Comments
 (0)