Skip to content

Commit d723905

Browse files
authored
Merge pull request github#14368 from tamasvajk/standalone/use-legacy-framework-dlls
C#: Choose between .NET framework or core DLLs in standalone
2 parents 5356100 + 15ec0a1 commit d723905

File tree

31 files changed

+1109
-108
lines changed

31 files changed

+1109
-108
lines changed

.github/workflows/csharp-qltest.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ jobs:
9191
run: |
9292
# Generate (Asp)NetCore stubs
9393
STUBS_PATH=stubs_output
94-
python3 ql/src/Stubs/make_stubs_nuget.py webapp Swashbuckle.AspNetCore.Swagger latest "$STUBS_PATH"
94+
python3 ql/src/Stubs/make_stubs_nuget.py webapp Swashbuckle.AspNetCore.Swagger 6.5.0 "$STUBS_PATH"
9595
rm -rf ql/test/resources/stubs/_frameworks
9696
# Update existing stubs in the repo with the freshly generated ones
9797
mv "$STUBS_PATH/output/stubs/_frameworks" ql/test/resources/stubs/

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

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ internal sealed partial class AssemblyInfo
3232

3333
/// <summary>
3434
/// The version number of the .NET Core framework that this assembly targets.
35-
///
35+
///
3636
/// This is extracted from the `TargetFrameworkAttribute` of the assembly, e.g.
3737
/// ```
3838
/// [assembly:TargetFramework(".NETCoreApp,Version=v7.0")]
@@ -160,11 +160,22 @@ public static AssemblyInfo ReadFromFile(string filename)
160160
* loading the same assembly from different locations.
161161
*/
162162
using var pereader = new System.Reflection.PortableExecutable.PEReader(new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read));
163+
if (!pereader.HasMetadata)
164+
{
165+
throw new AssemblyLoadException();
166+
}
167+
163168
using var sha1 = SHA1.Create();
164169
var metadata = pereader.GetMetadata();
170+
165171
unsafe
166172
{
167173
var reader = new MetadataReader(metadata.Pointer, metadata.Length);
174+
if (!reader.IsAssembly)
175+
{
176+
throw new AssemblyLoadException();
177+
}
178+
168179
var def = reader.GetAssemblyDefinition();
169180

170181
// This is how you compute the public key token from the full public key.

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

Lines changed: 178 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,12 @@ public DependencyManager(string srcDir, IDependencyOptions options, ILogger logg
4747
this.progressMonitor = new ProgressMonitor(logger);
4848
this.sourceDir = new DirectoryInfo(srcDir);
4949

50+
packageDirectory = new TemporaryDirectory(ComputeTempDirectory(sourceDir.FullName));
51+
tempWorkingDirectory = new TemporaryDirectory(FileUtils.GetTemporaryWorkingDirectory(out cleanupTempWorkingDirectory));
52+
5053
try
5154
{
52-
this.dotnet = DotNet.Make(options, progressMonitor);
55+
this.dotnet = DotNet.Make(options, progressMonitor, tempWorkingDirectory);
5356
}
5457
catch
5558
{
@@ -59,8 +62,6 @@ public DependencyManager(string srcDir, IDependencyOptions options, ILogger logg
5962

6063
this.progressMonitor.FindingFiles(srcDir);
6164

62-
packageDirectory = new TemporaryDirectory(ComputeTempDirectory(sourceDir.FullName));
63-
tempWorkingDirectory = new TemporaryDirectory(FileUtils.GetTemporaryWorkingDirectory(out cleanupTempWorkingDirectory));
6465

6566
var allFiles = GetAllFiles();
6667
var binaryFileExtensions = new HashSet<string>(new[] { ".dll", ".exe" }); // TODO: add more binary file extensions.
@@ -77,21 +78,6 @@ public DependencyManager(string srcDir, IDependencyOptions options, ILogger logg
7778
? allFiles.SelectFileNamesByExtension(".dll").ToList()
7879
: options.DllDirs.Select(Path.GetFullPath).ToList();
7980

80-
// Find DLLs in the .Net / Asp.Net Framework
81-
if (options.ScanNetFrameworkDlls)
82-
{
83-
var runtime = new Runtime(dotnet);
84-
var runtimeLocation = runtime.GetRuntime(options.UseSelfContainedDotnet);
85-
progressMonitor.LogInfo($".NET runtime location selected: {runtimeLocation}");
86-
dllDirNames.Add(runtimeLocation);
87-
88-
if (fileContent.UseAspNetDlls && runtime.GetAspRuntime() is string aspRuntime)
89-
{
90-
progressMonitor.LogInfo($"ASP.NET runtime location selected: {aspRuntime}");
91-
dllDirNames.Add(aspRuntime);
92-
}
93-
}
94-
9581
if (options.UseNuGet)
9682
{
9783
dllDirNames.Add(packageDirectory.DirInfo.FullName);
@@ -111,6 +97,26 @@ public DependencyManager(string srcDir, IDependencyOptions options, ILogger logg
11197
DownloadMissingPackages(allNonBinaryFiles);
11298
}
11399

100+
var existsNetCoreRefNugetPackage = false;
101+
var existsNetFrameworkRefNugetPackage = false;
102+
103+
// Find DLLs in the .Net / Asp.Net Framework
104+
// This block needs to come after the nuget restore, because the nuget restore might fetch the .NET Core/Framework reference assemblies.
105+
if (options.ScanNetFrameworkDlls)
106+
{
107+
existsNetCoreRefNugetPackage = IsNugetPackageAvailable("microsoft.netcore.app.ref");
108+
existsNetFrameworkRefNugetPackage = IsNugetPackageAvailable("microsoft.netframework.referenceassemblies");
109+
110+
if (existsNetCoreRefNugetPackage || existsNetFrameworkRefNugetPackage)
111+
{
112+
progressMonitor.LogInfo("Found .NET Core/Framework DLLs in NuGet packages. Not adding installation directory.");
113+
}
114+
else
115+
{
116+
AddNetFrameworkDlls(dllDirNames);
117+
}
118+
}
119+
114120
assemblyCache = new AssemblyCache(dllDirNames, progressMonitor);
115121
AnalyseSolutions(solutions);
116122

@@ -119,7 +125,7 @@ public DependencyManager(string srcDir, IDependencyOptions options, ILogger logg
119125
UseReference(filename);
120126
}
121127

122-
RemoveRuntimeNugetPackageReferences();
128+
RemoveUnnecessaryNugetPackages(existsNetCoreRefNugetPackage, existsNetFrameworkRefNugetPackage);
123129
ResolveConflicts();
124130

125131
// Output the findings
@@ -154,38 +160,158 @@ public DependencyManager(string srcDir, IDependencyOptions options, ILogger logg
154160
DateTime.Now - startTime);
155161
}
156162

157-
private void RemoveRuntimeNugetPackageReferences()
163+
private void RemoveUnnecessaryNugetPackages(bool existsNetCoreRefNugetPackage, bool existsNetFrameworkRefNugetPackage)
164+
{
165+
RemoveNugetAnalyzerReferences();
166+
RemoveRuntimeNugetPackageReferences();
167+
168+
if (fileContent.IsNewProjectStructureUsed
169+
&& !fileContent.UseAspNetCoreDlls)
170+
{
171+
// This might have been restored by the CLI even though the project isn't an asp.net core one.
172+
RemoveNugetPackageReference("microsoft.aspnetcore.app.ref");
173+
}
174+
175+
if (existsNetCoreRefNugetPackage && existsNetFrameworkRefNugetPackage)
176+
{
177+
// Multiple packages are available, we keep only one:
178+
RemoveNugetPackageReference("microsoft.netframework.referenceassemblies.");
179+
}
180+
181+
// TODO: There could be multiple `microsoft.netframework.referenceassemblies` packages,
182+
// we could keep the newest one, but this is covered by the conflict resolution logic
183+
// (if the file names match)
184+
}
185+
186+
private void RemoveNugetAnalyzerReferences()
158187
{
159188
if (!options.UseNuGet)
160189
{
161190
return;
162191
}
163192

164193
var packageFolder = packageDirectory.DirInfo.FullName.ToLowerInvariant();
165-
var runtimePackageNamePrefixes = new[]
194+
if (packageFolder == null)
195+
{
196+
return;
197+
}
198+
199+
foreach (var filename in usedReferences.Keys)
166200
{
167-
Path.Combine(packageFolder, "microsoft.netcore.app.runtime"),
168-
Path.Combine(packageFolder, "microsoft.aspnetcore.app.runtime"),
169-
Path.Combine(packageFolder, "microsoft.windowsdesktop.app.runtime"),
201+
var lowerFilename = filename.ToLowerInvariant();
202+
203+
if (lowerFilename.StartsWith(packageFolder))
204+
{
205+
var firstDirectorySeparatorCharIndex = lowerFilename.IndexOf(Path.DirectorySeparatorChar, packageFolder.Length + 1);
206+
if (firstDirectorySeparatorCharIndex == -1)
207+
{
208+
continue;
209+
}
210+
var secondDirectorySeparatorCharIndex = lowerFilename.IndexOf(Path.DirectorySeparatorChar, firstDirectorySeparatorCharIndex + 1);
211+
if (secondDirectorySeparatorCharIndex == -1)
212+
{
213+
continue;
214+
}
215+
var subFolderIndex = secondDirectorySeparatorCharIndex + 1;
216+
var isInAnalyzersFolder = lowerFilename.IndexOf("analyzers", subFolderIndex) == subFolderIndex;
217+
if (isInAnalyzersFolder)
218+
{
219+
usedReferences.Remove(filename);
220+
progressMonitor.RemovedReference(filename);
221+
}
222+
}
223+
}
224+
}
225+
private void AddNetFrameworkDlls(List<string> dllDirNames)
226+
{
227+
var runtime = new Runtime(dotnet);
228+
string? runtimeLocation = null;
229+
230+
if (options.UseSelfContainedDotnet)
231+
{
232+
runtimeLocation = runtime.ExecutingRuntime;
233+
}
234+
else if (fileContent.IsNewProjectStructureUsed)
235+
{
236+
runtimeLocation = runtime.NetCoreRuntime;
237+
}
238+
else if (fileContent.IsLegacyProjectStructureUsed)
239+
{
240+
runtimeLocation = runtime.DesktopRuntime;
241+
}
242+
243+
runtimeLocation ??= runtime.ExecutingRuntime;
244+
245+
progressMonitor.LogInfo($".NET runtime location selected: {runtimeLocation}");
246+
dllDirNames.Add(runtimeLocation);
247+
248+
if (fileContent.IsNewProjectStructureUsed
249+
&& fileContent.UseAspNetCoreDlls
250+
&& runtime.AspNetCoreRuntime is string aspRuntime)
251+
{
252+
progressMonitor.LogInfo($"ASP.NET runtime location selected: {aspRuntime}");
253+
dllDirNames.Add(aspRuntime);
254+
}
255+
}
256+
257+
private void RemoveRuntimeNugetPackageReferences()
258+
{
259+
var runtimePackagePrefixes = new[]
260+
{
261+
"microsoft.netcore.app.runtime",
262+
"microsoft.aspnetcore.app.runtime",
263+
"microsoft.windowsdesktop.app.runtime",
170264

171265
// legacy runtime packages:
172-
Path.Combine(packageFolder, "runtime.linux-x64.microsoft.netcore.app"),
173-
Path.Combine(packageFolder, "runtime.osx-x64.microsoft.netcore.app"),
174-
Path.Combine(packageFolder, "runtime.win-x64.microsoft.netcore.app"),
266+
"runtime.linux-x64.microsoft.netcore.app",
267+
"runtime.osx-x64.microsoft.netcore.app",
268+
"runtime.win-x64.microsoft.netcore.app",
269+
270+
// Internal implementation packages not meant for direct consumption:
271+
"runtime."
175272
};
273+
RemoveNugetPackageReference(runtimePackagePrefixes);
274+
}
275+
276+
private void RemoveNugetPackageReference(params string[] packagePrefixes)
277+
{
278+
if (!options.UseNuGet)
279+
{
280+
return;
281+
}
282+
283+
var packageFolder = packageDirectory.DirInfo.FullName.ToLowerInvariant();
284+
if (packageFolder == null)
285+
{
286+
return;
287+
}
288+
289+
var packagePathPrefixes = packagePrefixes.Select(p => Path.Combine(packageFolder, p.ToLowerInvariant()));
176290

177291
foreach (var filename in usedReferences.Keys)
178292
{
179293
var lowerFilename = filename.ToLowerInvariant();
180294

181-
if (runtimePackageNamePrefixes.Any(prefix => lowerFilename.StartsWith(prefix)))
295+
if (packagePathPrefixes.Any(prefix => lowerFilename.StartsWith(prefix)))
182296
{
183297
usedReferences.Remove(filename);
184298
progressMonitor.RemovedReference(filename);
185299
}
186300
}
187301
}
188302

303+
private bool IsNugetPackageAvailable(string packagePrefix)
304+
{
305+
if (!options.UseNuGet)
306+
{
307+
return false;
308+
}
309+
310+
return new DirectoryInfo(packageDirectory.DirInfo.FullName)
311+
.EnumerateDirectories(packagePrefix + "*", new EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive, RecurseSubdirectories = false })
312+
.Any();
313+
}
314+
189315
private void GenerateSourceFileFromImplicitUsings()
190316
{
191317
var usings = new HashSet<string>();
@@ -198,7 +324,7 @@ private void GenerateSourceFileFromImplicitUsings()
198324
usings.UnionWith(new[] { "System", "System.Collections.Generic", "System.IO", "System.Linq", "System.Net.Http", "System.Threading",
199325
"System.Threading.Tasks" });
200326

201-
if (fileContent.UseAspNetDlls)
327+
if (fileContent.UseAspNetCoreDlls)
202328
{
203329
usings.UnionWith(new[] { "System.Net.Http.Json", "Microsoft.AspNetCore.Builder", "Microsoft.AspNetCore.Hosting",
204330
"Microsoft.AspNetCore.Http", "Microsoft.AspNetCore.Routing", "Microsoft.Extensions.Configuration",
@@ -461,11 +587,11 @@ private void AnalyseProject(FileInfo project)
461587

462588
}
463589

464-
private bool RestoreProject(string project, string? pathToNugetConfig = null) =>
465-
dotnet.RestoreProjectToDirectory(project, packageDirectory.DirInfo.FullName, pathToNugetConfig);
590+
private bool RestoreProject(string project, bool forceDotnetRefAssemblyFetching, string? pathToNugetConfig = null) =>
591+
dotnet.RestoreProjectToDirectory(project, packageDirectory.DirInfo.FullName, forceDotnetRefAssemblyFetching, pathToNugetConfig);
466592

467593
private bool RestoreSolution(string solution, out IEnumerable<string> projects) =>
468-
dotnet.RestoreSolutionToDirectory(solution, packageDirectory.DirInfo.FullName, out projects);
594+
dotnet.RestoreSolutionToDirectory(solution, packageDirectory.DirInfo.FullName, forceDotnetRefAssemblyFetching: true, out projects);
469595

470596
/// <summary>
471597
/// Executes `dotnet restore` on all solution files in solutions.
@@ -491,7 +617,7 @@ private void RestoreProjects(IEnumerable<string> projects)
491617
{
492618
Parallel.ForEach(projects, new ParallelOptions { MaxDegreeOfParallelism = options.Threads }, project =>
493619
{
494-
RestoreProject(project);
620+
RestoreProject(project, forceDotnetRefAssemblyFetching: true);
495621
});
496622
}
497623

@@ -536,7 +662,7 @@ private void DownloadMissingPackages(List<FileInfo> allFiles)
536662
return;
537663
}
538664

539-
success = RestoreProject(tempDir.DirInfo.FullName, nugetConfig);
665+
success = RestoreProject(tempDir.DirInfo.FullName, forceDotnetRefAssemblyFetching: false, pathToNugetConfig: nugetConfig);
540666
// TODO: the restore might fail, we could retry with a prerelease (*-* instead of *) version of the package.
541667
if (!success)
542668
{
@@ -564,9 +690,25 @@ private void AnalyseSolutions(IEnumerable<string> solutions)
564690

565691
public void Dispose()
566692
{
567-
packageDirectory?.Dispose();
693+
try
694+
{
695+
packageDirectory?.Dispose();
696+
}
697+
catch (Exception exc)
698+
{
699+
progressMonitor.LogInfo("Couldn't delete package directory: " + exc.Message);
700+
}
568701
if (cleanupTempWorkingDirectory)
569-
tempWorkingDirectory?.Dispose();
702+
{
703+
try
704+
{
705+
tempWorkingDirectory?.Dispose();
706+
}
707+
catch (Exception exc)
708+
{
709+
progressMonitor.LogInfo("Couldn't delete temporary working directory: " + exc.Message);
710+
}
711+
}
570712
}
571713
}
572714
}

0 commit comments

Comments
 (0)