From 2618a5fba23432c89882bf343f481f4248ae7ab3 Mon Sep 17 00:00:00 2001 From: evan314159 <110177090+evan314159@users.noreply.github.com> Date: Tue, 16 Sep 2025 17:14:52 +0800 Subject: Fix sync disposal of async-created IAsyncDisposable objects (#14755) --- .../AnalyzerReleases.Shipped.md | 9 +++ .../AsyncDisposalPatternAnalyzer.cs | 82 ++++++++++++++++++++++ .../Jellyfin.CodeAnalysis.csproj | 17 +++++ 3 files changed, 108 insertions(+) create mode 100644 src/Jellyfin.CodeAnalysis/AnalyzerReleases.Shipped.md create mode 100644 src/Jellyfin.CodeAnalysis/AsyncDisposalPatternAnalyzer.cs create mode 100644 src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj (limited to 'src/Jellyfin.CodeAnalysis') diff --git a/src/Jellyfin.CodeAnalysis/AnalyzerReleases.Shipped.md b/src/Jellyfin.CodeAnalysis/AnalyzerReleases.Shipped.md new file mode 100644 index 0000000000..d23e3f9ed3 --- /dev/null +++ b/src/Jellyfin.CodeAnalysis/AnalyzerReleases.Shipped.md @@ -0,0 +1,9 @@ +; Shipped analyzer releases +; https://github.com/dotnet/roslyn/blob/main/src/RoslynAnalyzers/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md + +## Release 1.0 + +### New Rules +Rule ID | Category | Severity | Notes +--------|----------|----------|------- +JF0001 | Usage | Warning | Async-created IAsyncDisposable objects should use 'await using' diff --git a/src/Jellyfin.CodeAnalysis/AsyncDisposalPatternAnalyzer.cs b/src/Jellyfin.CodeAnalysis/AsyncDisposalPatternAnalyzer.cs new file mode 100644 index 0000000000..90c8dfeca7 --- /dev/null +++ b/src/Jellyfin.CodeAnalysis/AsyncDisposalPatternAnalyzer.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Jellyfin.CodeAnalysis; + +/// +/// Analyzer to detect sync disposal of async-created IAsyncDisposable objects. +/// +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public class AsyncDisposalPatternAnalyzer : DiagnosticAnalyzer +{ + /// + /// Diagnostic descriptor for sync disposal of async-created IAsyncDisposable objects. + /// + public static readonly DiagnosticDescriptor AsyncDisposableSyncDisposal = new( + id: "JF0001", + title: "Async-created IAsyncDisposable objects should use 'await using'", + messageFormat: "Using 'using' with async-created IAsyncDisposable object '{0}'. Use 'await using' instead to prevent resource leaks.", + category: "Usage", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: "Objects that implement IAsyncDisposable and are created using 'await' should be disposed using 'await using' to prevent resource leaks."); + + /// + public override ImmutableArray SupportedDiagnostics => [AsyncDisposableSyncDisposal]; + + /// + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + context.RegisterSyntaxNodeAction(AnalyzeUsingStatement, SyntaxKind.UsingStatement); + } + + private static void AnalyzeUsingStatement(SyntaxNodeAnalysisContext context) + { + var usingStatement = (UsingStatementSyntax)context.Node; + + // Skip 'await using' statements + if (usingStatement.AwaitKeyword.IsKind(SyntaxKind.AwaitKeyword)) + { + return; + } + + // Check if there's a variable declaration + if (usingStatement.Declaration?.Variables is null) + { + return; + } + + foreach (var variable in usingStatement.Declaration.Variables) + { + if (variable.Initializer?.Value is AwaitExpressionSyntax awaitExpression) + { + var typeInfo = context.SemanticModel.GetTypeInfo(awaitExpression); + var type = typeInfo.Type; + + if (type is not null && ImplementsIAsyncDisposable(type)) + { + var diagnostic = Diagnostic.Create( + AsyncDisposableSyncDisposal, + usingStatement.GetLocation(), + type.Name); + + context.ReportDiagnostic(diagnostic); + } + } + } + } + + private static bool ImplementsIAsyncDisposable(ITypeSymbol type) + { + return type.AllInterfaces.Any(i => + string.Equals(i.Name, "IAsyncDisposable", StringComparison.Ordinal) + && string.Equals(i.ContainingNamespace?.ToDisplayString(), "System", StringComparison.Ordinal)); + } +} diff --git a/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj b/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj new file mode 100644 index 0000000000..64d20e9044 --- /dev/null +++ b/src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj @@ -0,0 +1,17 @@ + + + + netstandard2.0 + latest + false + false + true + true + + + + + + + + -- cgit v1.2.3 From 526ec8330557246e5eeab3e9a8ff23eb2be930ae Mon Sep 17 00:00:00 2001 From: JPVenson Date: Fri, 26 Sep 2025 20:49:51 +0300 Subject: Add Jellyfin.CodeAnalysis project to abi diff (#14875) --- .github/workflows/ci-compat.yml | 2 +- src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) (limited to 'src/Jellyfin.CodeAnalysis') diff --git a/.github/workflows/ci-compat.yml b/.github/workflows/ci-compat.yml index 702dd29b82..94d54ecee2 100644 --- a/.github/workflows/ci-compat.yml +++ b/.github/workflows/ci-compat.yml @@ -105,7 +105,7 @@ jobs: run: | { echo 'body<false true true + false + true + true + true + snupkg + + + + + $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb + + + + Jellyfin Contributors + Jellyfin.CodeAnalysis + 10.11.0 + https://github.com/jellyfin/jellyfin + GPL-3.0-only -- cgit v1.2.3 From b9c96f3d2c722fd210cec0694d257b0296d13964 Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Sat, 27 Sep 2025 16:41:01 -0600 Subject: Revert "Add Jellyfin.CodeAnalysis project to abi diff (#14875)" This reverts commit 526ec8330557246e5eeab3e9a8ff23eb2be930ae. --- .github/workflows/ci-compat.yml | 2 +- src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj | 18 ------------------ 2 files changed, 1 insertion(+), 19 deletions(-) (limited to 'src/Jellyfin.CodeAnalysis') diff --git a/.github/workflows/ci-compat.yml b/.github/workflows/ci-compat.yml index 94d54ecee2..702dd29b82 100644 --- a/.github/workflows/ci-compat.yml +++ b/.github/workflows/ci-compat.yml @@ -105,7 +105,7 @@ jobs: run: | { echo 'body<false true true - false - true - true - true - snupkg - - - - - $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb - - - - Jellyfin Contributors - Jellyfin.CodeAnalysis - 10.11.0 - https://github.com/jellyfin/jellyfin - GPL-3.0-only -- cgit v1.2.3