From 535084876cf18d9bf913971fbfffc9e105355c88 Mon Sep 17 00:00:00 2001
From: TSR Berry <20988865+TSRBerry@users.noreply.github.com>
Date: Tue, 16 Jan 2024 04:18:14 +0100
Subject: [PATCH 1/8] Add first version of CatchClauseAnalyzer
---
Directory.Packages.props | 5 +
Ryujinx.sln | 12 +
.../AnalyzerReleases.Shipped.md | 7 +
src/Ryujinx.Analyzers/CatchClauseAnalyzer.cs | 232 ++++++++++++++++++
.../CatchClauseCodeFixProvider.cs | 150 +++++++++++
src/Ryujinx.Analyzers/Resources.Designer.cs | 72 ++++++
src/Ryujinx.Analyzers/Resources.resx | 33 +++
.../Ryujinx.Analyzers.csproj | 44 ++++
.../CatchClauseAnalyzerTests.cs | 149 +++++++++++
.../CatchClauseCodeFixProviderTests.cs | 151 ++++++++++++
.../Fixtures/CatchClauseLogger.cs | 79 ++++++
.../Ryujinx.Tests.Analyzers.csproj | 35 +++
12 files changed, 969 insertions(+)
create mode 100644 src/Ryujinx.Analyzers/AnalyzerReleases.Shipped.md
create mode 100644 src/Ryujinx.Analyzers/CatchClauseAnalyzer.cs
create mode 100644 src/Ryujinx.Analyzers/CatchClauseCodeFixProvider.cs
create mode 100644 src/Ryujinx.Analyzers/Resources.Designer.cs
create mode 100644 src/Ryujinx.Analyzers/Resources.resx
create mode 100644 src/Ryujinx.Analyzers/Ryujinx.Analyzers.csproj
create mode 100644 src/Ryujinx.Tests.Analyzers/CatchClauseAnalyzerTests.cs
create mode 100644 src/Ryujinx.Tests.Analyzers/CatchClauseCodeFixProviderTests.cs
create mode 100644 src/Ryujinx.Tests.Analyzers/Fixtures/CatchClauseLogger.cs
create mode 100644 src/Ryujinx.Tests.Analyzers/Ryujinx.Tests.Analyzers.csproj
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 455735fc46..b52fd5d7c4 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -20,6 +20,9 @@
+
+
+
@@ -48,5 +51,7 @@
+
+
diff --git a/Ryujinx.sln b/Ryujinx.sln
index b8304164d5..84dd6e934f 100644
--- a/Ryujinx.sln
+++ b/Ryujinx.sln
@@ -87,6 +87,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon", "src\Ryuj
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon.Kernel.Generators", "src\Ryujinx.Horizon.Kernel.Generators\Ryujinx.Horizon.Kernel.Generators.csproj", "{7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.Analyzers", "src\Ryujinx.Analyzers\Ryujinx.Analyzers.csproj", "{C9523766-7101-442D-89F8-98A43B00267D}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.Tests.Analyzers", "src\Ryujinx.Tests.Analyzers\Ryujinx.Tests.Analyzers.csproj", "{F9D4CBAA-C63D-4062-94A8-F06299DC486B}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -249,6 +253,14 @@ Global
{7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C9523766-7101-442D-89F8-98A43B00267D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C9523766-7101-442D-89F8-98A43B00267D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C9523766-7101-442D-89F8-98A43B00267D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C9523766-7101-442D-89F8-98A43B00267D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F9D4CBAA-C63D-4062-94A8-F06299DC486B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F9D4CBAA-C63D-4062-94A8-F06299DC486B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F9D4CBAA-C63D-4062-94A8-F06299DC486B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F9D4CBAA-C63D-4062-94A8-F06299DC486B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/src/Ryujinx.Analyzers/AnalyzerReleases.Shipped.md b/src/Ryujinx.Analyzers/AnalyzerReleases.Shipped.md
new file mode 100644
index 0000000000..dd5108b624
--- /dev/null
+++ b/src/Ryujinx.Analyzers/AnalyzerReleases.Shipped.md
@@ -0,0 +1,7 @@
+## Release 1.0
+
+### New Rules
+
+| Rule ID | Category | Severity | Notes |
+|---------|-----------------|----------|-------------------------------------|
+| RYU0001 | Maintainability | Warning | Caught exceptions should be logged. |
diff --git a/src/Ryujinx.Analyzers/CatchClauseAnalyzer.cs b/src/Ryujinx.Analyzers/CatchClauseAnalyzer.cs
new file mode 100644
index 0000000000..dbbf15961e
--- /dev/null
+++ b/src/Ryujinx.Analyzers/CatchClauseAnalyzer.cs
@@ -0,0 +1,232 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Diagnostics;
+using System.Collections.Immutable;
+
+namespace Ryujinx.Analyzers
+{
+ [DiagnosticAnalyzer(LanguageNames.CSharp)]
+ public class CatchClauseAnalyzer : DiagnosticAnalyzer
+ {
+ public const string LoggerIdentifier = "Logger";
+ public const string LogClassIdentifier = "LogClass";
+
+ public const string DiagnosticId = "RYU0001";
+
+ private static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.RYU0001Title),
+ Resources.ResourceManager, typeof(Resources));
+
+ private static readonly LocalizableString MessageFormat =
+ new LocalizableResourceString(nameof(Resources.RYU0001MessageFormat), Resources.ResourceManager,
+ typeof(Resources));
+
+ private static readonly LocalizableString Description =
+ new LocalizableResourceString(nameof(Resources.RYU0001Description), Resources.ResourceManager,
+ typeof(Resources));
+
+ private const string Category = "Maintainability";
+
+ private static readonly DiagnosticDescriptor Rule = new(DiagnosticId, Title, MessageFormat, Category,
+ DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description);
+
+ public override ImmutableArray SupportedDiagnostics { get; } =
+ ImmutableArray.Create(Rule);
+
+ public override void Initialize(AnalysisContext context)
+ {
+ context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.ReportDiagnostics);
+ context.EnableConcurrentExecution();
+
+ context.RegisterSyntaxNodeAction(AnalyzeSyntax, SyntaxKind.CatchClause);
+ }
+
+ private static bool EndsWithExpressionText(ExpressionSyntax expression, string text)
+ {
+ if (expression is MemberAccessExpressionSyntax memberAccessExpression)
+ {
+ if (memberAccessExpression.Expression.ToString().EndsWith(text))
+ {
+ return true;
+ }
+ }
+
+ foreach (var childNode in expression.ChildNodes())
+ {
+ if (childNode is not ExpressionSyntax childExpression)
+ {
+ continue;
+ }
+
+ if (EndsWithExpressionText(childExpression, text))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private static bool ContainsInvocationArgText(ExpressionSyntax expression, string text)
+ {
+ if (expression is InvocationExpressionSyntax invocationExpression)
+ {
+ if (invocationExpression.ArgumentList.Arguments.Count > 0)
+ {
+ var invocationArg = invocationExpression.ArgumentList.Arguments.First();
+
+ return invocationArg.ToString().StartsWith($"{text}.") ||
+ invocationArg.ToString().Contains($".{text}.");
+ }
+
+ return false;
+ }
+
+ foreach (var childNode in expression.ChildNodes())
+ {
+ if (childNode is not ExpressionSyntax childExpression)
+ {
+ continue;
+ }
+
+ if (ContainsInvocationArgText(childExpression, text))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private static bool ContainsIdentifier(ArgumentSyntax argument, string identifierText)
+ {
+ foreach (var argChild in argument.ChildNodes())
+ {
+ switch (argChild)
+ {
+ case IdentifierNameSyntax identifierName when identifierName.ToString() == identifierText:
+ return true;
+ case InterpolatedStringExpressionSyntax interpolatedStringExpression:
+ {
+ foreach (var interpolatedStringChild in interpolatedStringExpression.ChildNodes())
+ {
+ if (interpolatedStringChild is not InterpolationSyntax interpolation)
+ {
+ continue;
+ }
+
+ foreach (var interpolationChild in interpolation.ChildNodes())
+ {
+ if (interpolationChild is not IdentifierNameSyntax interpolationIdentifier)
+ {
+ continue;
+ }
+
+ if (interpolationIdentifier.ToString() == identifierText)
+ {
+ return true;
+ }
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private static bool InvocationContainsIdentifier(ExpressionSyntax expression, string identifierText)
+ {
+ foreach (var expressionChild in expression.ChildNodes())
+ {
+ if (expressionChild is not InvocationExpressionSyntax invocationExpression)
+ {
+ continue;
+ }
+
+ foreach (var argument in invocationExpression.ArgumentList.Arguments)
+ {
+ if (ContainsIdentifier(argument, identifierText))
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ /// Executed for each Syntax Node with 'SyntaxKind.CatchClause'.
+ ///
+ /// Operation context.
+ private void AnalyzeSyntax(SyntaxNodeAnalysisContext context)
+ {
+ if (context.Node is not CatchClauseSyntax catchClauseSyntax)
+ return;
+
+ var catchDeclaration = catchClauseSyntax.Declaration;
+
+ // Find catch clauses without declaration.
+ if (catchDeclaration == null)
+ {
+ var diagnostic = Diagnostic.Create(Rule,
+ // The highlighted area in the analyzed source code. Keep it as specific as possible.
+ catchClauseSyntax.GetLocation(),
+ // The value is passed to 'MessageFormat' argument of your 'Rule'.
+ "Exception");
+
+ context.ReportDiagnostic(diagnostic);
+ }
+ // Find catch declarations without an identifier
+ else if (string.IsNullOrWhiteSpace(catchDeclaration.Identifier.Text))
+ {
+ var diagnostic = Diagnostic.Create(Rule,
+ // The highlighted area in the analyzed source code. Keep it as specific as possible.
+ catchClauseSyntax.GetLocation(),
+ // The value is passed to 'MessageFormat' argument of your 'Rule'.
+ catchDeclaration.Type.ToString());
+
+ context.ReportDiagnostic(diagnostic);
+ }
+ // Check logging statements for a reference to the identifier of the catch declaration
+ else
+ {
+ var catchDeclarationIdentifier = catchDeclaration.Identifier;
+ bool exceptionLogged = false;
+
+ // Iterate through all expression statements
+ foreach (var statement in catchClauseSyntax.Block.Statements)
+ {
+ if (statement is not ExpressionStatementSyntax expressionStatement)
+ {
+ continue;
+ }
+
+ // Find Logger invocation
+ if (EndsWithExpressionText(expressionStatement.Expression, LoggerIdentifier) && ContainsInvocationArgText(expressionStatement.Expression, LogClassIdentifier))
+ {
+ // Find catchDeclarationIdentifier in Logger invocation
+ if (InvocationContainsIdentifier(expressionStatement.Expression, catchDeclarationIdentifier.Text))
+ {
+ exceptionLogged = true;
+ }
+ }
+ }
+
+ // Create a diagnostic report if the exception was not logged
+ if (!exceptionLogged)
+ {
+ var diagnostic = Diagnostic.Create(Rule,
+ // The highlighted area in the analyzed source code. Keep it as specific as possible.
+ catchClauseSyntax.GetLocation(),
+ // The value is passed to 'MessageFormat' argument of your 'Rule'.
+ catchDeclaration.Type.ToString());
+
+ context.ReportDiagnostic(diagnostic);
+ }
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx.Analyzers/CatchClauseCodeFixProvider.cs b/src/Ryujinx.Analyzers/CatchClauseCodeFixProvider.cs
new file mode 100644
index 0000000000..81a78fe33a
--- /dev/null
+++ b/src/Ryujinx.Analyzers/CatchClauseCodeFixProvider.cs
@@ -0,0 +1,150 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CodeActions;
+using Microsoft.CodeAnalysis.CodeFixes;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Formatting;
+using Microsoft.CodeAnalysis.Simplification;
+using System.Collections.Immutable;
+using System.Composition;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using SyntaxFactory = Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
+using SyntaxKind = Microsoft.CodeAnalysis.CSharp.SyntaxKind;
+
+namespace Ryujinx.Analyzers
+{
+ [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(CatchClauseCodeFixProvider)), Shared]
+ public class CatchClauseCodeFixProvider : CodeFixProvider
+ {
+ public sealed override ImmutableArray FixableDiagnosticIds { get; } =
+ ImmutableArray.Create(CatchClauseAnalyzer.DiagnosticId);
+
+ public override FixAllProvider? GetFixAllProvider() => null;
+
+ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
+ {
+ var diagnostic = context.Diagnostics.Single();
+ var diagnosticSpan = diagnostic.Location.SourceSpan;
+ var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
+
+ // Find SyntaxNode corresponding to the diagnostic.
+ var diagnosticNode = root?.FindNode(diagnosticSpan);
+
+ // To get the required metadata, we should match the Node to the specific type: 'CatchClauseSyntax'.
+ if (diagnosticNode is not CatchClauseSyntax catchClause)
+ return;
+
+ // Register a code action that will invoke the fix.
+ context.RegisterCodeFix(
+ CodeAction.Create(
+ title: string.Format(Resources.RYU0001CodeFixTitle, GetExceptionType(catchClause)),
+ createChangedDocument: c => LogException(context.Document, catchClause, c),
+ equivalenceKey: nameof(Resources.RYU0001CodeFixTitle)),
+ diagnostic);
+ }
+
+ private static string GetExceptionType(CatchClauseSyntax catchClause)
+ {
+ return catchClause.Declaration != null ? catchClause.Declaration.Type.ToString() : "Exception";
+ }
+
+ private static MemberAccessExpressionSyntax GetLoggingClass(string className)
+ {
+ return SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression,
+ SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression,
+ SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression,
+ SyntaxFactory.IdentifierName("Ryujinx"),
+ SyntaxFactory.IdentifierName("Common")),
+ SyntaxFactory.IdentifierName("Logging")),
+ SyntaxFactory.IdentifierName(className))
+ .WithAdditionalAnnotations(Simplifier.AddImportsAnnotation, Simplifier.Annotation);
+ }
+
+ ///
+ /// Executed on the quick fix action raised by the user.
+ ///
+ /// Affected source file.
+ /// Highlighted catch clause syntax node.
+ /// Any fix is cancellable by the user, so we should support the cancellation token.
+ /// Clone of the document with the modified catch clause.
+ private async Task LogException(Document document,
+ CatchClauseSyntax catchClauseSyntax, CancellationToken cancellationToken)
+ {
+ CatchDeclarationSyntax catchDeclaration;
+ string catchDeclarationIdentifier = "exception";
+
+ // Add a catch declaration if it doesn't exist.
+ if (catchClauseSyntax.Declaration == null)
+ {
+ // System.Exception exception
+ catchDeclaration =
+ SyntaxFactory.CatchDeclaration(
+ SyntaxFactory.QualifiedName(
+ SyntaxFactory.IdentifierName("System"), SyntaxFactory.IdentifierName("Exception")),
+ SyntaxFactory.Identifier("exception")
+ );
+ }
+ else
+ {
+ if (!string.IsNullOrWhiteSpace(catchClauseSyntax.Declaration.Identifier.Text))
+ {
+ catchDeclaration = catchClauseSyntax.Declaration;
+ catchDeclarationIdentifier = catchDeclaration.Identifier.Text;
+ }
+ else
+ {
+ catchDeclaration = catchClauseSyntax.Declaration.WithIdentifier(
+ SyntaxFactory.Identifier(catchDeclarationIdentifier));
+ }
+ }
+
+ // Create logging statement.
+ // Ryujinx.Common.Logging.Logger.Error?.Print(LogClass.Application, $"Exception caught: {exception}");
+ var newStatements = catchClauseSyntax.Block.Statements.Insert(0, SyntaxFactory.ExpressionStatement(SyntaxFactory.ConditionalAccessExpression(
+ SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression,
+ GetLoggingClass("Logger"),
+ SyntaxFactory.IdentifierName("Error")),
+ SyntaxFactory.InvocationExpression(
+ SyntaxFactory.MemberBindingExpression(SyntaxFactory.IdentifierName("Print")))
+ .AddArgumentListArguments(
+ SyntaxFactory.Argument(SyntaxFactory.MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ GetLoggingClass("LogClass"),
+ SyntaxFactory.IdentifierName("Application"))),
+ SyntaxFactory.Argument(SyntaxFactory.InterpolatedStringExpression(
+ SyntaxFactory.Token(SyntaxKind.InterpolatedStringStartToken)).AddContents(
+ SyntaxFactory.InterpolatedStringText().WithTextToken(
+ SyntaxFactory.Token(
+ SyntaxTriviaList.Empty,
+ SyntaxKind.InterpolatedStringTextToken,
+ "Exception caught: ",
+ "Exception caught: ",
+ SyntaxTriviaList.Empty)
+ ),
+ SyntaxFactory.Interpolation(
+ SyntaxFactory.IdentifierName(
+ catchDeclarationIdentifier).WithAdditionalAnnotations(
+ RenameAnnotation.Create())
+ )
+ )
+ )
+ )
+ )));
+
+ // Produce the new catch clause.
+ var newCatchClause = catchClauseSyntax
+ .WithCatchKeyword(catchClauseSyntax.CatchKeyword.WithTrailingTrivia(SyntaxFactory.Space))
+ .WithDeclaration(catchDeclaration)
+ .WithBlock(catchClauseSyntax.Block.WithStatements(newStatements))
+ .WithAdditionalAnnotations(Formatter.Annotation);
+
+ // Replace the old local declaration with the new local declaration.
+ SyntaxNode oldRoot = (await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false))!;
+ SyntaxNode newRoot = oldRoot.ReplaceNode(catchClauseSyntax, newCatchClause);
+
+ // Return document with the transformed tree.
+ return document.WithSyntaxRoot(newRoot);
+ }
+ }
+}
diff --git a/src/Ryujinx.Analyzers/Resources.Designer.cs b/src/Ryujinx.Analyzers/Resources.Designer.cs
new file mode 100644
index 0000000000..8b24d0e322
--- /dev/null
+++ b/src/Ryujinx.Analyzers/Resources.Designer.cs
@@ -0,0 +1,72 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace Ryujinx.Analyzers {
+ using System;
+
+
+ [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resources {
+
+ private static System.Resources.ResourceManager resourceMan;
+
+ private static System.Globalization.CultureInfo resourceCulture;
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources() {
+ }
+
+ [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.Equals(null, resourceMan)) {
+ System.Resources.ResourceManager temp = new System.Resources.ResourceManager("Ryujinx.Analyzers.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ internal static string RYU0001Title {
+ get {
+ return ResourceManager.GetString("RYU0001Title", resourceCulture);
+ }
+ }
+
+ internal static string RYU0001Description {
+ get {
+ return ResourceManager.GetString("RYU0001Description", resourceCulture);
+ }
+ }
+
+ internal static string RYU0001MessageFormat {
+ get {
+ return ResourceManager.GetString("RYU0001MessageFormat", resourceCulture);
+ }
+ }
+
+ internal static string RYU0001CodeFixTitle {
+ get {
+ return ResourceManager.GetString("RYU0001CodeFixTitle", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx.Analyzers/Resources.resx b/src/Ryujinx.Analyzers/Resources.resx
new file mode 100644
index 0000000000..a73cc62fd6
--- /dev/null
+++ b/src/Ryujinx.Analyzers/Resources.resx
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 1.3
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Caught exception is not logged
+
+
+ Caught exceptions should be logged.
+
+
+ Caught exception '{0}' is not logged
+
+
+ Log the caught exception '{0}'
+
+
\ No newline at end of file
diff --git a/src/Ryujinx.Analyzers/Ryujinx.Analyzers.csproj b/src/Ryujinx.Analyzers/Ryujinx.Analyzers.csproj
new file mode 100644
index 0000000000..267ccf0124
--- /dev/null
+++ b/src/Ryujinx.Analyzers/Ryujinx.Analyzers.csproj
@@ -0,0 +1,44 @@
+
+
+
+ netstandard2.0
+ false
+ enable
+ latest
+
+ true
+ true
+
+ Ryujinx.Analyzers
+ Ryujinx.Analyzers
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+ ResXFileCodeGenerator
+ Resources.Designer.cs
+
+
+
+
+
+ True
+ True
+ Resources.resx
+
+
+
+
+
+
+
+
diff --git a/src/Ryujinx.Tests.Analyzers/CatchClauseAnalyzerTests.cs b/src/Ryujinx.Tests.Analyzers/CatchClauseAnalyzerTests.cs
new file mode 100644
index 0000000000..9cce0d2bf3
--- /dev/null
+++ b/src/Ryujinx.Tests.Analyzers/CatchClauseAnalyzerTests.cs
@@ -0,0 +1,149 @@
+using System.IO;
+using System.Reflection;
+using System.Threading.Tasks;
+using Xunit;
+using Verifier =
+ Microsoft.CodeAnalysis.CSharp.Testing.XUnit.AnalyzerVerifier<
+ Ryujinx.Analyzers.CatchClauseAnalyzer>;
+
+namespace Ryujinx.Tests.Analyzers
+{
+ public class CatchClauseAnalyzerTests
+ {
+ private static readonly string _loggerTextPath = Path.Combine(
+ Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!,
+ "Fixtures", "CatchClauseLogger.cs");
+
+ private static readonly string _loggerText = File.ReadAllText(_loggerTextPath);
+
+ [Fact]
+ public async Task CatchWithoutDeclaration_WarningDiagnostic()
+ {
+ const string text = @"
+using System;
+
+public class MyClass
+{
+ public void MyMethod1()
+ {
+ try
+ {
+ Console.WriteLine(""test"");
+ }
+ catch
+ {
+ throw;
+ }
+ }
+}
+";
+
+ var expected = Verifier.Diagnostic()
+ .WithSpan(12, 9, 15, 10)
+ .WithArguments("Exception");
+ await Verifier.VerifyAnalyzerAsync(text, expected).ConfigureAwait(false);
+ }
+
+ [Fact]
+ public async Task CatchWithoutIdentifier_WarningDiagnostic()
+ {
+ const string text = @"
+using System;
+
+public class MyClass
+{
+ public void MyMethod2()
+ {
+ try
+ {
+ Console.WriteLine(""test"");
+ }
+ catch (NullReferenceException)
+ {
+ // testme
+ }
+ }
+}
+";
+
+ var expected = Verifier.Diagnostic()
+ .WithSpan(12, 9, 15, 10)
+ .WithArguments("NullReferenceException");
+ await Verifier.VerifyAnalyzerAsync(text, expected).ConfigureAwait(false);
+ }
+
+ [Fact]
+ public async Task LogWithoutCatchIdentifier_WarningDiagnostic()
+ {
+ string text = _loggerText + @"
+public class MyClass
+{
+ public void MyMethod3()
+ {
+ try
+ {
+ Console.WriteLine(""test"");
+ }
+ catch (ArgumentException exception)
+ {
+ Ryujinx.Common.Logging.Logger.Info?.Print(Ryujinx.Common.Logging.LogClass.Application, ""test exception"");
+ }
+ }
+}
+";
+
+ var expected = Verifier.Diagnostic()
+ .WithSpan(89, 9, 92, 10)
+ .WithArguments("ArgumentException");
+ await Verifier.VerifyAnalyzerAsync(text, expected).ConfigureAwait(false);
+ }
+
+ [Fact]
+ public async Task LogWithIdentifierInString_NoDiagnostic()
+ {
+ string text = _loggerText + @"
+public class MyClass
+{
+ public void MyMethod4()
+ {
+ try
+ {
+ Console.WriteLine(""test"");
+ }
+ catch (Exception abc)
+ {
+ Ryujinx.Common.Logging.Logger.Info?.Print(Ryujinx.Common.Logging.LogClass.Application, $""test: {abc}"");
+ Console.WriteLine(""Test"");
+ }
+ }
+}
+";
+
+ await Verifier.VerifyAnalyzerAsync(text).ConfigureAwait(false);
+ }
+
+ [Fact]
+ public async Task LogWithIdentifierAsArg_NoDiagnostic()
+ {
+ string text = _loggerText + @"
+public class MyClass
+{
+ public void MyMethod5()
+ {
+ try
+ {
+ Console.WriteLine(""test"");
+ }
+ catch (System.Exception exception)
+ {
+ Ryujinx.Common.Logging.Logger.Info?.Print(Ryujinx.Common.Logging.LogClass.Application, $""test"", exception);
+ Console.WriteLine(""Test"");
+ }
+ }
+}
+";
+
+ await Verifier.VerifyAnalyzerAsync(text).ConfigureAwait(false);
+ }
+ }
+}
diff --git a/src/Ryujinx.Tests.Analyzers/CatchClauseCodeFixProviderTests.cs b/src/Ryujinx.Tests.Analyzers/CatchClauseCodeFixProviderTests.cs
new file mode 100644
index 0000000000..b3a67fef7f
--- /dev/null
+++ b/src/Ryujinx.Tests.Analyzers/CatchClauseCodeFixProviderTests.cs
@@ -0,0 +1,151 @@
+using System.IO;
+using System.Reflection;
+using System.Threading.Tasks;
+using Xunit;
+using Verifier =
+ Microsoft.CodeAnalysis.CSharp.Testing.XUnit.CodeFixVerifier;
+
+namespace Ryujinx.Tests.Analyzers
+{
+ public class CatchClauseCodeFixProviderTests
+ {
+ private static readonly string _loggerTextPath = Path.Combine(
+ Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!,
+ "Fixtures", "CatchClauseLogger.cs");
+
+ private static readonly string _loggerText = File.ReadAllText(_loggerTextPath);
+
+ [Fact]
+ public async Task CatchWithoutDeclaration_LogException()
+ {
+ string text = _loggerText + @"
+public class MyClass
+{
+ public void MyMethod1()
+ {
+ try
+ {
+ Console.WriteLine(""test"");
+ }
+ catch
+ {
+ // ignored
+ }
+ }
+}
+";
+
+ string newText = _loggerText + @"
+public class MyClass
+{
+ public void MyMethod1()
+ {
+ try
+ {
+ Console.WriteLine(""test"");
+ }
+ catch (System.Exception exception)
+ {
+ Ryujinx.Common.Logging.Logger.Error?.Print(Ryujinx.Common.Logging.LogClass.Application, $""Exception caught: {exception}"");
+ // ignored
+ }
+ }
+}
+";
+
+ var expected = Verifier.Diagnostic()
+ .WithSpan(89, 9, 92, 10)
+ .WithArguments("Exception");
+ await Verifier.VerifyCodeFixAsync(text, expected, newText).ConfigureAwait(false);
+ }
+
+ [Fact]
+ public async Task CatchWithoutIdentifier_LogException()
+ {
+ string text = _loggerText + @"
+public class MyClass
+{
+ public void MyMethod2()
+ {
+ try
+ {
+ Console.WriteLine(""test"");
+ }
+ catch (NullReferenceException)
+ {
+ // ignored
+ }
+ }
+}
+";
+
+ string newText = _loggerText + @"
+public class MyClass
+{
+ public void MyMethod2()
+ {
+ try
+ {
+ Console.WriteLine(""test"");
+ }
+ catch (NullReferenceException exception)
+ {
+ Ryujinx.Common.Logging.Logger.Error?.Print(Ryujinx.Common.Logging.LogClass.Application, $""Exception caught: {exception}"");
+ // ignored
+ }
+ }
+}
+";
+
+ var expected = Verifier.Diagnostic()
+ .WithSpan(89, 9, 92, 10)
+ .WithArguments("NullReferenceException");
+ await Verifier.VerifyCodeFixAsync(text, expected, newText).ConfigureAwait(false);
+ }
+
+ [Fact]
+ public async Task LogWithoutCatchIdentifier_LogException()
+ {
+ string text = _loggerText + @"
+public class MyClass
+{
+ public void MyMethod3()
+ {
+ try
+ {
+ Console.WriteLine(""test"");
+ }
+ catch (ArgumentException ex)
+ {
+ Ryujinx.Common.Logging.Logger.Info?.Print(Ryujinx.Common.Logging.LogClass.Application, ""test"");
+ }
+ }
+}
+";
+
+ string newText = _loggerText + @"
+public class MyClass
+{
+ public void MyMethod3()
+ {
+ try
+ {
+ Console.WriteLine(""test"");
+ }
+ catch (ArgumentException ex)
+ {
+ Ryujinx.Common.Logging.Logger.Error?.Print(Ryujinx.Common.Logging.LogClass.Application, $""Exception caught: {ex}"");
+ Ryujinx.Common.Logging.Logger.Info?.Print(Ryujinx.Common.Logging.LogClass.Application, ""test"");
+ }
+ }
+}
+";
+
+ var expected = Verifier.Diagnostic()
+ .WithSpan(89, 9, 92, 10)
+ .WithArguments("ArgumentException");
+ await Verifier.VerifyCodeFixAsync(text, expected, newText).ConfigureAwait(false);
+ }
+ }
+}
diff --git a/src/Ryujinx.Tests.Analyzers/Fixtures/CatchClauseLogger.cs b/src/Ryujinx.Tests.Analyzers/Fixtures/CatchClauseLogger.cs
new file mode 100644
index 0000000000..2f40e8581a
--- /dev/null
+++ b/src/Ryujinx.Tests.Analyzers/Fixtures/CatchClauseLogger.cs
@@ -0,0 +1,79 @@
+using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Threading;
+// ReSharper disable All
+
+namespace Ryujinx.Common.Logging
+{
+ public enum LogClass
+ {
+ Application,
+ }
+
+ public class LogEventArgs : EventArgs
+ {
+ public readonly int Level;
+ public readonly TimeSpan Time;
+ public readonly string ThreadName;
+
+ public readonly string Message;
+ public readonly object Data;
+
+ public LogEventArgs(int level, TimeSpan time, string threadName, string message, object data = null)
+ {
+ Level = level;
+ Time = time;
+ ThreadName = threadName;
+ Message = message;
+ Data = data;
+ }
+ }
+
+ public static class Logger
+ {
+ private static readonly Stopwatch _time;
+ private static readonly bool[] _enabledClasses;
+ public static event EventHandler Updated;
+ public readonly struct Log
+ {
+ internal readonly int Level;
+
+ internal Log(int level)
+ {
+ Level = level;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Print(LogClass logClass, string message, [CallerMemberName] string caller = "")
+ {
+ if (_enabledClasses[(int)logClass])
+ {
+ Updated?.Invoke(null, new LogEventArgs(Level, _time.Elapsed, Thread.CurrentThread.Name, FormatMessage(logClass, caller, message)));
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Print(LogClass logClass, string message, object data, [CallerMemberName] string caller = "")
+ {
+ if (_enabledClasses[(int)logClass])
+ {
+ Updated?.Invoke(null, new LogEventArgs(Level, _time.Elapsed, Thread.CurrentThread.Name, FormatMessage(logClass, caller, message), data));
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static string FormatMessage(LogClass logClass, string caller, string message) => $"{logClass} {caller}: {message}";
+ }
+
+ public static Log? Debug { get; private set; }
+ public static Log? Info { get; private set; }
+ public static Log? Warning { get; private set; }
+ public static Log? Error { get; private set; }
+ public static Log? Guest { get; private set; }
+ public static Log? AccessLog { get; private set; }
+ public static Log? Stub { get; private set; }
+ public static Log? Trace { get; private set; }
+ public static Log Notice { get; } // Always enabled
+ }
+}
diff --git a/src/Ryujinx.Tests.Analyzers/Ryujinx.Tests.Analyzers.csproj b/src/Ryujinx.Tests.Analyzers/Ryujinx.Tests.Analyzers.csproj
new file mode 100644
index 0000000000..74f893463b
--- /dev/null
+++ b/src/Ryujinx.Tests.Analyzers/Ryujinx.Tests.Analyzers.csproj
@@ -0,0 +1,35 @@
+
+
+
+ net8.0
+ enable
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
+
From cb6ab72a48dfac5f2b7c4b2d9bf0820260a0e957 Mon Sep 17 00:00:00 2001
From: TSR Berry <20988865+TSRBerry@users.noreply.github.com>
Date: Tue, 16 Jan 2024 04:18:49 +0100
Subject: [PATCH 2/8] Add Ryujinx.Analyzers to all non-test projects
---
src/ARMeilleure/ARMeilleure.csproj | 2 ++
.../Ryujinx.Audio.Backends.OpenAL.csproj | 2 ++
.../Ryujinx.Audio.Backends.SDL2.csproj | 2 ++
.../Ryujinx.Audio.Backends.SoundIo.csproj | 2 ++
src/Ryujinx.Audio/Ryujinx.Audio.csproj | 2 ++
src/Ryujinx.Common/Ryujinx.Common.csproj | 5 +++++
src/Ryujinx.Cpu/Ryujinx.Cpu.csproj | 2 ++
.../Ryujinx.Graphics.Device.csproj | 2 ++
src/Ryujinx.Graphics.GAL/Ryujinx.Graphics.GAL.csproj | 9 ++-------
src/Ryujinx.Graphics.Gpu/Ryujinx.Graphics.Gpu.csproj | 2 ++
.../Ryujinx.Graphics.Host1x.csproj | 2 ++
.../Ryujinx.Graphics.Nvdec.FFmpeg.csproj | 2 ++
.../Ryujinx.Graphics.Nvdec.Vp9.csproj | 2 ++
src/Ryujinx.Graphics.Nvdec/Ryujinx.Graphics.Nvdec.csproj | 2 ++
.../Ryujinx.Graphics.OpenGL.csproj | 2 ++
.../Ryujinx.Graphics.Shader.csproj | 2 ++
.../Ryujinx.Graphics.Texture.csproj | 2 ++
src/Ryujinx.Graphics.Vic/Ryujinx.Graphics.Vic.csproj | 2 ++
src/Ryujinx.Graphics.Video/Ryujinx.Graphics.Video.csproj | 2 ++
.../Ryujinx.Graphics.Vulkan.csproj | 9 ++-------
src/Ryujinx.Gtk3/Ryujinx.Gtk3.csproj | 2 ++
src/Ryujinx.HLE/Ryujinx.HLE.csproj | 2 ++
src/Ryujinx.Headless.SDL2/Ryujinx.Headless.SDL2.csproj | 2 ++
src/Ryujinx.Horizon.Common/Ryujinx.Horizon.Common.csproj | 2 ++
.../Ryujinx.Horizon.Generators.csproj | 5 +++++
.../Ryujinx.Horizon.Kernel.Generators.csproj | 4 ++++
src/Ryujinx.Horizon/Ryujinx.Horizon.csproj | 5 ++++-
src/Ryujinx.Input.SDL2/Ryujinx.Input.SDL2.csproj | 2 ++
src/Ryujinx.Input/Ryujinx.Input.csproj | 2 ++
src/Ryujinx.Memory/Ryujinx.Memory.csproj | 2 ++
src/Ryujinx.SDL2.Common/Ryujinx.SDL2.Common.csproj | 2 ++
src/Ryujinx.ShaderTools/Ryujinx.ShaderTools.csproj | 2 ++
src/Ryujinx.UI.Common/Ryujinx.UI.Common.csproj | 2 ++
.../Ryujinx.UI.LocaleGenerator.csproj | 5 +++++
src/Ryujinx/Ryujinx.csproj | 5 ++++-
src/Spv.Generator/Spv.Generator.csproj | 5 +++++
36 files changed, 90 insertions(+), 16 deletions(-)
diff --git a/src/ARMeilleure/ARMeilleure.csproj b/src/ARMeilleure/ARMeilleure.csproj
index 550e50c26c..7cd6974dfa 100644
--- a/src/ARMeilleure/ARMeilleure.csproj
+++ b/src/ARMeilleure/ARMeilleure.csproj
@@ -8,6 +8,8 @@
+
diff --git a/src/Ryujinx.Audio.Backends.OpenAL/Ryujinx.Audio.Backends.OpenAL.csproj b/src/Ryujinx.Audio.Backends.OpenAL/Ryujinx.Audio.Backends.OpenAL.csproj
index b5fd8f9e7e..95d56724a8 100644
--- a/src/Ryujinx.Audio.Backends.OpenAL/Ryujinx.Audio.Backends.OpenAL.csproj
+++ b/src/Ryujinx.Audio.Backends.OpenAL/Ryujinx.Audio.Backends.OpenAL.csproj
@@ -10,6 +10,8 @@
+
diff --git a/src/Ryujinx.Audio.Backends.SDL2/Ryujinx.Audio.Backends.SDL2.csproj b/src/Ryujinx.Audio.Backends.SDL2/Ryujinx.Audio.Backends.SDL2.csproj
index dd18e70a15..0c27643546 100644
--- a/src/Ryujinx.Audio.Backends.SDL2/Ryujinx.Audio.Backends.SDL2.csproj
+++ b/src/Ryujinx.Audio.Backends.SDL2/Ryujinx.Audio.Backends.SDL2.csproj
@@ -8,6 +8,8 @@
+
diff --git a/src/Ryujinx.Audio.Backends.SoundIo/Ryujinx.Audio.Backends.SoundIo.csproj b/src/Ryujinx.Audio.Backends.SoundIo/Ryujinx.Audio.Backends.SoundIo.csproj
index 5c94234636..a4cc6e13f6 100644
--- a/src/Ryujinx.Audio.Backends.SoundIo/Ryujinx.Audio.Backends.SoundIo.csproj
+++ b/src/Ryujinx.Audio.Backends.SoundIo/Ryujinx.Audio.Backends.SoundIo.csproj
@@ -8,6 +8,8 @@
+
diff --git a/src/Ryujinx.Audio/Ryujinx.Audio.csproj b/src/Ryujinx.Audio/Ryujinx.Audio.csproj
index fc20f4ec48..c319097fe4 100644
--- a/src/Ryujinx.Audio/Ryujinx.Audio.csproj
+++ b/src/Ryujinx.Audio/Ryujinx.Audio.csproj
@@ -9,6 +9,8 @@
+
diff --git a/src/Ryujinx.Common/Ryujinx.Common.csproj b/src/Ryujinx.Common/Ryujinx.Common.csproj
index da2f13a21c..7802d13385 100644
--- a/src/Ryujinx.Common/Ryujinx.Common.csproj
+++ b/src/Ryujinx.Common/Ryujinx.Common.csproj
@@ -12,4 +12,9 @@
+
+
+
+
diff --git a/src/Ryujinx.Cpu/Ryujinx.Cpu.csproj b/src/Ryujinx.Cpu/Ryujinx.Cpu.csproj
index 5a6bf5c3d3..3ae6f8c509 100644
--- a/src/Ryujinx.Cpu/Ryujinx.Cpu.csproj
+++ b/src/Ryujinx.Cpu/Ryujinx.Cpu.csproj
@@ -8,6 +8,8 @@
+
diff --git a/src/Ryujinx.Graphics.Device/Ryujinx.Graphics.Device.csproj b/src/Ryujinx.Graphics.Device/Ryujinx.Graphics.Device.csproj
index 973a9e2609..d0f796dbf2 100644
--- a/src/Ryujinx.Graphics.Device/Ryujinx.Graphics.Device.csproj
+++ b/src/Ryujinx.Graphics.Device/Ryujinx.Graphics.Device.csproj
@@ -6,6 +6,8 @@
+
diff --git a/src/Ryujinx.Graphics.GAL/Ryujinx.Graphics.GAL.csproj b/src/Ryujinx.Graphics.GAL/Ryujinx.Graphics.GAL.csproj
index d88b641a39..88aa836fda 100644
--- a/src/Ryujinx.Graphics.GAL/Ryujinx.Graphics.GAL.csproj
+++ b/src/Ryujinx.Graphics.GAL/Ryujinx.Graphics.GAL.csproj
@@ -2,19 +2,14 @@
net8.0
-
-
-
- true
-
-
-
true
+
diff --git a/src/Ryujinx.Graphics.Gpu/Ryujinx.Graphics.Gpu.csproj b/src/Ryujinx.Graphics.Gpu/Ryujinx.Graphics.Gpu.csproj
index 6f1cce6aca..e525a69706 100644
--- a/src/Ryujinx.Graphics.Gpu/Ryujinx.Graphics.Gpu.csproj
+++ b/src/Ryujinx.Graphics.Gpu/Ryujinx.Graphics.Gpu.csproj
@@ -12,6 +12,8 @@
+
diff --git a/src/Ryujinx.Graphics.Host1x/Ryujinx.Graphics.Host1x.csproj b/src/Ryujinx.Graphics.Host1x/Ryujinx.Graphics.Host1x.csproj
index d631d039f8..43ea2c5627 100644
--- a/src/Ryujinx.Graphics.Host1x/Ryujinx.Graphics.Host1x.csproj
+++ b/src/Ryujinx.Graphics.Host1x/Ryujinx.Graphics.Host1x.csproj
@@ -6,6 +6,8 @@
+
diff --git a/src/Ryujinx.Graphics.Nvdec.FFmpeg/Ryujinx.Graphics.Nvdec.FFmpeg.csproj b/src/Ryujinx.Graphics.Nvdec.FFmpeg/Ryujinx.Graphics.Nvdec.FFmpeg.csproj
index d1a6358c26..0bba1ff9f1 100644
--- a/src/Ryujinx.Graphics.Nvdec.FFmpeg/Ryujinx.Graphics.Nvdec.FFmpeg.csproj
+++ b/src/Ryujinx.Graphics.Nvdec.FFmpeg/Ryujinx.Graphics.Nvdec.FFmpeg.csproj
@@ -8,6 +8,8 @@
+
diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/Ryujinx.Graphics.Nvdec.Vp9.csproj b/src/Ryujinx.Graphics.Nvdec.Vp9/Ryujinx.Graphics.Nvdec.Vp9.csproj
index d1a6358c26..0bba1ff9f1 100644
--- a/src/Ryujinx.Graphics.Nvdec.Vp9/Ryujinx.Graphics.Nvdec.Vp9.csproj
+++ b/src/Ryujinx.Graphics.Nvdec.Vp9/Ryujinx.Graphics.Nvdec.Vp9.csproj
@@ -8,6 +8,8 @@
+
diff --git a/src/Ryujinx.Graphics.Nvdec/Ryujinx.Graphics.Nvdec.csproj b/src/Ryujinx.Graphics.Nvdec/Ryujinx.Graphics.Nvdec.csproj
index 6c00e9a7c2..6b4eaaddf4 100644
--- a/src/Ryujinx.Graphics.Nvdec/Ryujinx.Graphics.Nvdec.csproj
+++ b/src/Ryujinx.Graphics.Nvdec/Ryujinx.Graphics.Nvdec.csproj
@@ -12,6 +12,8 @@
+
diff --git a/src/Ryujinx.Graphics.OpenGL/Ryujinx.Graphics.OpenGL.csproj b/src/Ryujinx.Graphics.OpenGL/Ryujinx.Graphics.OpenGL.csproj
index 3d64da99bc..cf71815097 100644
--- a/src/Ryujinx.Graphics.OpenGL/Ryujinx.Graphics.OpenGL.csproj
+++ b/src/Ryujinx.Graphics.OpenGL/Ryujinx.Graphics.OpenGL.csproj
@@ -27,6 +27,8 @@
+
diff --git a/src/Ryujinx.Graphics.Shader/Ryujinx.Graphics.Shader.csproj b/src/Ryujinx.Graphics.Shader/Ryujinx.Graphics.Shader.csproj
index 8ccf5348fe..44a2e3932e 100644
--- a/src/Ryujinx.Graphics.Shader/Ryujinx.Graphics.Shader.csproj
+++ b/src/Ryujinx.Graphics.Shader/Ryujinx.Graphics.Shader.csproj
@@ -7,6 +7,8 @@
+
diff --git a/src/Ryujinx.Graphics.Texture/Ryujinx.Graphics.Texture.csproj b/src/Ryujinx.Graphics.Texture/Ryujinx.Graphics.Texture.csproj
index 51721490eb..a8173f2af9 100644
--- a/src/Ryujinx.Graphics.Texture/Ryujinx.Graphics.Texture.csproj
+++ b/src/Ryujinx.Graphics.Texture/Ryujinx.Graphics.Texture.csproj
@@ -6,6 +6,8 @@
+
diff --git a/src/Ryujinx.Graphics.Vic/Ryujinx.Graphics.Vic.csproj b/src/Ryujinx.Graphics.Vic/Ryujinx.Graphics.Vic.csproj
index a6c4fb2bb1..28c1453a91 100644
--- a/src/Ryujinx.Graphics.Vic/Ryujinx.Graphics.Vic.csproj
+++ b/src/Ryujinx.Graphics.Vic/Ryujinx.Graphics.Vic.csproj
@@ -9,6 +9,8 @@
+
diff --git a/src/Ryujinx.Graphics.Video/Ryujinx.Graphics.Video.csproj b/src/Ryujinx.Graphics.Video/Ryujinx.Graphics.Video.csproj
index abff58a532..878843ba99 100644
--- a/src/Ryujinx.Graphics.Video/Ryujinx.Graphics.Video.csproj
+++ b/src/Ryujinx.Graphics.Video/Ryujinx.Graphics.Video.csproj
@@ -6,6 +6,8 @@
+
diff --git a/src/Ryujinx.Graphics.Vulkan/Ryujinx.Graphics.Vulkan.csproj b/src/Ryujinx.Graphics.Vulkan/Ryujinx.Graphics.Vulkan.csproj
index f6a7be91e4..af101a1d63 100644
--- a/src/Ryujinx.Graphics.Vulkan/Ryujinx.Graphics.Vulkan.csproj
+++ b/src/Ryujinx.Graphics.Vulkan/Ryujinx.Graphics.Vulkan.csproj
@@ -2,13 +2,6 @@
net8.0
-
-
-
- true
-
-
-
true
@@ -60,6 +53,8 @@
+
diff --git a/src/Ryujinx.Gtk3/Ryujinx.Gtk3.csproj b/src/Ryujinx.Gtk3/Ryujinx.Gtk3.csproj
index 68bf98981e..e1a20e0ea2 100644
--- a/src/Ryujinx.Gtk3/Ryujinx.Gtk3.csproj
+++ b/src/Ryujinx.Gtk3/Ryujinx.Gtk3.csproj
@@ -46,6 +46,8 @@
+
diff --git a/src/Ryujinx.HLE/Ryujinx.HLE.csproj b/src/Ryujinx.HLE/Ryujinx.HLE.csproj
index 0fcf9e4b57..d832e37206 100644
--- a/src/Ryujinx.HLE/Ryujinx.HLE.csproj
+++ b/src/Ryujinx.HLE/Ryujinx.HLE.csproj
@@ -18,6 +18,8 @@
+
diff --git a/src/Ryujinx.Headless.SDL2/Ryujinx.Headless.SDL2.csproj b/src/Ryujinx.Headless.SDL2/Ryujinx.Headless.SDL2.csproj
index cc5a365183..74cbb804f4 100644
--- a/src/Ryujinx.Headless.SDL2/Ryujinx.Headless.SDL2.csproj
+++ b/src/Ryujinx.Headless.SDL2/Ryujinx.Headless.SDL2.csproj
@@ -30,6 +30,8 @@
+
diff --git a/src/Ryujinx.Horizon.Common/Ryujinx.Horizon.Common.csproj b/src/Ryujinx.Horizon.Common/Ryujinx.Horizon.Common.csproj
index fa1544c4fa..a1b510182d 100644
--- a/src/Ryujinx.Horizon.Common/Ryujinx.Horizon.Common.csproj
+++ b/src/Ryujinx.Horizon.Common/Ryujinx.Horizon.Common.csproj
@@ -6,6 +6,8 @@
+
diff --git a/src/Ryujinx.Horizon.Generators/Ryujinx.Horizon.Generators.csproj b/src/Ryujinx.Horizon.Generators/Ryujinx.Horizon.Generators.csproj
index d588039933..435db7be5a 100644
--- a/src/Ryujinx.Horizon.Generators/Ryujinx.Horizon.Generators.csproj
+++ b/src/Ryujinx.Horizon.Generators/Ryujinx.Horizon.Generators.csproj
@@ -13,4 +13,9 @@
+
+
+
+
diff --git a/src/Ryujinx.Horizon.Kernel.Generators/Ryujinx.Horizon.Kernel.Generators.csproj b/src/Ryujinx.Horizon.Kernel.Generators/Ryujinx.Horizon.Kernel.Generators.csproj
index d588039933..d3c423a887 100644
--- a/src/Ryujinx.Horizon.Kernel.Generators/Ryujinx.Horizon.Kernel.Generators.csproj
+++ b/src/Ryujinx.Horizon.Kernel.Generators/Ryujinx.Horizon.Kernel.Generators.csproj
@@ -13,4 +13,8 @@
+
+
+
diff --git a/src/Ryujinx.Horizon/Ryujinx.Horizon.csproj b/src/Ryujinx.Horizon/Ryujinx.Horizon.csproj
index d1f572d5c8..01f8f74e44 100644
--- a/src/Ryujinx.Horizon/Ryujinx.Horizon.csproj
+++ b/src/Ryujinx.Horizon/Ryujinx.Horizon.csproj
@@ -8,8 +8,11 @@
-
+
+
diff --git a/src/Ryujinx.Input.SDL2/Ryujinx.Input.SDL2.csproj b/src/Ryujinx.Input.SDL2/Ryujinx.Input.SDL2.csproj
index 1ab79d08ab..f0bc41fa51 100644
--- a/src/Ryujinx.Input.SDL2/Ryujinx.Input.SDL2.csproj
+++ b/src/Ryujinx.Input.SDL2/Ryujinx.Input.SDL2.csproj
@@ -8,6 +8,8 @@
+
diff --git a/src/Ryujinx.Input/Ryujinx.Input.csproj b/src/Ryujinx.Input/Ryujinx.Input.csproj
index 59a9eeb619..f77ddcd178 100644
--- a/src/Ryujinx.Input/Ryujinx.Input.csproj
+++ b/src/Ryujinx.Input/Ryujinx.Input.csproj
@@ -12,6 +12,8 @@
+
diff --git a/src/Ryujinx.Memory/Ryujinx.Memory.csproj b/src/Ryujinx.Memory/Ryujinx.Memory.csproj
index 8310a3e5c8..48ee668d62 100644
--- a/src/Ryujinx.Memory/Ryujinx.Memory.csproj
+++ b/src/Ryujinx.Memory/Ryujinx.Memory.csproj
@@ -7,6 +7,8 @@
+
diff --git a/src/Ryujinx.SDL2.Common/Ryujinx.SDL2.Common.csproj b/src/Ryujinx.SDL2.Common/Ryujinx.SDL2.Common.csproj
index 8e79530455..8171bcd9fd 100644
--- a/src/Ryujinx.SDL2.Common/Ryujinx.SDL2.Common.csproj
+++ b/src/Ryujinx.SDL2.Common/Ryujinx.SDL2.Common.csproj
@@ -10,6 +10,8 @@
+
diff --git a/src/Ryujinx.ShaderTools/Ryujinx.ShaderTools.csproj b/src/Ryujinx.ShaderTools/Ryujinx.ShaderTools.csproj
index ab89fb5c7a..d2f9b0b34a 100644
--- a/src/Ryujinx.ShaderTools/Ryujinx.ShaderTools.csproj
+++ b/src/Ryujinx.ShaderTools/Ryujinx.ShaderTools.csproj
@@ -8,6 +8,8 @@
+
diff --git a/src/Ryujinx.UI.Common/Ryujinx.UI.Common.csproj b/src/Ryujinx.UI.Common/Ryujinx.UI.Common.csproj
index 387e998b0b..622f55f9b3 100644
--- a/src/Ryujinx.UI.Common/Ryujinx.UI.Common.csproj
+++ b/src/Ryujinx.UI.Common/Ryujinx.UI.Common.csproj
@@ -63,6 +63,8 @@
+
diff --git a/src/Ryujinx.UI.LocaleGenerator/Ryujinx.UI.LocaleGenerator.csproj b/src/Ryujinx.UI.LocaleGenerator/Ryujinx.UI.LocaleGenerator.csproj
index 05cbc7644b..ca84b2beb2 100644
--- a/src/Ryujinx.UI.LocaleGenerator/Ryujinx.UI.LocaleGenerator.csproj
+++ b/src/Ryujinx.UI.LocaleGenerator/Ryujinx.UI.LocaleGenerator.csproj
@@ -15,4 +15,9 @@
+
+
+
+
diff --git a/src/Ryujinx/Ryujinx.csproj b/src/Ryujinx/Ryujinx.csproj
index 2a5a9fadd6..c43e721f79 100644
--- a/src/Ryujinx/Ryujinx.csproj
+++ b/src/Ryujinx/Ryujinx.csproj
@@ -70,7 +70,10 @@
-
+
+
diff --git a/src/Spv.Generator/Spv.Generator.csproj b/src/Spv.Generator/Spv.Generator.csproj
index ae2821edbb..d29bf9c279 100644
--- a/src/Spv.Generator/Spv.Generator.csproj
+++ b/src/Spv.Generator/Spv.Generator.csproj
@@ -4,4 +4,9 @@
net8.0
+
+
+
+
From 3a7ff61029ce6995cb4306190d68d358df096c61 Mon Sep 17 00:00:00 2001
From: TSR Berry <20988865+TSRBerry@users.noreply.github.com>
Date: Tue, 16 Jan 2024 05:00:59 +0100
Subject: [PATCH 3/8] Make dotnet format happy again (except for RYU0001)
---
src/Ryujinx.Analyzers/CatchClauseAnalyzer.cs | 24 +++++++++----------
.../CatchClauseCodeFixProvider.cs | 12 +++++-----
.../CatchClauseAnalyzerTests.cs | 8 +++----
3 files changed, 22 insertions(+), 22 deletions(-)
diff --git a/src/Ryujinx.Analyzers/CatchClauseAnalyzer.cs b/src/Ryujinx.Analyzers/CatchClauseAnalyzer.cs
index dbbf15961e..6d2c02b906 100644
--- a/src/Ryujinx.Analyzers/CatchClauseAnalyzer.cs
+++ b/src/Ryujinx.Analyzers/CatchClauseAnalyzer.cs
@@ -9,29 +9,29 @@ namespace Ryujinx.Analyzers
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class CatchClauseAnalyzer : DiagnosticAnalyzer
{
- public const string LoggerIdentifier = "Logger";
- public const string LogClassIdentifier = "LogClass";
+ private const string LoggerIdentifier = "Logger";
+ private const string LogClassIdentifier = "LogClass";
public const string DiagnosticId = "RYU0001";
- private static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.RYU0001Title),
+ private static readonly LocalizableString _title = new LocalizableResourceString(nameof(Resources.RYU0001Title),
Resources.ResourceManager, typeof(Resources));
- private static readonly LocalizableString MessageFormat =
+ private static readonly LocalizableString _messageFormat =
new LocalizableResourceString(nameof(Resources.RYU0001MessageFormat), Resources.ResourceManager,
typeof(Resources));
- private static readonly LocalizableString Description =
+ private static readonly LocalizableString _description =
new LocalizableResourceString(nameof(Resources.RYU0001Description), Resources.ResourceManager,
typeof(Resources));
private const string Category = "Maintainability";
- private static readonly DiagnosticDescriptor Rule = new(DiagnosticId, Title, MessageFormat, Category,
- DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description);
+ private static readonly DiagnosticDescriptor _rule = new(DiagnosticId, _title, _messageFormat, Category,
+ DiagnosticSeverity.Warning, isEnabledByDefault: true, description: _description);
public override ImmutableArray SupportedDiagnostics { get; } =
- ImmutableArray.Create(Rule);
+ ImmutableArray.Create(_rule);
public override void Initialize(AnalysisContext context)
{
@@ -171,7 +171,7 @@ namespace Ryujinx.Analyzers
// Find catch clauses without declaration.
if (catchDeclaration == null)
{
- var diagnostic = Diagnostic.Create(Rule,
+ var diagnostic = Diagnostic.Create(_rule,
// The highlighted area in the analyzed source code. Keep it as specific as possible.
catchClauseSyntax.GetLocation(),
// The value is passed to 'MessageFormat' argument of your 'Rule'.
@@ -182,7 +182,7 @@ namespace Ryujinx.Analyzers
// Find catch declarations without an identifier
else if (string.IsNullOrWhiteSpace(catchDeclaration.Identifier.Text))
{
- var diagnostic = Diagnostic.Create(Rule,
+ var diagnostic = Diagnostic.Create(_rule,
// The highlighted area in the analyzed source code. Keep it as specific as possible.
catchClauseSyntax.GetLocation(),
// The value is passed to 'MessageFormat' argument of your 'Rule'.
@@ -214,11 +214,11 @@ namespace Ryujinx.Analyzers
}
}
}
-
+
// Create a diagnostic report if the exception was not logged
if (!exceptionLogged)
{
- var diagnostic = Diagnostic.Create(Rule,
+ var diagnostic = Diagnostic.Create(_rule,
// The highlighted area in the analyzed source code. Keep it as specific as possible.
catchClauseSyntax.GetLocation(),
// The value is passed to 'MessageFormat' argument of your 'Rule'.
diff --git a/src/Ryujinx.Analyzers/CatchClauseCodeFixProvider.cs b/src/Ryujinx.Analyzers/CatchClauseCodeFixProvider.cs
index 81a78fe33a..153888632a 100644
--- a/src/Ryujinx.Analyzers/CatchClauseCodeFixProvider.cs
+++ b/src/Ryujinx.Analyzers/CatchClauseCodeFixProvider.cs
@@ -78,12 +78,12 @@ namespace Ryujinx.Analyzers
if (catchClauseSyntax.Declaration == null)
{
// System.Exception exception
- catchDeclaration =
- SyntaxFactory.CatchDeclaration(
- SyntaxFactory.QualifiedName(
- SyntaxFactory.IdentifierName("System"), SyntaxFactory.IdentifierName("Exception")),
- SyntaxFactory.Identifier("exception")
- );
+ catchDeclaration =
+ SyntaxFactory.CatchDeclaration(
+ SyntaxFactory.QualifiedName(
+ SyntaxFactory.IdentifierName("System"), SyntaxFactory.IdentifierName("Exception")),
+ SyntaxFactory.Identifier("exception")
+ );
}
else
{
diff --git a/src/Ryujinx.Tests.Analyzers/CatchClauseAnalyzerTests.cs b/src/Ryujinx.Tests.Analyzers/CatchClauseAnalyzerTests.cs
index 9cce0d2bf3..1e115dc6ee 100644
--- a/src/Ryujinx.Tests.Analyzers/CatchClauseAnalyzerTests.cs
+++ b/src/Ryujinx.Tests.Analyzers/CatchClauseAnalyzerTests.cs
@@ -19,7 +19,7 @@ namespace Ryujinx.Tests.Analyzers
[Fact]
public async Task CatchWithoutDeclaration_WarningDiagnostic()
{
- const string text = @"
+ const string Text = @"
using System;
public class MyClass
@@ -41,13 +41,13 @@ public class MyClass
var expected = Verifier.Diagnostic()
.WithSpan(12, 9, 15, 10)
.WithArguments("Exception");
- await Verifier.VerifyAnalyzerAsync(text, expected).ConfigureAwait(false);
+ await Verifier.VerifyAnalyzerAsync(Text, expected).ConfigureAwait(false);
}
[Fact]
public async Task CatchWithoutIdentifier_WarningDiagnostic()
{
- const string text = @"
+ const string Text = @"
using System;
public class MyClass
@@ -69,7 +69,7 @@ public class MyClass
var expected = Verifier.Diagnostic()
.WithSpan(12, 9, 15, 10)
.WithArguments("NullReferenceException");
- await Verifier.VerifyAnalyzerAsync(text, expected).ConfigureAwait(false);
+ await Verifier.VerifyAnalyzerAsync(Text, expected).ConfigureAwait(false);
}
[Fact]
From fc7f80f1c0515e66668e8f033aeddac4bca92ab7 Mon Sep 17 00:00:00 2001
From: TSR Berry <20988865+TSRBerry@users.noreply.github.com>
Date: Tue, 16 Jan 2024 05:13:07 +0100
Subject: [PATCH 4/8] Make sure Ryujinx.Analyzers is available during format
---
.github/workflows/checks.yml | 3 +++
1 file changed, 3 insertions(+)
diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml
index 2bef2d8e00..dc9721cfe7 100644
--- a/.github/workflows/checks.yml
+++ b/.github/workflows/checks.yml
@@ -36,6 +36,9 @@ jobs:
- run: dotnet restore
+ - name: Build Ryujinx.Analyzers
+ run: dotnet build src/Ryujinx.Analyzers
+
- name: Print dotnet format version
run: dotnet format --version
From 8e2b479a70a00344f9094983d284cd732a4d863f Mon Sep 17 00:00:00 2001
From: TSR Berry <20988865+TSRBerry@users.noreply.github.com>
Date: Tue, 16 Jan 2024 13:03:48 +0100
Subject: [PATCH 5/8] Add 3 more test cases
---
.../CatchClauseAnalyzerTests.cs | 73 +++++++++++++++++++
1 file changed, 73 insertions(+)
diff --git a/src/Ryujinx.Tests.Analyzers/CatchClauseAnalyzerTests.cs b/src/Ryujinx.Tests.Analyzers/CatchClauseAnalyzerTests.cs
index 1e115dc6ee..24e09af13d 100644
--- a/src/Ryujinx.Tests.Analyzers/CatchClauseAnalyzerTests.cs
+++ b/src/Ryujinx.Tests.Analyzers/CatchClauseAnalyzerTests.cs
@@ -141,6 +141,79 @@ public class MyClass
}
}
}
+";
+
+ await Verifier.VerifyAnalyzerAsync(text).ConfigureAwait(false);
+ }
+
+ [Fact]
+ public async Task LogWithIdentifierAndMethodCall_NoDiagnostic()
+ {
+ string text = _loggerText + @"
+public class MyClass
+{
+ public void MyMethod6()
+ {
+ try
+ {
+ Console.WriteLine(""test"");
+ }
+ catch (InvalidOperationException abc)
+ {
+ Ryujinx.Common.Logging.Logger.Info?.Print(Ryujinx.Common.Logging.LogClass.Application, $""test: {abc.ToString()}"");
+ Console.WriteLine(""Test"");
+ }
+ }
+}
+";
+
+ await Verifier.VerifyAnalyzerAsync(text).ConfigureAwait(false);
+ }
+
+ [Fact]
+ public async Task LogWithMethodCallOnIdentifier_NoDiagnostic()
+ {
+ string text = _loggerText + @"
+public class MyClass
+{
+ public void MyMethod7()
+ {
+ try
+ {
+ Console.WriteLine(""test"");
+ }
+ catch (IndexOutOfRangeException mistake1)
+ {
+ string test = ""another test"";
+ Ryujinx.Common.Logging.Logger.Info?.Print(Ryujinx.Common.Logging.LogClass.Application, $""test: {string.Concat(mistake1, test)}"");
+ Console.WriteLine(""Test"");
+ }
+ }
+}
+";
+
+ await Verifier.VerifyAnalyzerAsync(text).ConfigureAwait(false);
+ }
+
+ [Fact]
+ public async Task LogWithPropertyOfException_NoDiagnostic()
+ {
+ string text = _loggerText + @"
+public class MyClass
+{
+ public void MyMethod8()
+ {
+ try
+ {
+ Console.WriteLine(""test"");
+ }
+ catch (ArgumentOutOfRangeException oob)
+ {
+ Ryujinx.Common.Logging.Logger.Info?.Print(Ryujinx.Common.Logging.LogClass.Application, $""test: {oob.Message}"");
+ Console.WriteLine(""Test"");
+ }
+ }
+}
";
await Verifier.VerifyAnalyzerAsync(text).ConfigureAwait(false);
From 40bceebacbd6349a9a765dbefba71d8cc3237e0c Mon Sep 17 00:00:00 2001
From: TSR Berry <20988865+TSRBerry@users.noreply.github.com>
Date: Tue, 16 Jan 2024 13:05:47 +0100
Subject: [PATCH 6/8] CatchClauseAnalyzer: Simplify and fix
ContainsIdentifier()
---
src/Ryujinx.Analyzers/CatchClauseAnalyzer.cs | 63 ++------------------
1 file changed, 6 insertions(+), 57 deletions(-)
diff --git a/src/Ryujinx.Analyzers/CatchClauseAnalyzer.cs b/src/Ryujinx.Analyzers/CatchClauseAnalyzer.cs
index 6d2c02b906..2fbb209a7a 100644
--- a/src/Ryujinx.Analyzers/CatchClauseAnalyzer.cs
+++ b/src/Ryujinx.Analyzers/CatchClauseAnalyzer.cs
@@ -3,6 +3,7 @@ using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using System.Collections.Immutable;
+using System.Linq;
namespace Ryujinx.Analyzers
{
@@ -98,63 +99,11 @@ namespace Ryujinx.Analyzers
return false;
}
- private static bool ContainsIdentifier(ArgumentSyntax argument, string identifierText)
+ private static bool ContainsIdentifier(ExpressionSyntax expression, string identifierText)
{
- foreach (var argChild in argument.ChildNodes())
- {
- switch (argChild)
- {
- case IdentifierNameSyntax identifierName when identifierName.ToString() == identifierText:
- return true;
- case InterpolatedStringExpressionSyntax interpolatedStringExpression:
- {
- foreach (var interpolatedStringChild in interpolatedStringExpression.ChildNodes())
- {
- if (interpolatedStringChild is not InterpolationSyntax interpolation)
- {
- continue;
- }
-
- foreach (var interpolationChild in interpolation.ChildNodes())
- {
- if (interpolationChild is not IdentifierNameSyntax interpolationIdentifier)
- {
- continue;
- }
-
- if (interpolationIdentifier.ToString() == identifierText)
- {
- return true;
- }
- }
- }
- break;
- }
- }
- }
-
- return false;
- }
-
- private static bool InvocationContainsIdentifier(ExpressionSyntax expression, string identifierText)
- {
- foreach (var expressionChild in expression.ChildNodes())
- {
- if (expressionChild is not InvocationExpressionSyntax invocationExpression)
- {
- continue;
- }
-
- foreach (var argument in invocationExpression.ArgumentList.Arguments)
- {
- if (ContainsIdentifier(argument, identifierText))
- {
- return true;
- }
- }
- }
-
- return false;
+ return expression.DescendantNodes().Any(
+ x => x is IdentifierNameSyntax identifierName
+ && identifierName.ToString() == identifierText);
}
///
@@ -208,7 +157,7 @@ namespace Ryujinx.Analyzers
if (EndsWithExpressionText(expressionStatement.Expression, LoggerIdentifier) && ContainsInvocationArgText(expressionStatement.Expression, LogClassIdentifier))
{
// Find catchDeclarationIdentifier in Logger invocation
- if (InvocationContainsIdentifier(expressionStatement.Expression, catchDeclarationIdentifier.Text))
+ if (ContainsIdentifier(expressionStatement.Expression, catchDeclarationIdentifier.Text))
{
exceptionLogged = true;
}
From 81988c9ed8e56c97176038728ee9ba2ad8aaf39a Mon Sep 17 00:00:00 2001
From: TSR Berry <20988865+TSRBerry@users.noreply.github.com>
Date: Tue, 16 Jan 2024 13:48:13 +0100
Subject: [PATCH 7/8] Add a new test and fix for logging statements in child
blocks
---
src/Ryujinx.Analyzers/CatchClauseAnalyzer.cs | 4 +--
.../CatchClauseAnalyzerTests.cs | 30 +++++++++++++++++++
2 files changed, 32 insertions(+), 2 deletions(-)
diff --git a/src/Ryujinx.Analyzers/CatchClauseAnalyzer.cs b/src/Ryujinx.Analyzers/CatchClauseAnalyzer.cs
index 2fbb209a7a..f22be9dfc5 100644
--- a/src/Ryujinx.Analyzers/CatchClauseAnalyzer.cs
+++ b/src/Ryujinx.Analyzers/CatchClauseAnalyzer.cs
@@ -146,9 +146,9 @@ namespace Ryujinx.Analyzers
bool exceptionLogged = false;
// Iterate through all expression statements
- foreach (var statement in catchClauseSyntax.Block.Statements)
+ foreach (var blockNode in catchClauseSyntax.Block.DescendantNodes())
{
- if (statement is not ExpressionStatementSyntax expressionStatement)
+ if (blockNode is not ExpressionStatementSyntax expressionStatement)
{
continue;
}
diff --git a/src/Ryujinx.Tests.Analyzers/CatchClauseAnalyzerTests.cs b/src/Ryujinx.Tests.Analyzers/CatchClauseAnalyzerTests.cs
index 24e09af13d..c5e4c8dab1 100644
--- a/src/Ryujinx.Tests.Analyzers/CatchClauseAnalyzerTests.cs
+++ b/src/Ryujinx.Tests.Analyzers/CatchClauseAnalyzerTests.cs
@@ -214,6 +214,36 @@ public class MyClass
}
}
}
+";
+
+ await Verifier.VerifyAnalyzerAsync(text).ConfigureAwait(false);
+ }
+
+ [Fact]
+ public async Task LogWithIdentifierInSubBlock_NoDiagnostic()
+ {
+ string text = _loggerText + @"
+public class MyClass
+{
+ public void MyMethod9()
+ {
+ try
+ {
+ Console.WriteLine(""test"");
+ }
+ catch (Exception ex)
+ {
+ string testString = ""first time?"";
+
+ if (1 == 1)
+ {
+ Ryujinx.Common.Logging.Logger.Info?.Print(Ryujinx.Common.Logging.LogClass.Application, $""test: {testString} Error: {ex.Message}"");
+ }
+
+ Console.WriteLine(""Test"");
+ }
+ }
+}
";
await Verifier.VerifyAnalyzerAsync(text).ConfigureAwait(false);
From 8449c8e2ab11f21274cd6e9016ef1dfe99f65f45 Mon Sep 17 00:00:00 2001
From: TSR Berry <20988865+TSRBerry@users.noreply.github.com>
Date: Sun, 17 Mar 2024 18:38:59 +0100
Subject: [PATCH 8/8] Ignore catch clauses with throw statements or with "_" as
the identifier
---
src/Ryujinx.Analyzers/CatchClauseAnalyzer.cs | 12 +++
.../CatchClauseAnalyzerTests.cs | 91 +++++++++++++++++--
2 files changed, 95 insertions(+), 8 deletions(-)
diff --git a/src/Ryujinx.Analyzers/CatchClauseAnalyzer.cs b/src/Ryujinx.Analyzers/CatchClauseAnalyzer.cs
index f22be9dfc5..a0500cfeb4 100644
--- a/src/Ryujinx.Analyzers/CatchClauseAnalyzer.cs
+++ b/src/Ryujinx.Analyzers/CatchClauseAnalyzer.cs
@@ -117,6 +117,12 @@ namespace Ryujinx.Analyzers
var catchDeclaration = catchClauseSyntax.Declaration;
+ // Ignore if catch block contains a throw statement.
+ if (catchClauseSyntax.Block.DescendantNodes().Any(x => x is ThrowStatementSyntax))
+ {
+ return;
+ }
+
// Find catch clauses without declaration.
if (catchDeclaration == null)
{
@@ -145,6 +151,12 @@ namespace Ryujinx.Analyzers
var catchDeclarationIdentifier = catchDeclaration.Identifier;
bool exceptionLogged = false;
+ // Ignore if identifier is equal to "_"
+ if (catchDeclarationIdentifier.Text == "_")
+ {
+ return;
+ }
+
// Iterate through all expression statements
foreach (var blockNode in catchClauseSyntax.Block.DescendantNodes())
{
diff --git a/src/Ryujinx.Tests.Analyzers/CatchClauseAnalyzerTests.cs b/src/Ryujinx.Tests.Analyzers/CatchClauseAnalyzerTests.cs
index c5e4c8dab1..32c5d541c2 100644
--- a/src/Ryujinx.Tests.Analyzers/CatchClauseAnalyzerTests.cs
+++ b/src/Ryujinx.Tests.Analyzers/CatchClauseAnalyzerTests.cs
@@ -32,7 +32,7 @@ public class MyClass
}
catch
{
- throw;
+ // Skip
}
}
}
@@ -72,13 +72,88 @@ public class MyClass
await Verifier.VerifyAnalyzerAsync(Text, expected).ConfigureAwait(false);
}
+ [Fact]
+ public async Task CatchWithEmptyThrowStatement_NoDiagnostic()
+ {
+ string text = @"
+using System;
+
+public class MyClass
+{
+ public void MyMethod3()
+ {
+ try
+ {
+ Console.WriteLine(""test"");
+ }
+ catch
+ {
+ throw;
+ }
+ }
+}
+";
+
+ await Verifier.VerifyAnalyzerAsync(text).ConfigureAwait(false);
+ }
+
+ [Fact]
+ public async Task CatchWithIdentifierAndThrowStatement_NoDiagnostic()
+ {
+ string text = @"
+using System;
+
+public class MyClass
+{
+ public void MyMethod4()
+ {
+ try
+ {
+ Console.WriteLine(""test"");
+ }
+ catch (NullReferenceException exception)
+ {
+ throw new InvalidOperationException(""invalid"");
+ }
+ }
+}
+";
+
+ await Verifier.VerifyAnalyzerAsync(text).ConfigureAwait(false);
+ }
+
+ [Fact]
+ public async Task CatchWithIgnoredIdentifier_NoDiagnostic()
+ {
+ string text = @"
+using System;
+
+public class MyClass
+{
+ public void MyMethod5()
+ {
+ try
+ {
+ Console.WriteLine(""test"");
+ }
+ catch (NullReferenceException _)
+ {
+ // Skip
+ }
+ }
+}
+";
+
+ await Verifier.VerifyAnalyzerAsync(text).ConfigureAwait(false);
+ }
+
[Fact]
public async Task LogWithoutCatchIdentifier_WarningDiagnostic()
{
string text = _loggerText + @"
public class MyClass
{
- public void MyMethod3()
+ public void MyMethod6()
{
try
{
@@ -104,7 +179,7 @@ public class MyClass
string text = _loggerText + @"
public class MyClass
{
- public void MyMethod4()
+ public void MyMethod7()
{
try
{
@@ -128,7 +203,7 @@ public class MyClass
string text = _loggerText + @"
public class MyClass
{
- public void MyMethod5()
+ public void MyMethod8()
{
try
{
@@ -152,7 +227,7 @@ public class MyClass
string text = _loggerText + @"
public class MyClass
{
- public void MyMethod6()
+ public void MyMethod9()
{
try
{
@@ -176,7 +251,7 @@ public class MyClass
string text = _loggerText + @"
public class MyClass
{
- public void MyMethod7()
+ public void MyMethod10()
{
try
{
@@ -201,7 +276,7 @@ public class MyClass
string text = _loggerText + @"
public class MyClass
{
- public void MyMethod8()
+ public void MyMethod11()
{
try
{
@@ -225,7 +300,7 @@ public class MyClass
string text = _loggerText + @"
public class MyClass
{
- public void MyMethod9()
+ public void MyMethod12()
{
try
{