diff --git a/.github/codeql-config.yml b/.github/codeql-config.yml index de5088a..a438d93 100644 --- a/.github/codeql-config.yml +++ b/.github/codeql-config.yml @@ -1,6 +1,8 @@ --- # CodeQL configuration for SonarMark # Excludes test code from path-combine security analysis +# Excludes justified generic exception handlers +# Excludes HttpResponseMessage disposal warnings in HttpMessageHandler implementations name: "SonarMark CodeQL Config" @@ -10,3 +12,13 @@ query-filters: id: cs/path-combine paths: - test/**/*.cs + - exclude: + id: cs/catch-of-all-exceptions + paths: + - src/DemaConsulting.SonarMark/Context.cs + - src/DemaConsulting.SonarMark/Program.cs + - src/DemaConsulting.SonarMark/Validation.cs + - exclude: + id: cs/local-not-disposed + paths: + - src/DemaConsulting.SonarMark/Validation.cs diff --git a/src/DemaConsulting.SonarMark/Context.cs b/src/DemaConsulting.SonarMark/Context.cs index 05c194a..af9ec0a 100644 --- a/src/DemaConsulting.SonarMark/Context.cs +++ b/src/DemaConsulting.SonarMark/Context.cs @@ -171,6 +171,7 @@ private void OpenLogFile(string logFile) { _logWriter = new StreamWriter(logFile, append: false); } + // Generic catch is justified here to wrap any file system exception with context catch (Exception ex) { throw new InvalidOperationException($"Failed to open log file '{logFile}': {ex.Message}", ex); diff --git a/src/DemaConsulting.SonarMark/Program.cs b/src/DemaConsulting.SonarMark/Program.cs index 8b4e099..43ee4b0 100644 --- a/src/DemaConsulting.SonarMark/Program.cs +++ b/src/DemaConsulting.SonarMark/Program.cs @@ -211,6 +211,7 @@ private static void ProcessSonarAnalysis(Context context) File.WriteAllText(context.ReportFile, markdown); context.WriteLine("Quality report generated successfully."); } + // Generic catch is justified here as a top-level handler to log any error without crashing catch (Exception ex) { context.WriteError($"Error: Failed to write report: {ex.Message}"); diff --git a/src/DemaConsulting.SonarMark/SonarQubeClient.cs b/src/DemaConsulting.SonarMark/SonarQubeClient.cs index bd39a18..e5bfb9a 100644 --- a/src/DemaConsulting.SonarMark/SonarQubeClient.cs +++ b/src/DemaConsulting.SonarMark/SonarQubeClient.cs @@ -203,21 +203,15 @@ private async Task GetProjectNameByKeyAsync( /// List of quality gate conditions private static List ParseQualityGateConditions(JsonElement projectStatus) { - var conditions = new List(); - if (!projectStatus.TryGetProperty("conditions", out var conditionsElement) || conditionsElement.ValueKind != JsonValueKind.Array) { - return conditions; - } - - foreach (var condition in conditionsElement.EnumerateArray()) - { - var parsedCondition = ParseQualityGateCondition(condition); - conditions.Add(parsedCondition); + return []; } - return conditions; + return conditionsElement.EnumerateArray() + .Select(ParseQualityGateCondition) + .ToList(); } /// @@ -284,20 +278,14 @@ private async Task> GetIssuesAsync( /// List of parsed issues private static List ParseIssues(JsonElement issuesElement) { - var issues = new List(); - if (issuesElement.ValueKind != JsonValueKind.Array) { - return issues; - } - - foreach (var issue in issuesElement.EnumerateArray()) - { - var parsedIssue = ParseIssue(issue); - issues.Add(parsedIssue); + return []; } - return issues; + return issuesElement.EnumerateArray() + .Select(ParseIssue) + .ToList(); } /// @@ -365,20 +353,14 @@ private async Task> GetHotSpotsAsync( /// List of parsed hot-spots private static List ParseHotSpots(JsonElement hotSpotsElement) { - var hotSpots = new List(); - if (hotSpotsElement.ValueKind != JsonValueKind.Array) { - return hotSpots; - } - - foreach (var hotSpot in hotSpotsElement.EnumerateArray()) - { - var parsedHotSpot = ParseHotSpot(hotSpot); - hotSpots.Add(parsedHotSpot); + return []; } - return hotSpots; + return hotSpotsElement.EnumerateArray() + .Select(ParseHotSpot) + .ToList(); } /// diff --git a/src/DemaConsulting.SonarMark/Validation.cs b/src/DemaConsulting.SonarMark/Validation.cs index 5540f9a..8022052 100644 --- a/src/DemaConsulting.SonarMark/Validation.cs +++ b/src/DemaConsulting.SonarMark/Validation.cs @@ -317,6 +317,7 @@ private static void RunValidationTest( context.WriteError($"✗ {displayName} - FAILED: Exit code {exitCode}"); } } + // Generic catch is justified here to handle any exception during test execution catch (Exception ex) { HandleTestException(test, context, displayName, ex); @@ -370,6 +371,7 @@ private static void WriteResultsFile(Context context, DemaConsulting.TestResults File.WriteAllText(context.ResultsFile, content); context.WriteLine($"Results written to {context.ResultsFile}"); } + // Generic catch is justified here as a top-level handler to log file write errors catch (Exception ex) { context.WriteError($"Error: Failed to write results file: {ex.Message}"); @@ -427,6 +429,11 @@ private static void HandleTestException( /// /// Mock HTTP message handler that returns predefined responses for validation testing. /// + /// + /// HttpResponseMessage objects returned from SendAsync are owned by the HttpClient + /// and will be disposed by the framework. CodeQL warnings about non-disposal here + /// are false positives as this follows the standard HttpMessageHandler pattern. + /// private sealed class MockHttpMessageHandler : HttpMessageHandler { /// diff --git a/test/DemaConsulting.SonarMark.Tests/ContextTests.cs b/test/DemaConsulting.SonarMark.Tests/ContextTests.cs index 10f4578..2c4a4af 100644 --- a/test/DemaConsulting.SonarMark.Tests/ContextTests.cs +++ b/test/DemaConsulting.SonarMark.Tests/ContextTests.cs @@ -394,7 +394,7 @@ public void Context_Create_UnsupportedArgument_ThrowsException() public void Context_WriteLine_NormalMode_WritesToConsole() { var originalOut = Console.Out; - var output = new StringWriter(); + using var output = new StringWriter(); Console.SetOut(output); try @@ -417,7 +417,7 @@ public void Context_WriteLine_NormalMode_WritesToConsole() public void Context_WriteLine_SilentMode_DoesNotWriteToConsole() { var originalOut = Console.Out; - var output = new StringWriter(); + using var output = new StringWriter(); Console.SetOut(output); try @@ -440,7 +440,7 @@ public void Context_WriteLine_SilentMode_DoesNotWriteToConsole() public void Context_WriteError_NormalMode_WritesToConsole() { var originalOut = Console.Out; - var output = new StringWriter(); + using var output = new StringWriter(); Console.SetOut(output); try @@ -464,7 +464,7 @@ public void Context_WriteError_NormalMode_WritesToConsole() public void Context_WriteError_SilentMode_DoesNotWriteToConsole() { var originalOut = Console.Out; - var output = new StringWriter(); + using var output = new StringWriter(); Console.SetOut(output); try diff --git a/test/DemaConsulting.SonarMark.Tests/ProgramTests.cs b/test/DemaConsulting.SonarMark.Tests/ProgramTests.cs index 1d90ba3..e6979e3 100644 --- a/test/DemaConsulting.SonarMark.Tests/ProgramTests.cs +++ b/test/DemaConsulting.SonarMark.Tests/ProgramTests.cs @@ -23,7 +23,7 @@ public void Program_Version_NotEmpty_IsNotEmpty() public void Program_Run_VersionFlag_OutputsVersion() { var originalOut = Console.Out; - var output = new StringWriter(); + using var output = new StringWriter(); Console.SetOut(output); try @@ -46,7 +46,7 @@ public void Program_Run_VersionFlag_OutputsVersion() public void Program_Run_HelpFlag_OutputsBannerAndHelp() { var originalOut = Console.Out; - var output = new StringWriter(); + using var output = new StringWriter(); Console.SetOut(output); try @@ -86,7 +86,7 @@ public void Program_Run_ValidateFlag_OutputsNotImplemented() public void Program_Run_NoFlags_OutputsBannerAndRequiresServer() { var originalOut = Console.Out; - var output = new StringWriter(); + using var output = new StringWriter(); Console.SetOut(output); try @@ -112,7 +112,7 @@ public void Program_Run_NoFlags_OutputsBannerAndRequiresServer() public void Program_Run_ServerWithoutProjectKey_OutputsError() { var originalOut = Console.Out; - var output = new StringWriter(); + using var output = new StringWriter(); Console.SetOut(output); try @@ -137,7 +137,7 @@ public void Program_Run_ServerWithoutProjectKey_OutputsError() public void Program_Run_SilentFlag_SuppressesBanner() { var originalOut = Console.Out; - var output = new StringWriter(); + using var output = new StringWriter(); Console.SetOut(output); try