From 3ac0f78bed7db021a1b3f4b6663e700994f1266f Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Thu, 13 Aug 2026 11:49:49 +0200 Subject: [PATCH 1/4] C#: Remove the explicit feed timeout fallback. --- .../NugetPackageRestorer.cs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs index aeedbc176867..9ff4760ed396 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs @@ -129,17 +129,6 @@ public HashSet Restore() var allExplicitReachable = explicitFeeds.Count == feedManager.ReachableExplicitFeeds.Count; EmitUnreachableFeedsDiagnostics(allExplicitReachable); - - if (feedManager.ExplicitFeedTimeout) - { - // If we experience a timeout, we use this fallback. - // todo: we could also check the reachability of the inherited nuget feeds, but to use those in the fallback we would need to handle authentication too. - var unresponsiveMissingPackageLocation = DownloadMissingPackages([]); - return unresponsiveMissingPackageLocation is null - ? [] - : [unresponsiveMissingPackageLocation]; - } - } try From 79c272ea15b0cc76a718893540a43b2d2839daf2 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Thu, 13 Aug 2026 12:44:27 +0200 Subject: [PATCH 2/4] C#: Remove the timeout bool logic in the feed manager. --- .../FeedManager.cs | 52 +++++-------------- 1 file changed, 13 insertions(+), 39 deletions(-) diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/FeedManager.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/FeedManager.cs index 1d5523a983cb..0cbf106df38a 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/FeedManager.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/FeedManager.cs @@ -60,17 +60,12 @@ internal sealed partial class FeedManager : IDisposable /// public ImmutableHashSet InheritedFeeds => AllFeeds.Except(ExplicitFeeds).ToImmutableHashSet(); - private readonly Lazy<(bool, ImmutableHashSet)> lazyReachableExplicitFeeds; - - /// - /// Gets whether there was a timeout when checking the reachability of the explicitly configured NuGet feeds. - /// - public bool ExplicitFeedTimeout => lazyReachableExplicitFeeds.Value.Item1; + private readonly Lazy> lazyReachableExplicitFeeds; /// /// Gets the list of reachable NuGet feeds that are explicitly configured. /// - public ImmutableHashSet ReachableExplicitFeeds => lazyReachableExplicitFeeds.Value.Item2; + public ImmutableHashSet ReachableExplicitFeeds => lazyReachableExplicitFeeds.Value; private readonly Lazy> lazyReachableFeeds; /// @@ -96,15 +91,11 @@ public FeedManager(ILogger logger, IDotNet dotnet, DependabotProxy? dependabotPr lazyExplicitFeeds = new Lazy>(GetExplicitFeeds); lazyAllFeeds = new Lazy>(GetAllFeeds); - lazyReachableExplicitFeeds = new Lazy<(bool, ImmutableHashSet)>(() => - { - var timeout = CheckSpecifiedFeeds(ExplicitFeeds, out var reachableFeeds); - return (timeout, reachableFeeds); - }); + lazyReachableExplicitFeeds = new Lazy>(() => CheckSpecifiedFeeds(ExplicitFeeds)); lazyReachableFeeds = new Lazy>(() => { // Inherited feeds should only be used, if they are indeed reachable (as they may be environment specific). - CheckSpecifiedFeeds(InheritedFeeds, out var reachableInheritedFeeds); + var reachableInheritedFeeds = CheckSpecifiedFeeds(InheritedFeeds); return ReachableExplicitFeeds.Union(reachableInheritedFeeds).ToImmutableHashSet(); }); lazyReachableFallbackFeeds = new Lazy>(() => @@ -271,7 +262,7 @@ private static async Task ExecuteGetRequest(string address, return await httpClient.GetAsync(address, HttpCompletionOption.ResponseHeadersRead, cancellationToken); } - private bool IsFeedReachable(string feed, int timeoutMilliSeconds, int tryCount, out bool isTimeout) + private bool IsFeedReachable(string feed, int timeoutMilliSeconds, int tryCount) { logger.LogInfo($"Checking if NuGet feed '{feed}' is reachable..."); @@ -304,8 +295,6 @@ private bool IsFeedReachable(string feed, int timeoutMilliSeconds, int tryCount, using HttpClient client = new(httpClientHandler); - isTimeout = false; - for (var i = 0; i < tryCount; i++) { using var cts = new CancellationTokenSource(); @@ -335,7 +324,6 @@ private bool IsFeedReachable(string feed, int timeoutMilliSeconds, int tryCount, } logger.LogWarning($"Didn't receive answer from NuGet feed '{feed}'. Tried it {tryCount} times."); - isTimeout = true; return false; } @@ -359,12 +347,8 @@ private HashSet GetExcludedFeeds() /// Checks that we can connect to the specified NuGet feeds. /// /// The set of package feeds to check. - /// The list of feeds that were reachable. - /// - /// True if there is a timeout when trying to reach the feeds (excluding any feeds that are configured - /// to be excluded from the check) or false otherwise. - /// - private bool CheckSpecifiedFeeds(ImmutableHashSet feeds, out ImmutableHashSet reachableFeeds) + /// The list of feeds that were reachable. + private ImmutableHashSet CheckSpecifiedFeeds(ImmutableHashSet feeds) { // Exclude any feeds from the feed check that are configured by the corresponding environment variable. // These feeds are always assumed to be reachable. @@ -380,12 +364,10 @@ private bool CheckSpecifiedFeeds(ImmutableHashSet feeds, out ImmutableHa return true; }).ToHashSet(); - var reachable = GetReachableNuGetFeeds(feedsToCheck, isFallback: false, out var isTimeout); + var reachable = GetReachableNuGetFeeds(feedsToCheck, isFallback: false); // Always consider feeds excluded for the reachability check as reachable. - reachableFeeds = reachable.Union(feeds.Where(feed => excludedFeeds.Contains(feed))).ToImmutableHashSet(); - - return isTimeout; + return reachable.Union(feeds.Where(feed => excludedFeeds.Contains(feed))).ToImmutableHashSet(); } /// @@ -398,7 +380,7 @@ public bool IsDefaultFeedReachable() if (CheckNugetFeedResponsiveness) { var (initialTimeout, tryCount) = GetFeedRequestSettings(isFallback: false); - return IsFeedReachable(PublicNugetOrgFeed, initialTimeout, tryCount, out var _); + return IsFeedReachable(PublicNugetOrgFeed, initialTimeout, tryCount); } return true; @@ -409,22 +391,15 @@ public bool IsDefaultFeedReachable() /// /// The feeds to check. /// Whether the feeds are fallback feeds or not. - /// Whether a timeout occurred while checking the feeds. /// The list of feeds that could be reached. - private List GetReachableNuGetFeeds(HashSet feedsToCheck, bool isFallback, out bool isTimeout) + private List GetReachableNuGetFeeds(HashSet feedsToCheck, bool isFallback) { var fallbackStr = isFallback ? "fallback " : ""; logger.LogInfo($"Checking {fallbackStr}NuGet feed reachability on feeds: {string.Join(", ", feedsToCheck.OrderBy(f => f))}"); var (initialTimeout, tryCount) = GetFeedRequestSettings(isFallback); - var timeout = false; var reachableFeeds = feedsToCheck - .Where(feed => - { - var reachable = IsFeedReachable(feed, initialTimeout, tryCount, out var feedTimeout); - timeout |= feedTimeout; - return reachable; - }) + .Where(feed => IsFeedReachable(feed, initialTimeout, tryCount)) .ToList(); if (reachableFeeds.Count == 0) @@ -436,7 +411,6 @@ private List GetReachableNuGetFeeds(HashSet feedsToCheck, bool i logger.LogInfo($"Reachable {fallbackStr}NuGet feeds: {string.Join(", ", reachableFeeds.OrderBy(f => f))}"); } - isTimeout = timeout; return reachableFeeds; } @@ -460,7 +434,7 @@ private List GetReachableFallbackNugetFeeds() } } - return GetReachableNuGetFeeds(fallbackFeeds, isFallback: true, out var _); + return GetReachableNuGetFeeds(fallbackFeeds, isFallback: true); } private ImmutableHashSet GetExplicitFeeds() From f6c6322315cd28ec5897f56f6e45cb89cee28e99 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Thu, 13 Aug 2026 15:25:16 +0200 Subject: [PATCH 3/4] C#: Update integration tests. --- .../CompilationInfo.expected | 7 +++++++ .../CompilationInfo.expected | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/csharp/ql/integration-tests/posix/standalone_dependencies_nuget_config_error_timeout/CompilationInfo.expected b/csharp/ql/integration-tests/posix/standalone_dependencies_nuget_config_error_timeout/CompilationInfo.expected index 5a7abcf543c8..4acd4f54e8a6 100644 --- a/csharp/ql/integration-tests/posix/standalone_dependencies_nuget_config_error_timeout/CompilationInfo.expected +++ b/csharp/ql/integration-tests/posix/standalone_dependencies_nuget_config_error_timeout/CompilationInfo.expected @@ -1,4 +1,8 @@ | All NuGet feeds reachable | 0.0 | +| Failed project restore with missing package error | 1.0 | +| Failed project restore with package source error | 0.0 | +| Failed solution restore with missing package error | 0.0 | +| Failed solution restore with package source error | 0.0 | | Fallback nuget restore | 1.0 | | Inherited NuGet feed count | 1.0 | | NuGet feed responsiveness checked | 1.0 | @@ -7,10 +11,13 @@ | Resolved assembly conflicts | 7.0 | | Resource extraction enabled | 0.0 | | Restored .NET framework variants | 0.0 | +| Restored projects through solution files | 0.0 | | Solution files on filesystem | 1.0 | | Source files generated | 0.0 | | Source files on filesystem | 1.0 | | Successfully ran fallback nuget restore | 1.0 | +| Successfully restored project files | 0.0 | +| Successfully restored solution files | 1.0 | | Unresolved references | 0.0 | | UseWPF set | 0.0 | | UseWindowsForms set | 0.0 | diff --git a/csharp/ql/integration-tests/posix/standalone_dependencies_nuget_config_fallback/CompilationInfo.expected b/csharp/ql/integration-tests/posix/standalone_dependencies_nuget_config_fallback/CompilationInfo.expected index 9cc03f2f5372..d421cfc42e1a 100644 --- a/csharp/ql/integration-tests/posix/standalone_dependencies_nuget_config_fallback/CompilationInfo.expected +++ b/csharp/ql/integration-tests/posix/standalone_dependencies_nuget_config_fallback/CompilationInfo.expected @@ -1,4 +1,8 @@ | All NuGet feeds reachable | 0.0 | +| Failed project restore with missing package error | 1.0 | +| Failed project restore with package source error | 0.0 | +| Failed solution restore with missing package error | 0.0 | +| Failed solution restore with package source error | 0.0 | | Fallback nuget restore | 1.0 | | Inherited NuGet feed count | 1.0 | | NuGet feed responsiveness checked | 1.0 | @@ -7,10 +11,13 @@ | Resolved assembly conflicts | 7.0 | | Resource extraction enabled | 0.0 | | Restored .NET framework variants | 0.0 | +| Restored projects through solution files | 0.0 | | Solution files on filesystem | 1.0 | | Source files generated | 0.0 | | Source files on filesystem | 1.0 | | Successfully ran fallback nuget restore | 1.0 | +| Successfully restored project files | 0.0 | +| Successfully restored solution files | 1.0 | | Unresolved references | 0.0 | | UseWPF set | 0.0 | | UseWindowsForms set | 0.0 | From 30671019b325099782311096b653a757e7373f68 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Wed, 19 Aug 2026 10:07:45 +0200 Subject: [PATCH 4/4] C#: Use ArgumentList instead of Arguments for ProcessStartInfo when invoking dotnet in the dependency fetcher. --- .../DotNet.cs | 41 ++++++++-------- .../DotNetCliInvoker.cs | 20 ++++---- .../FeedManager.cs | 22 ++++----- .../IDotNet.cs | 4 +- .../IDotNetCliInvoker.cs | 8 ++-- .../NugetPackageRestorer.cs | 14 +++--- .../PackagesConfigRestorer.cs | 10 ++-- .../DotnetSourceGeneratorWrapper.cs | 2 +- .../Semmle.Extraction.Tests/DotNet.cs | 48 +++++++++---------- .../Semmle.Extraction.Tests/Runtime.cs | 2 +- 10 files changed, 82 insertions(+), 89 deletions(-) diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/DotNet.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/DotNet.cs index e02d157dc620..39fc007ee98b 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/DotNet.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/DotNet.cs @@ -49,7 +49,7 @@ private void Info() // Allow up to four attempts (with up to three retries) to run `dotnet --info`, to mitigate transient issues for (int attempt = 0; attempt < 4; attempt++) { - var exitCode = dotnetCliInvoker.RunCommandExitCode("--info", silent: false); + var exitCode = dotnetCliInvoker.RunCommandExitCode(["--info"], silent: false); switch (exitCode) { case 0: @@ -63,9 +63,9 @@ private void Info() } } - private string GetRestoreArgs(RestoreSettings restoreSettings) + private List GetRestoreArgs(RestoreSettings restoreSettings) { - var args = $"restore --no-dependencies \"{restoreSettings.File}\" --packages \"{restoreSettings.PackageDirectory}\" /p:DisableImplicitNuGetFallbackFolder=true --verbosity normal"; + List args = ["restore", "--no-dependencies", restoreSettings.File, "--packages", restoreSettings.PackageDirectory, "/p:DisableImplicitNuGetFallbackFolder=true", "--verbosity", "normal"]; if (restoreSettings.ForceDotnetRefAssemblyFetching) { @@ -77,23 +77,20 @@ private string GetRestoreArgs(RestoreSettings restoreSettings) Directory.CreateDirectory(path); } - args += $" /p:TargetFrameworkRootPath=\"{path}\" /p:NetCoreTargetingPackRoot=\"{path}\" /p:AllowMissingPrunePackageData=true"; + args.AddRange([$"/p:TargetFrameworkRootPath={path}", $"/p:NetCoreTargetingPackRoot={path}", "/p:AllowMissingPrunePackageData=true"]); } if (restoreSettings.ForceReevaluation) { - args += " --force"; + args.Add("--force"); } if (restoreSettings.TargetWindows) { - args += " /p:EnableWindowsTargeting=true"; + args.Add("/p:EnableWindowsTargeting=true"); } - if (restoreSettings.NugetSources is not null) - { - args += $" {restoreSettings.NugetSources}"; - } + args.AddRange(restoreSettings.NugetSources); return args; } @@ -107,48 +104,48 @@ public RestoreResult Restore(RestoreSettings restoreSettings) public bool New(string folder) { - var args = $"new console --no-restore --output \"{folder}\""; + List args = ["new", "console", "--no-restore", "--output", folder]; return dotnetCliInvoker.RunCommand(args); } public bool AddPackage(string folder, string package) { - var args = $"add \"{folder}\" package \"{package}\" --no-restore"; + List args = ["add", folder, "package", package, "--no-restore"]; return dotnetCliInvoker.RunCommand(args); } - public IList GetListedRuntimes() => GetResultList("--list-runtimes"); + public IList GetListedRuntimes() => GetResultList(["--list-runtimes"]); - public IList GetListedSdks() => GetResultList("--list-sdks"); + public IList GetListedSdks() => GetResultList(["--list-sdks"]); - private IList GetResultList(string args, string? workingDirectory = null, bool silent = true) + private IList GetResultList(List args, string? workingDirectory = null, bool silent = true) { if (dotnetCliInvoker.RunCommand(args, workingDirectory, out var results, silent)) { return results; } - logger.LogWarning($"Running 'dotnet {args}' failed."); - return []; + logger.LogWarning($"Running 'dotnet {string.Join(" ", args)}' failed."); + return new List(); } - public bool Exec(string execArgs) + public bool Exec(List execArgs) { - var args = $"exec {execArgs}"; + List args = ["exec", .. execArgs]; return dotnetCliInvoker.RunCommand(args); } - private const string nugetListSourceCommand = "nuget list source --format Short"; + private static readonly IReadOnlyList nugetListSourceCommandArgs = ["nuget", "list", "source", "--format", "Short"]; public IList GetNugetFeeds(string nugetConfig) { logger.LogInfo($"Getting NuGet feeds from '{nugetConfig}'..."); - return GetResultList($"{nugetListSourceCommand} --configfile \"{nugetConfig}\""); + return GetResultList([.. nugetListSourceCommandArgs, "--configfile", nugetConfig]); } public IList GetNugetFeedsFromFolder(string folderPath) { logger.LogInfo($"Getting NuGet feeds in folder '{folderPath}'..."); - return GetResultList(nugetListSourceCommand, folderPath); + return GetResultList(nugetListSourceCommandArgs.ToList(), folderPath); } // The version number should be kept in sync with the version .NET version used for building the application. diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/DotNetCliInvoker.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/DotNetCliInvoker.cs index 4c4e789973ca..0f022b188c6f 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/DotNetCliInvoker.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/DotNetCliInvoker.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Diagnostics; using Semmle.Util; using Semmle.Util.Logging; @@ -25,7 +24,7 @@ public DotNetCliInvoker(ILogger logger, string exec, DependabotProxy? dependabot logger.LogInfo($"Using .NET CLI executable: '{Exec}'"); } - private ProcessStartInfo MakeDotnetStartInfo(string args, string? workingDirectory) + private ProcessStartInfo MakeDotnetStartInfo(List args, string? workingDirectory) { var startInfo = new ProcessStartInfo(Exec, args) { @@ -33,6 +32,7 @@ private ProcessStartInfo MakeDotnetStartInfo(string args, string? workingDirecto RedirectStandardOutput = true, RedirectStandardError = true }; + if (!string.IsNullOrWhiteSpace(workingDirectory)) { startInfo.WorkingDirectory = workingDirectory; @@ -57,39 +57,39 @@ private ProcessStartInfo MakeDotnetStartInfo(string args, string? workingDirecto return startInfo; } - private int RunCommandExitCodeAux(string args, string? workingDirectory, out IList output, out string dirLog, bool silent) + private int RunCommandExitCodeAux(List args, string? workingDirectory, out IList output, out string dirLog, bool silent) { dirLog = string.IsNullOrWhiteSpace(workingDirectory) ? "" : $" in {workingDirectory}"; var pi = MakeDotnetStartInfo(args, workingDirectory); var threadId = Environment.CurrentManagedThreadId; void onOut(string s) => logger.Log(silent ? Severity.Debug : Severity.Info, s, threadId); void onError(string s) => logger.LogError(s, threadId); - logger.LogInfo($"Running '{Exec} {args}'{dirLog}"); + logger.LogInfo($"Running '{Exec} {string.Join(" ", args)}'{dirLog}"); var exitCode = pi.ReadOutput(out output, onOut, onError); return exitCode; } - private bool RunCommandAux(string args, string? workingDirectory, out IList output, bool silent) + private bool RunCommandAux(List args, string? workingDirectory, out IList output, bool silent) { var exitCode = RunCommandExitCodeAux(args, workingDirectory, out output, out var dirLog, silent); if (exitCode != 0) { - logger.LogError($"Command '{Exec} {args}'{dirLog} failed with exit code {exitCode}"); + logger.LogError($"Command '{Exec} {string.Join(" ", args)}'{dirLog} failed with exit code {exitCode}"); return false; } return true; } - public bool RunCommand(string args, bool silent = true) => + public bool RunCommand(List args, bool silent = true) => RunCommandAux(args, null, out _, silent); - public int RunCommandExitCode(string args, bool silent = true) => + public int RunCommandExitCode(List args, bool silent = true) => RunCommandExitCodeAux(args, null, out _, out _, silent); - public bool RunCommand(string args, out IList output, bool silent = true) => + public bool RunCommand(List args, out IList output, bool silent = true) => RunCommandAux(args, null, out output, silent); - public bool RunCommand(string args, string? workingDirectory, out IList output, bool silent = true) => + public bool RunCommand(List args, string? workingDirectory, out IList output, bool silent = true) => RunCommandAux(args, workingDirectory, out output, silent); } } diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/FeedManager.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/FeedManager.cs index 0cbf106df38a..0086c26acbaf 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/FeedManager.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/FeedManager.cs @@ -158,24 +158,18 @@ private IEnumerable GetFeedsFromNugetConfig(string nugetConfigPath) => /// /// The list of feeds to use for the restore command. /// The prefix to use for each source argument (e.g., "-s"). - /// The constructed NuGet sources argument for the restore command. - public string FeedsToRestoreArgument(IEnumerable feeds, string sourceArgumentPrefix) + /// The list of NuGet sources arguments for the restore command. + public List FeedsToRestoreArgument(IEnumerable feeds, string sourceArgumentPrefix) { // If there are no feeds, we want to override any default feeds that `restore` would use by passing a dummy source argument. if (!feeds.Any()) { - return $" {sourceArgumentPrefix} \"{emptyPackageDirectory.DirInfo.FullName}\""; + return [sourceArgumentPrefix, emptyPackageDirectory.DirInfo.FullName]; } // Add package sources. If any are present, they override all sources specified in // the configuration file(s). - var feedArgs = new StringBuilder(); - foreach (var feed in feeds) - { - feedArgs.Append($" {sourceArgumentPrefix} \"{feed}\""); - } - - return feedArgs.ToString(); + return feeds.SelectMany(feed => [sourceArgumentPrefix, feed]).ToList(); } private IEnumerable FeedsToUseAux(HashSet feedsToConsider) @@ -212,8 +206,8 @@ public IEnumerable FeedsToUse(string path) /// Constructs the NuGet sources argument for the `dotnet restore` command based on the given feeds. /// /// The list of NuGet feeds to use for the restore command. - /// A string representing the NuGet sources argument for the `dotnet restore` command. - public string FeedsToDotnetRestoreArgument(IEnumerable feeds) + /// A list representing the NuGet sources arguments for the `dotnet restore` command. + public List FeedsToDotnetRestoreArgument(IEnumerable feeds) { return FeedsToRestoreArgument(feeds, "-s"); } @@ -225,12 +219,12 @@ public string FeedsToDotnetRestoreArgument(IEnumerable feeds) /// /// Path to project/solution /// A string representing the NuGet sources argument for the `dotnet restore` command. - public string? MakeDotnetRestoreSourcesArgument(string path) + public List MakeDotnetRestoreSourcesArguments(string path) { // Do not construct a set of explicit NuGet sources to use for restore. if (!CheckNugetFeedResponsiveness && !HasPrivateRegistryFeeds) { - return null; + return []; } var feedsToUse = FeedsToUse(path); diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/IDotNet.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/IDotNet.cs index 394a05e9e596..0e93fa92813a 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/IDotNet.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/IDotNet.cs @@ -12,12 +12,12 @@ public interface IDotNet bool AddPackage(string folder, string package); IList GetListedRuntimes(); IList GetListedSdks(); - bool Exec(string execArgs); + bool Exec(List execArgs); IList GetNugetFeeds(string nugetConfig); IList GetNugetFeedsFromFolder(string folderPath); } - public record class RestoreSettings(string File, string PackageDirectory, bool ForceDotnetRefAssemblyFetching, string? NugetSources = null, bool ForceReevaluation = false, bool TargetWindows = false); + public record class RestoreSettings(string File, string PackageDirectory, bool ForceDotnetRefAssemblyFetching, List NugetSources, bool ForceReevaluation = false, bool TargetWindows = false); public partial record class RestoreResult(bool Success, IList Output) { diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/IDotNetCliInvoker.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/IDotNetCliInvoker.cs index ef5bcd4753bb..b5400ec63319 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/IDotNetCliInvoker.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/IDotNetCliInvoker.cs @@ -30,26 +30,26 @@ internal interface IDotNetCliInvoker /// Execute `dotnet ` and return true if the command succeeded, otherwise false. /// If `silent` is true the output of the command is logged as `debug` otherwise as `info`. /// - bool RunCommand(string args, bool silent = true); + bool RunCommand(List args, bool silent = true); /// /// Execute `dotnet ` and return the exit code. /// If `silent` is true the output of the command is logged as `debug` otherwise as `info`. /// - int RunCommandExitCode(string args, bool silent = true); + int RunCommandExitCode(List args, bool silent = true); /// /// Execute `dotnet ` and return true if the command succeeded, otherwise false. /// The output of the command is returned in `output`. /// If `silent` is true the output of the command is logged as `debug` otherwise as `info`. /// - bool RunCommand(string args, out IList output, bool silent = true); + bool RunCommand(List args, out IList output, bool silent = true); /// /// Execute `dotnet ` in `` and return true if the command succeeded, otherwise false. /// The output of the command is returned in `output`. /// If `silent` is true the output of the command is logged as `debug` otherwise as `info`. /// - bool RunCommand(string args, string? workingDirectory, out IList output, bool silent = true); + bool RunCommand(List args, string? workingDirectory, out IList output, bool silent = true); } } diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs index 9ff4760ed396..fac49018dd27 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs @@ -216,7 +216,7 @@ private IEnumerable RestoreSolutions(out DependencyContainer dependencie var projects = fileProvider.Solutions.SelectMany(solution => { logger.LogInfo($"Restoring solution {solution}..."); - var nugetSources = feedManager.MakeDotnetRestoreSourcesArgument(solution); + var nugetSources = feedManager.MakeDotnetRestoreSourcesArguments(solution); var res = dotnet.Restore(new(solution, PackageDirectory.DirInfo.FullName, ForceDotnetRefAssemblyFetching: true, NugetSources: nugetSources, TargetWindows: isWindows)); if (res.Success) { @@ -264,7 +264,7 @@ private void RestoreProjects(IEnumerable projects, out ConcurrentBag GetRestoredPackageDirectoryNames(DirectoryInf .Select(d => Path.GetFileName(d).ToLowerInvariant()); } - private bool TryRestorePackageManually(string package, string? nugetSources, PackageReferenceSource packageReferenceSource = PackageReferenceSource.SdkCsProj, bool tryPrereleaseVersion = true) + private bool TryRestorePackageManually(string package, List nugetSources, PackageReferenceSource packageReferenceSource = PackageReferenceSource.SdkCsProj, bool tryPrereleaseVersion = true) { logger.LogInfo($"Restoring package {package}..."); using var tempDir = new TemporaryDirectory( @@ -464,7 +464,7 @@ private bool TryRestorePackageManually(string package, string? nugetSources, Pac { logger.LogDebug($"Trying to restore '{package}' without explicitly providing NuGet sources."); // Restore could not be completed because the listed source is unavailable. Try without an explicit restore source argument. - res = TryRestorePackageManually(package, nugetSources: null, tempDir, tryPrereleaseVersion); + res = TryRestorePackageManually(package, [], tempDir, tryPrereleaseVersion); if (res.Success) { return true; @@ -475,16 +475,16 @@ private bool TryRestorePackageManually(string package, string? nugetSources, Pac return false; } - private RestoreResult TryRestorePackageManually(string package, string? nugetSources, TemporaryDirectory tempDir, bool tryPrereleaseVersion) + private RestoreResult TryRestorePackageManually(string package, List nugetSources, TemporaryDirectory tempDir, bool tryPrereleaseVersion) { - var res = dotnet.Restore(new(tempDir.DirInfo.FullName, missingPackageDirectory.DirInfo.FullName, ForceDotnetRefAssemblyFetching: false, NugetSources: nugetSources, ForceReevaluation: true)); + var res = dotnet.Restore(new(tempDir.DirInfo.FullName, missingPackageDirectory.DirInfo.FullName, ForceDotnetRefAssemblyFetching: false, nugetSources, ForceReevaluation: true)); if (!res.Success && tryPrereleaseVersion && res.HasNugetNoStablePackageVersionError) { logger.LogDebug($"Failed to restore nuget package {package} because no stable version was found."); TryChangePackageVersion(tempDir.DirInfo, "*-*"); - res = dotnet.Restore(new(tempDir.DirInfo.FullName, missingPackageDirectory.DirInfo.FullName, ForceDotnetRefAssemblyFetching: false, NugetSources: nugetSources, ForceReevaluation: true)); + res = dotnet.Restore(new(tempDir.DirInfo.FullName, missingPackageDirectory.DirInfo.FullName, ForceDotnetRefAssemblyFetching: false, nugetSources, ForceReevaluation: true)); if (!res.Success) { TryChangePackageVersion(tempDir.DirInfo, "*"); diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/PackagesConfigRestorer.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/PackagesConfigRestorer.cs index 05c10cfb2bf3..305897ed926e 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/PackagesConfigRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/PackagesConfigRestorer.cs @@ -168,7 +168,7 @@ private bool TryRestoreNugetPackage(string packagesConfig) { logger.LogInfo($"Restoring file \"{packagesConfig}\"..."); - var sourcesArgument = ""; + List sourcesArgument = []; var feedsToUse = feedManager.FeedsToUse(packagesConfig).ToList(); var useDefaultFeed = feedsToUse.Count == 0 && IsDefaultFeedReachable; @@ -189,16 +189,18 @@ private bool TryRestoreNugetPackage(string packagesConfig) * really unwieldy and this solution works for now. */ - string exe, args; + string exe; + List args; + if (RunWithMono) { exe = "mono"; - args = $"\"{nugetExe}\" install -OutputDirectory \"{packageDirectory}\" {sourcesArgument} \"{packagesConfig}\""; + args = [nugetExe!, "install", "-OutputDirectory", packageDirectory.ToString(), .. sourcesArgument, packagesConfig]; } else { exe = nugetExe!; - args = $"install -OutputDirectory \"{packageDirectory}\" {sourcesArgument} \"{packagesConfig}\""; + args = ["install", "-OutputDirectory", packageDirectory.ToString(), .. sourcesArgument, packagesConfig]; } var pi = new ProcessStartInfo(exe, args) diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/SourceGenerators/DotnetSourceGeneratorWrapper/DotnetSourceGeneratorWrapper.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/SourceGenerators/DotnetSourceGeneratorWrapper/DotnetSourceGeneratorWrapper.cs index 9518afac4008..8ecf13d53a6a 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/SourceGenerators/DotnetSourceGeneratorWrapper/DotnetSourceGeneratorWrapper.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/SourceGenerators/DotnetSourceGeneratorWrapper/DotnetSourceGeneratorWrapper.cs @@ -79,7 +79,7 @@ public IEnumerable RunSourceGenerator(IEnumerable additionalFile sw.Write(argsString); } - dotnet.Exec($"\"{cscPath}\" /noconfig @\"{cscArgsPath}\""); + dotnet.Exec([cscPath, "/noconfig", $"@{cscArgsPath}"]); var files = Directory.GetFiles(outputFolder, "*.*", new EnumerationOptions { RecurseSubdirectories = true }); diff --git a/csharp/extractor/Semmle.Extraction.Tests/DotNet.cs b/csharp/extractor/Semmle.Extraction.Tests/DotNet.cs index 74939b61af5e..c9d3bc49a3b5 100644 --- a/csharp/extractor/Semmle.Extraction.Tests/DotNet.cs +++ b/csharp/extractor/Semmle.Extraction.Tests/DotNet.cs @@ -8,45 +8,45 @@ namespace Semmle.Extraction.Tests { internal class DotNetCliInvokerStub : IDotNetCliInvoker { - private readonly IList output; - private string lastArgs = ""; + private readonly IList returnOutput; + private List lastArgs = new List(); public string WorkingDirectory { get; private set; } = ""; public bool Success { get; set; } = true; public int ExitCode { get; set; } = 0; - public DotNetCliInvokerStub(IList output) + public DotNetCliInvokerStub(IList returnOutput) { - this.output = output; + this.returnOutput = returnOutput; } public string Exec => "dotnet"; - public bool RunCommand(string args, bool silent) + public bool RunCommand(List args, bool silent) { lastArgs = args; return Success; } - public int RunCommandExitCode(string args, bool silent) + public int RunCommandExitCode(List args, bool silent) { lastArgs = args; return ExitCode; } - public bool RunCommand(string args, out IList output, bool silent) + public bool RunCommand(List args, out IList output, bool silent) { lastArgs = args; - output = this.output; + output = this.returnOutput; return Success; } - public bool RunCommand(string args, string? workingDirectory, out IList output, bool silent) + public bool RunCommand(List args, string? workingDirectory, out IList output, bool silent) { WorkingDirectory = workingDirectory ?? ""; return RunCommand(args, out output, silent); } - public string GetLastArgs() => lastArgs; + public string GetLastArgs() => string.Join(" ", lastArgs); } public class DotNetTests @@ -115,11 +115,11 @@ public void TestDotnetRestoreProjectToDirectory1() var dotnet = MakeDotnet(dotnetCliInvoker); // Execute - dotnet.Restore(new("myproject.csproj", "mypackages", false)); + dotnet.Restore(new("myproject.csproj", "mypackages", false, [])); // Verify var lastArgs = dotnetCliInvoker.GetLastArgs(); - Assert.Equal("restore --no-dependencies \"myproject.csproj\" --packages \"mypackages\" /p:DisableImplicitNuGetFallbackFolder=true --verbosity normal", lastArgs); + Assert.Equal("restore --no-dependencies myproject.csproj --packages mypackages /p:DisableImplicitNuGetFallbackFolder=true --verbosity normal", lastArgs); } [Fact] @@ -130,11 +130,11 @@ public void TestDotnetRestoreProjectToDirectory2() var dotnet = MakeDotnet(dotnetCliInvoker); // Execute - var res = dotnet.Restore(new("myproject.csproj", "mypackages", false, null)); + var res = dotnet.Restore(new("myproject.csproj", "mypackages", false, [])); // Verify var lastArgs = dotnetCliInvoker.GetLastArgs(); - Assert.Equal("restore --no-dependencies \"myproject.csproj\" --packages \"mypackages\" /p:DisableImplicitNuGetFallbackFolder=true --verbosity normal", lastArgs); + Assert.Equal("restore --no-dependencies myproject.csproj --packages mypackages /p:DisableImplicitNuGetFallbackFolder=true --verbosity normal", lastArgs); Assert.Equal(2, res.AssetsFilePaths.Count()); Assert.Contains("/path/to/project.assets.json", res.AssetsFilePaths); Assert.Contains("/path/to/project2.assets.json", res.AssetsFilePaths); @@ -148,11 +148,11 @@ public void TestDotnetRestoreProjectToDirectory3() var dotnet = MakeDotnet(dotnetCliInvoker); // Execute - var res = dotnet.Restore(new("myproject.csproj", "mypackages", false, null, true)); + var res = dotnet.Restore(new("myproject.csproj", "mypackages", false, [], true)); // Verify var lastArgs = dotnetCliInvoker.GetLastArgs(); - Assert.Equal("restore --no-dependencies \"myproject.csproj\" --packages \"mypackages\" /p:DisableImplicitNuGetFallbackFolder=true --verbosity normal --force", lastArgs); + Assert.Equal("restore --no-dependencies myproject.csproj --packages mypackages /p:DisableImplicitNuGetFallbackFolder=true --verbosity normal --force", lastArgs); Assert.Equal(2, res.AssetsFilePaths.Count()); Assert.Contains("/path/to/project.assets.json", res.AssetsFilePaths); Assert.Contains("/path/to/project2.assets.json", res.AssetsFilePaths); @@ -166,11 +166,11 @@ public void TestDotnetRestoreSolutionToDirectory1() var dotnet = MakeDotnet(dotnetCliInvoker); // Execute - var res = dotnet.Restore(new("mysolution.sln", "mypackages", false)); + var res = dotnet.Restore(new("mysolution.sln", "mypackages", false, [])); // Verify var lastArgs = dotnetCliInvoker.GetLastArgs(); - Assert.Equal("restore --no-dependencies \"mysolution.sln\" --packages \"mypackages\" /p:DisableImplicitNuGetFallbackFolder=true --verbosity normal", lastArgs); + Assert.Equal("restore --no-dependencies mysolution.sln --packages mypackages /p:DisableImplicitNuGetFallbackFolder=true --verbosity normal", lastArgs); Assert.Equal(2, res.RestoredProjects.Count()); Assert.Contains("/path/to/project.csproj", res.RestoredProjects); Assert.Contains("/path/to/project2.csproj", res.RestoredProjects); @@ -188,11 +188,11 @@ public void TestDotnetRestoreSolutionToDirectory2() dotnetCliInvoker.Success = false; // Execute - var res = dotnet.Restore(new("mysolution.sln", "mypackages", false)); + var res = dotnet.Restore(new("mysolution.sln", "mypackages", false, [])); // Verify var lastArgs = dotnetCliInvoker.GetLastArgs(); - Assert.Equal("restore --no-dependencies \"mysolution.sln\" --packages \"mypackages\" /p:DisableImplicitNuGetFallbackFolder=true --verbosity normal", lastArgs); + Assert.Equal("restore --no-dependencies mysolution.sln --packages mypackages /p:DisableImplicitNuGetFallbackFolder=true --verbosity normal", lastArgs); Assert.Empty(res.RestoredProjects); Assert.Empty(res.AssetsFilePaths); } @@ -209,7 +209,7 @@ public void TestDotnetNew() // Verify var lastArgs = dotnetCliInvoker.GetLastArgs(); - Assert.Equal("new console --no-restore --output \"myfolder\"", lastArgs); + Assert.Equal("new console --no-restore --output myfolder", lastArgs); } [Fact] @@ -224,7 +224,7 @@ public void TestDotnetAddPackage() // Verify var lastArgs = dotnetCliInvoker.GetLastArgs(); - Assert.Equal("add \"myfolder\" package \"mypackage\" --no-restore", lastArgs); + Assert.Equal("add myfolder package mypackage --no-restore", lastArgs); } [Fact] @@ -270,7 +270,7 @@ public void TestDotnetExec() var dotnet = MakeDotnet(dotnetCliInvoker); // Execute - dotnet.Exec("myarg1 myarg2"); + dotnet.Exec(["myarg1", "myarg2"]); // Verify var lastArgs = dotnetCliInvoker.GetLastArgs(); @@ -289,7 +289,7 @@ public void TestNugetFeeds() // Verify var lastArgs = dotnetCliInvoker.GetLastArgs(); - Assert.Equal("nuget list source --format Short --configfile \"abc\"", lastArgs); + Assert.Equal("nuget list source --format Short --configfile abc", lastArgs); } [Fact] diff --git a/csharp/extractor/Semmle.Extraction.Tests/Runtime.cs b/csharp/extractor/Semmle.Extraction.Tests/Runtime.cs index 38101420fab2..348cadae2fd0 100644 --- a/csharp/extractor/Semmle.Extraction.Tests/Runtime.cs +++ b/csharp/extractor/Semmle.Extraction.Tests/Runtime.cs @@ -25,7 +25,7 @@ public DotNetStub(IList runtimes, IList sdks) public IList GetListedSdks() => sdks; - public bool Exec(string execArgs) => true; + public bool Exec(List execArgs) => true; public IList GetNugetFeeds(string nugetConfig) => [];