From 4ee3e1a3625b66696e2db0d40ada7eed66e56c96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marina=20Sundstr=C3=B6m?= Date: Fri, 5 Sep 2025 16:13:12 +0200 Subject: [PATCH 1/2] Handle LINQ enumeration exceptions within try-catch analysis --- CHANGELOG.md | 4 ++ .../CheckedExceptionsAnalyzerTests.Linq.cs | 44 ++++++++++++++++--- .../CheckedExceptionsAnalyzer.Analysis.cs | 7 +++ 3 files changed, 50 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 624c9f3..a580ced 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - PR [#304](https://github.com/marinasundstrom/CheckedExceptions/pull/PR_NUMBER) Support legacy `ignoredExceptions` and `informationalExceptions` settings by translating them to `exceptions` +### Fixed + +- PR [#PR_NUMBER](https://github.com/marinasundstrom/CheckedExceptions/pull/PR_NUMBER) Account for LINQ pipeline exceptions when analyzing foreach statements inside try-catch blocks + ## [2.2.3] - 2025-08-24 ### Fixed diff --git a/CheckedExceptions.Tests/CheckedExceptionsAnalyzerTests.Linq.cs b/CheckedExceptions.Tests/CheckedExceptionsAnalyzerTests.Linq.cs index 812ad6e..3a38d86 100644 --- a/CheckedExceptions.Tests/CheckedExceptionsAnalyzerTests.Linq.cs +++ b/CheckedExceptions.Tests/CheckedExceptionsAnalyzerTests.Linq.cs @@ -158,11 +158,45 @@ await Verifier.VerifyAnalyzerAsync(test, setup: o => o.ExpectedDiagnostics.AddRange(expected, expected2, expected3, expected4); o.DisabledDiagnostics.Remove(CheckedExceptionsAnalyzer.DiagnosticIdRedundantExceptionDeclaration); }, executable: true); - } - - [Fact] - public async Task PassDelegateByVariable() - { + } + + [Fact] + public async Task ForEachCaughtByCatchAll() + { + var test = /* lang=c#-test */ """ + #nullable enable + using System; + using System.Collections.Generic; + using System.Linq; + + IEnumerable items = []; + var query = items.Where(x => int.Parse("10") == x); + try + { + foreach (var item in query) { } + } + catch + { + } + """; + + var expected = Verifier.Diagnostic(CheckedExceptionsAnalyzer.DiagnosticIdImplicitlyDeclaredException) + .WithArguments("FormatException") + .WithSpan(7, 34, 7, 45); + + var expected2 = Verifier.Diagnostic(CheckedExceptionsAnalyzer.DiagnosticIdImplicitlyDeclaredException) + .WithArguments("OverflowException") + .WithSpan(7, 34, 7, 45); + + await Verifier.VerifyAnalyzerAsync(test, setup: o => + { + o.ExpectedDiagnostics.AddRange(expected, expected2); + }, executable: true); + } + + [Fact] + public async Task PassDelegateByVariable() + { var test = /* lang=c#-test */ """ #nullable enable using System; diff --git a/CheckedExceptions/CheckedExceptionsAnalyzer.Analysis.cs b/CheckedExceptions/CheckedExceptionsAnalyzer.Analysis.cs index 398f886..9ddf9a2 100644 --- a/CheckedExceptions/CheckedExceptionsAnalyzer.Analysis.cs +++ b/CheckedExceptions/CheckedExceptionsAnalyzer.Analysis.cs @@ -376,6 +376,13 @@ private static void CollectExceptionsFromExpression(SyntaxNode expression, Compi } } } + + // Capture exceptions from deferred enumerable pipelines directly referenced by the expression itself + var exprOp = semanticModel.GetOperation(expression); + if (exprOp is not null) + { + CollectEnumerationExceptions(exprOp, exceptions, compilation, semanticModel, settings, default); + } } private static HashSet? GetCaughtExceptions(SyntaxList catchClauses, SemanticModel semanticModel) From 395cad78acf9a32d8ec6ec43c9049d5d51826e06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marina=20Sundstr=C3=B6m?= Date: Fri, 5 Sep 2025 16:17:05 +0200 Subject: [PATCH 2/2] Update tests and CHANGELOG.md --- CHANGELOG.md | 12 +++++++----- Test/CheckedExceptions.settings.json | 2 +- Test/LinqTest.cs | 27 ++++++++++++++++++++++++++- 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a580ced..62c4a73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Fixed + +- PR [#306](https://github.com/marinasundstrom/CheckedExceptions/pull/306) Account for LINQ pipeline exceptions when analyzing foreach statements inside try-catch blocks + +## [2.5.0] - 2025-09-05 + ### Added - PR [#301](https://github.com/marinasundstrom/CheckedExceptions/pull/301) Allow treating `Exception` in `[Throws]` as a catch-all via `treatThrowsExceptionAsCatchRest` setting (base-type diagnostic unchanged) @@ -24,11 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Deprecated -- PR [#304](https://github.com/marinasundstrom/CheckedExceptions/pull/PR_NUMBER) Support legacy `ignoredExceptions` and `informationalExceptions` settings by translating them to `exceptions` - -### Fixed - -- PR [#PR_NUMBER](https://github.com/marinasundstrom/CheckedExceptions/pull/PR_NUMBER) Account for LINQ pipeline exceptions when analyzing foreach statements inside try-catch blocks +- PR [#304](https://github.com/marinasundstrom/CheckedExceptions/pull/304) Support legacy `ignoredExceptions` and `informationalExceptions` settings by translating them to `exceptions` ## [2.2.3] - 2025-08-24 diff --git a/Test/CheckedExceptions.settings.json b/Test/CheckedExceptions.settings.json index b7c9add..daa4b06 100644 --- a/Test/CheckedExceptions.settings.json +++ b/Test/CheckedExceptions.settings.json @@ -1,5 +1,5 @@ { - "defaultExceptionClassification": "NonStrict", + "defaultExceptionClassification": "Strict", "exceptions": { "System.NotImplementedException": "Informational", "System.IO.IOException": "Informational", diff --git a/Test/LinqTest.cs b/Test/LinqTest.cs index b9e9520..0cab116 100644 --- a/Test/LinqTest.cs +++ b/Test/LinqTest.cs @@ -22,16 +22,41 @@ public void Test1() private static void ExplicitlyDeclaredThrows() { IEnumerable items = []; - var query = items.Where([Throws(typeof(FormatException), typeof(OverflowException))] (x) => x == int.Parse("10")); + var query = items.Where((x) => x == int.Parse("10")); var x = query; var r = x.Select((z) => 2); foreach (var n in query) { } + try + { + foreach (var n in query) { } + } + catch (OverflowException exc) + { + + } + catch + { + + } + foreach (var item in r) { } + + try + { + foreach (var item in r) + { + + } + } + catch + { + + } } private static void ImplicitlyDeclaredThrows()