From bc37dac80881690800ddb199de9e33b188de6783 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9rald=20Barr=C3=A9?= Date: Fri, 1 Dec 2023 18:03:18 -0500 Subject: [PATCH] New rules to validate method signature when using [UnsafeAccessor] (#657) --- README.md | 2 + docs/README.md | 14 ++ docs/Rules/MA0145.md | 13 + docs/Rules/MA0146.md | 19 ++ .../Internals/ContextExtensions.cs | 155 ++---------- .../Internals/ContextExtensions.g.cs | 228 ++++++++++++++++++ .../Internals/ContextExtensions.tt | 60 +++++ .../Internals/DiagnosticReporter.cs | 45 ++++ .../Meziantou.Analyzer.csproj | 19 ++ src/Meziantou.Analyzer/RuleIdentifiers.cs | 2 + ...ateUnsafeAccessorAttributeUsageAnalyzer.cs | 220 +++++++++++++++++ .../Helpers/ProjectBuilder.Validation.cs | 4 +- ...safeAccessorAttributeUsageAnalyzerTests.cs | 157 ++++++++++++ 13 files changed, 806 insertions(+), 132 deletions(-) create mode 100644 docs/Rules/MA0145.md create mode 100644 docs/Rules/MA0146.md create mode 100644 src/Meziantou.Analyzer/Internals/ContextExtensions.g.cs create mode 100644 src/Meziantou.Analyzer/Internals/ContextExtensions.tt create mode 100644 src/Meziantou.Analyzer/Internals/DiagnosticReporter.cs create mode 100644 src/Meziantou.Analyzer/Rules/ValidateUnsafeAccessorAttributeUsageAnalyzer.cs create mode 100644 tests/Meziantou.Analyzer.Test/Rules/ValidateUnsafeAccessorAttributeUsageAnalyzerTests.cs diff --git a/README.md b/README.md index 0ce643a2f..121140129 100644 --- a/README.md +++ b/README.md @@ -160,6 +160,8 @@ If you are already using other analyzers, you can check [which rules are duplica |[MA0142](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0142.md)|Usage|Use pattern matching instead of equality operators|ℹ️|❌|✔️| |[MA0143](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0143.md)|Design|Primary constructor parameters should be readonly|⚠️|❌|❌| |[MA0144](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0144.md)|Performance|Use System.OperatingSystem to check the current OS|⚠️|✔️|❌| +|[MA0145](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0145.md)|Usage|Signature for \[UnsafeAccessorAttribute\] method is not valid|⚠️|✔️|❌| +|[MA0146](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0146.md)|Usage|Name must be set explicitly on local functions|⚠️|✔️|❌| diff --git a/docs/README.md b/docs/README.md index ebeb63085..3d87b8363 100644 --- a/docs/README.md +++ b/docs/README.md @@ -144,6 +144,8 @@ |[MA0142](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0142.md)|Usage|Use pattern matching instead of equality operators|ℹ️|❌|✔️| |[MA0143](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0143.md)|Design|Primary constructor parameters should be readonly|⚠️|❌|❌| |[MA0144](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0144.md)|Performance|Use System.OperatingSystem to check the current OS|⚠️|✔️|❌| +|[MA0145](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0145.md)|Usage|Signature for \[UnsafeAccessorAttribute\] method is not valid|⚠️|✔️|❌| +|[MA0146](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0146.md)|Usage|Name must be set explicitly on local functions|⚠️|✔️|❌| |Id|Suppressed rule|Justification| |--|---------------|-------------| @@ -582,6 +584,12 @@ dotnet_diagnostic.MA0143.severity = none # MA0144: Use System.OperatingSystem to check the current OS dotnet_diagnostic.MA0144.severity = warning + +# MA0145: Signature for [UnsafeAccessorAttribute] method is not valid +dotnet_diagnostic.MA0145.severity = warning + +# MA0146: Name must be set explicitly on local functions +dotnet_diagnostic.MA0146.severity = warning ``` # .editorconfig - all rules disabled @@ -1015,4 +1023,10 @@ dotnet_diagnostic.MA0143.severity = none # MA0144: Use System.OperatingSystem to check the current OS dotnet_diagnostic.MA0144.severity = none + +# MA0145: Signature for [UnsafeAccessorAttribute] method is not valid +dotnet_diagnostic.MA0145.severity = none + +# MA0146: Name must be set explicitly on local functions +dotnet_diagnostic.MA0146.severity = none ``` diff --git a/docs/Rules/MA0145.md b/docs/Rules/MA0145.md new file mode 100644 index 000000000..3bf5a6185 --- /dev/null +++ b/docs/Rules/MA0145.md @@ -0,0 +1,13 @@ +# MA0145 - Signature for \[UnsafeAccessorAttribute\] method is not valid + +Report some cases where a method decorated with `[UnsafeAccessorAttribute]` is not valid. + +Note: Because some references doesn't expose their private members through Roslyn, it's not possible to validate the full signature. + +````c# +[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "dummy")] +extern static ref int Demo(MyStruct a); // Not compliant as the first parameter is not by ref + +[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "dummy")] +extern static ref int Demo(ref MyStruct a); // ok +```` diff --git a/docs/Rules/MA0146.md b/docs/Rules/MA0146.md new file mode 100644 index 000000000..f7d425ee2 --- /dev/null +++ b/docs/Rules/MA0146.md @@ -0,0 +1,19 @@ +# MA0146 - Name must be set explicitly on local functions + +Local function names are mangle by the compiler, so the `Name` named constructor parameter is required + +````c# +// non compliant +void Sample() +{ + [UnsafeAccessor(UnsafeAccessorKind.Field)] + extern static ref int _Major_(System.Version a); +} + +// Ok +void Sample() +{ + [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_Major")] + extern static ref int _Major_(System.Version a); +} +```` diff --git a/src/Meziantou.Analyzer/Internals/ContextExtensions.cs b/src/Meziantou.Analyzer/Internals/ContextExtensions.cs index a4142f3a1..b0627d26a 100644 --- a/src/Meziantou.Analyzer/Internals/ContextExtensions.cs +++ b/src/Meziantou.Analyzer/Internals/ContextExtensions.cs @@ -2,57 +2,40 @@ using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Operations; namespace Meziantou.Analyzer; -internal static class ContextExtensions +internal static partial class ContextExtensions { - private static Diagnostic CreateDiagnostic(DiagnosticDescriptor descriptor, Location location, ImmutableDictionary? properties, params string?[] messageArgs) + private static Diagnostic CreateDiagnostic(DiagnosticDescriptor descriptor, Location location, ImmutableDictionary? properties, string?[]? messageArgs) { return Diagnostic.Create(descriptor, location, properties, messageArgs); } - public static void ReportDiagnostic(this SyntaxNodeAnalysisContext context, DiagnosticDescriptor descriptor, SyntaxToken syntaxToken, params string?[] messageArgs) - { - ReportDiagnostic(context, descriptor, ImmutableDictionary.Empty, syntaxToken, messageArgs); - } - - public static void ReportDiagnostic(this SyntaxNodeAnalysisContext context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, SyntaxToken syntaxToken, params string?[] messageArgs) - { - context.ReportDiagnostic(CreateDiagnostic(descriptor, syntaxToken.GetLocation(), properties, messageArgs)); - } + public static void ReportDiagnostic(this DiagnosticReporter context, DiagnosticDescriptor descriptor, SyntaxReference syntaxReference, string?[]? messageArgs = null) + => ReportDiagnostic(context, descriptor, ImmutableDictionary.Empty, syntaxReference, messageArgs); - public static void ReportDiagnostic(this SyntaxNodeAnalysisContext context, DiagnosticDescriptor descriptor, SyntaxNode syntaxNode, params string?[] messageArgs) - { - ReportDiagnostic(context, descriptor, ImmutableDictionary.Empty, syntaxNode, messageArgs); - } - - public static void ReportDiagnostic(this SyntaxNodeAnalysisContext context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, SyntaxNode syntaxNode, params string?[] messageArgs) + public static void ReportDiagnostic(this DiagnosticReporter context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, SyntaxReference syntaxReference, string?[]? messageArgs = null) { + var syntaxNode = syntaxReference.GetSyntax(context.CancellationToken); context.ReportDiagnostic(CreateDiagnostic(descriptor, syntaxNode.GetLocation(), properties, messageArgs)); } - public static void ReportDiagnostic(this SyntaxNodeAnalysisContext context, DiagnosticDescriptor descriptor, ISymbol symbol, params string?[] messageArgs) - { - ReportDiagnostic(context, descriptor, ImmutableDictionary.Empty, symbol, messageArgs); - } + public static void ReportDiagnostic(this DiagnosticReporter context, DiagnosticDescriptor descriptor, Location location, string?[]? messageArgs = null) => context.ReportDiagnostic(CreateDiagnostic(descriptor, location, ImmutableDictionary.Empty, messageArgs)); + public static void ReportDiagnostic(this DiagnosticReporter context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, Location location, string?[]? messageArgs = null) => context.ReportDiagnostic(CreateDiagnostic(descriptor, location, properties, messageArgs)); - public static void ReportDiagnostic(this SyntaxNodeAnalysisContext context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, ISymbol symbol, params string?[] messageArgs) - { - foreach (var location in symbol.Locations) - { - context.ReportDiagnostic(CreateDiagnostic(descriptor, location, properties, messageArgs)); - } - } + public static void ReportDiagnostic(this DiagnosticReporter context, DiagnosticDescriptor descriptor, SyntaxNode syntax, string?[]? messageArgs = null) => ReportDiagnostic(context, descriptor, ImmutableDictionary.Empty, syntax.GetLocation(), messageArgs); + public static void ReportDiagnostic(this DiagnosticReporter context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, SyntaxNode syntax, string?[]? messageArgs = null) => ReportDiagnostic(context, descriptor, properties, syntax.GetLocation(), messageArgs); - public static void ReportDiagnostic(this SymbolAnalysisContext context, DiagnosticDescriptor descriptor, ISymbol symbol, params string?[] messageArgs) + public static void ReportDiagnostic(this DiagnosticReporter context, DiagnosticDescriptor descriptor, SyntaxToken token, string?[]? messageArgs = null) => ReportDiagnostic(context, descriptor, ImmutableDictionary.Empty, token.GetLocation(), messageArgs); + public static void ReportDiagnostic(this DiagnosticReporter context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, SyntaxToken syntaxToken, params string?[]? messageArgs) { - ReportDiagnostic(context, descriptor, ImmutableDictionary.Empty, symbol, messageArgs); + context.ReportDiagnostic(CreateDiagnostic(descriptor, syntaxToken.GetLocation(), properties, messageArgs)); } - public static void ReportDiagnostic(this SymbolAnalysisContext context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, ISymbol symbol, params string?[] messageArgs) + public static void ReportDiagnostic(this DiagnosticReporter context, DiagnosticDescriptor descriptor, ISymbol symbol, string?[]? messageArgs = null) => ReportDiagnostic(context, descriptor, ImmutableDictionary.Empty, symbol, messageArgs); + public static void ReportDiagnostic(this DiagnosticReporter context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, ISymbol symbol, string?[]? messageArgs = null) { foreach (var location in symbol.Locations) { @@ -60,119 +43,31 @@ public static void ReportDiagnostic(this SymbolAnalysisContext context, Diagnost } } - public static void ReportDiagnostic(this SymbolAnalysisContext context, DiagnosticDescriptor descriptor, SyntaxReference syntaxReference, params string?[] messageArgs) - { - var syntaxNode = syntaxReference.GetSyntax(context.CancellationToken); - context.ReportDiagnostic(Diagnostic.Create(descriptor, syntaxNode.GetLocation(), ImmutableDictionary.Empty, messageArgs)); - } - - public static void ReportDiagnostic(this SymbolAnalysisContext context, DiagnosticDescriptor descriptor, Location location, params string?[] messageArgs) - { - context.ReportDiagnostic(Diagnostic.Create(descriptor, location, ImmutableDictionary.Empty, messageArgs)); - } - - public static void ReportDiagnostic(this SymbolAnalysisContext context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, Location location, params string?[] messageArgs) - { - context.ReportDiagnostic(CreateDiagnostic(descriptor, location, properties, messageArgs)); - } - - public static void ReportDiagnostic(this OperationAnalysisContext context, DiagnosticDescriptor descriptor, SyntaxToken token, params string?[] messageArgs) - { - ReportDiagnostic(context, descriptor, ImmutableDictionary.Empty, token, messageArgs); - } - - public static void ReportDiagnostic(this OperationAnalysisContext context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, SyntaxToken token, params string?[] messageArgs) - { - context.ReportDiagnostic(CreateDiagnostic(descriptor, token.GetLocation(), properties, messageArgs)); - } - - public static void ReportDiagnostic(this OperationAnalysisContext context, DiagnosticDescriptor descriptor, SyntaxNode node, params string?[] messageArgs) - { - ReportDiagnostic(context, descriptor, ImmutableDictionary.Empty, node, messageArgs); - } - - public static void ReportDiagnostic(this OperationAnalysisContext context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, SyntaxNode node, params string?[] messageArgs) - { - context.ReportDiagnostic(CreateDiagnostic(descriptor, node.GetLocation(), properties, messageArgs)); - } - - public static void ReportDiagnostic(this OperationAnalysisContext context, DiagnosticDescriptor descriptor, IOperation operation, params string?[] messageArgs) - { - ReportDiagnostic(context, descriptor, ImmutableDictionary.Empty, operation, messageArgs); - } - - public static void ReportDiagnostic(this OperationAnalysisContext context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, IOperation operation, params string?[] messageArgs) - { - context.ReportDiagnostic(CreateDiagnostic(descriptor, operation.Syntax.GetLocation(), properties, messageArgs)); - } + public static void ReportDiagnostic(this DiagnosticReporter context, DiagnosticDescriptor descriptor, IOperation operation, string?[]? messageArgs = null) + => ReportDiagnostic(context, descriptor, ImmutableDictionary.Empty, operation, messageArgs); + public static void ReportDiagnostic(this DiagnosticReporter context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, IOperation operation, string?[]? messageArgs = null) + => context.ReportDiagnostic(CreateDiagnostic(descriptor, operation.Syntax.GetLocation(), properties, messageArgs)); - public static void ReportDiagnostic(this OperationAnalysisContext context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, IInvocationOperation operation, DiagnosticReportOptions options, params string?[] messageArgs) + public static void ReportDiagnostic(this DiagnosticReporter context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, ILocalFunctionOperation operation, DiagnosticReportOptions options, string?[]? messageArgs = null) { if (options.HasFlag(DiagnosticReportOptions.ReportOnMethodName) && - operation.Syntax.ChildNodes().FirstOrDefault() is MemberAccessExpressionSyntax memberAccessExpression) + operation.Syntax is LocalFunctionStatementSyntax memberAccessExpression) { - context.ReportDiagnostic(Diagnostic.Create(descriptor, memberAccessExpression.Name.GetLocation(), properties, messageArgs)); + context.ReportDiagnostic(Diagnostic.Create(descriptor, memberAccessExpression.Identifier.GetLocation(), properties, messageArgs)); return; } context.ReportDiagnostic(descriptor, properties, operation, messageArgs); } - - public static void ReportDiagnostic(this OperationAnalysisContext context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, ILocalFunctionOperation operation, DiagnosticReportOptions options, params string?[] messageArgs) + public static void ReportDiagnostic(this DiagnosticReporter context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, IInvocationOperation operation, DiagnosticReportOptions options, params string?[]? messageArgs) { if (options.HasFlag(DiagnosticReportOptions.ReportOnMethodName) && - operation.Syntax is LocalFunctionStatementSyntax memberAccessExpression) + operation.Syntax.ChildNodes().FirstOrDefault() is MemberAccessExpressionSyntax memberAccessExpression) { - context.ReportDiagnostic(Diagnostic.Create(descriptor, memberAccessExpression.Identifier.GetLocation(), properties, messageArgs)); + context.ReportDiagnostic(Diagnostic.Create(descriptor, memberAccessExpression.Name.GetLocation(), properties, messageArgs)); return; } context.ReportDiagnostic(descriptor, properties, operation, messageArgs); } - - public static void ReportDiagnostic(this CompilationAnalysisContext context, DiagnosticDescriptor descriptor, ISymbol symbol, params string?[] messageArgs) - { - ReportDiagnostic(context, descriptor, ImmutableDictionary.Empty, symbol, messageArgs); - } - - public static void ReportDiagnostic(this CompilationAnalysisContext context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, ISymbol symbol, params string?[] messageArgs) - { - foreach (var location in symbol.Locations) - { - context.ReportDiagnostic(CreateDiagnostic(descriptor, location, properties, messageArgs)); - } - } - - public static void ReportDiagnostic(this OperationBlockAnalysisContext context, DiagnosticDescriptor descriptor, ISymbol symbol, params string?[] messageArgs) - { - ReportDiagnostic(context, descriptor, ImmutableDictionary.Empty, symbol, messageArgs); - } - - public static void ReportDiagnostic(this OperationBlockAnalysisContext context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, ISymbol symbol, params string?[] messageArgs) - { - foreach (var location in symbol.Locations) - { - ReportDiagnostic(context, descriptor, properties, location, messageArgs); - } - } - - public static void ReportDiagnostic(this OperationBlockAnalysisContext context, DiagnosticDescriptor descriptor, SyntaxNode syntax, params string?[] messageArgs) - { - ReportDiagnostic(context, descriptor, ImmutableDictionary.Empty, syntax.GetLocation(), messageArgs); - } - - public static void ReportDiagnostic(this OperationBlockAnalysisContext context, DiagnosticDescriptor descriptor, SyntaxToken token, params string?[] messageArgs) - { - ReportDiagnostic(context, descriptor, ImmutableDictionary.Empty, token.GetLocation(), messageArgs); - } - - public static void ReportDiagnostic(this OperationBlockAnalysisContext context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, SyntaxNode syntax, params string?[] messageArgs) - { - ReportDiagnostic(context, descriptor, properties, syntax.GetLocation(), messageArgs); - } - - public static void ReportDiagnostic(this OperationBlockAnalysisContext context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, Location location, params string?[] messageArgs) - { - context.ReportDiagnostic(CreateDiagnostic(descriptor, location, properties, messageArgs)); - } } diff --git a/src/Meziantou.Analyzer/Internals/ContextExtensions.g.cs b/src/Meziantou.Analyzer/Internals/ContextExtensions.g.cs new file mode 100644 index 000000000..b7fd92fb8 --- /dev/null +++ b/src/Meziantou.Analyzer/Internals/ContextExtensions.g.cs @@ -0,0 +1,228 @@ + + + + + +#nullable enable +using System; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; + +namespace Meziantou.Analyzer; + +internal static partial class ContextExtensions +{ + + public static void ReportDiagnostic(this SyntaxNodeAnalysisContext context, DiagnosticDescriptor descriptor, SyntaxToken syntaxToken, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, syntaxToken, messageArgs); + + public static void ReportDiagnostic(this SyntaxNodeAnalysisContext context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, SyntaxToken syntaxToken, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, properties, syntaxToken, messageArgs); + + public static void ReportDiagnostic(this SyntaxNodeAnalysisContext context, DiagnosticDescriptor descriptor, SyntaxNode syntaxNode, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, syntaxNode, messageArgs); + + public static void ReportDiagnostic(this SyntaxNodeAnalysisContext context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, SyntaxNode syntaxNode, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, properties, syntaxNode, messageArgs); + + public static void ReportDiagnostic(this SyntaxNodeAnalysisContext context, DiagnosticDescriptor descriptor, ISymbol symbol, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, ImmutableDictionary.Empty, symbol, messageArgs); + + public static void ReportDiagnostic(this SyntaxNodeAnalysisContext context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, ISymbol symbol, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, properties, symbol, messageArgs); + + public static void ReportDiagnostic(this SyntaxNodeAnalysisContext context, DiagnosticDescriptor descriptor, Location location, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, location, messageArgs); + + public static void ReportDiagnostic(this SyntaxNodeAnalysisContext context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, Location location, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, properties, location, messageArgs); + + public static void ReportDiagnostic(this SyntaxNodeAnalysisContext context, DiagnosticDescriptor descriptor, SyntaxReference syntaxReference, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, syntaxReference, messageArgs); + + public static void ReportDiagnostic(this SyntaxNodeAnalysisContext context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, SyntaxReference syntaxReference, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, properties, syntaxReference, messageArgs); + + public static void ReportDiagnostic(this SyntaxNodeAnalysisContext context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, IInvocationOperation operation, DiagnosticReportOptions options, params string?[] messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, properties, operation, options, messageArgs); + + public static void ReportDiagnostic(this SyntaxNodeAnalysisContext context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, ILocalFunctionOperation operation, DiagnosticReportOptions options, params string?[] messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, properties, operation, options, messageArgs); + + public static void ReportDiagnostic(this SyntaxNodeAnalysisContext context, DiagnosticDescriptor descriptor, IOperation operation, params string?[] messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, operation, messageArgs); + + public static void ReportDiagnostic(this SyntaxNodeAnalysisContext context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, IOperation operation, params string?[] messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, properties, operation, messageArgs); + + public static void ReportDiagnostic(this SymbolAnalysisContext context, DiagnosticDescriptor descriptor, SyntaxToken syntaxToken, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, syntaxToken, messageArgs); + + public static void ReportDiagnostic(this SymbolAnalysisContext context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, SyntaxToken syntaxToken, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, properties, syntaxToken, messageArgs); + + public static void ReportDiagnostic(this SymbolAnalysisContext context, DiagnosticDescriptor descriptor, SyntaxNode syntaxNode, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, syntaxNode, messageArgs); + + public static void ReportDiagnostic(this SymbolAnalysisContext context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, SyntaxNode syntaxNode, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, properties, syntaxNode, messageArgs); + + public static void ReportDiagnostic(this SymbolAnalysisContext context, DiagnosticDescriptor descriptor, ISymbol symbol, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, ImmutableDictionary.Empty, symbol, messageArgs); + + public static void ReportDiagnostic(this SymbolAnalysisContext context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, ISymbol symbol, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, properties, symbol, messageArgs); + + public static void ReportDiagnostic(this SymbolAnalysisContext context, DiagnosticDescriptor descriptor, Location location, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, location, messageArgs); + + public static void ReportDiagnostic(this SymbolAnalysisContext context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, Location location, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, properties, location, messageArgs); + + public static void ReportDiagnostic(this SymbolAnalysisContext context, DiagnosticDescriptor descriptor, SyntaxReference syntaxReference, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, syntaxReference, messageArgs); + + public static void ReportDiagnostic(this SymbolAnalysisContext context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, SyntaxReference syntaxReference, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, properties, syntaxReference, messageArgs); + + public static void ReportDiagnostic(this SymbolAnalysisContext context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, IInvocationOperation operation, DiagnosticReportOptions options, params string?[] messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, properties, operation, options, messageArgs); + + public static void ReportDiagnostic(this SymbolAnalysisContext context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, ILocalFunctionOperation operation, DiagnosticReportOptions options, params string?[] messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, properties, operation, options, messageArgs); + + public static void ReportDiagnostic(this SymbolAnalysisContext context, DiagnosticDescriptor descriptor, IOperation operation, params string?[] messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, operation, messageArgs); + + public static void ReportDiagnostic(this SymbolAnalysisContext context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, IOperation operation, params string?[] messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, properties, operation, messageArgs); + + public static void ReportDiagnostic(this OperationAnalysisContext context, DiagnosticDescriptor descriptor, SyntaxToken syntaxToken, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, syntaxToken, messageArgs); + + public static void ReportDiagnostic(this OperationAnalysisContext context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, SyntaxToken syntaxToken, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, properties, syntaxToken, messageArgs); + + public static void ReportDiagnostic(this OperationAnalysisContext context, DiagnosticDescriptor descriptor, SyntaxNode syntaxNode, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, syntaxNode, messageArgs); + + public static void ReportDiagnostic(this OperationAnalysisContext context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, SyntaxNode syntaxNode, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, properties, syntaxNode, messageArgs); + + public static void ReportDiagnostic(this OperationAnalysisContext context, DiagnosticDescriptor descriptor, ISymbol symbol, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, ImmutableDictionary.Empty, symbol, messageArgs); + + public static void ReportDiagnostic(this OperationAnalysisContext context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, ISymbol symbol, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, properties, symbol, messageArgs); + + public static void ReportDiagnostic(this OperationAnalysisContext context, DiagnosticDescriptor descriptor, Location location, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, location, messageArgs); + + public static void ReportDiagnostic(this OperationAnalysisContext context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, Location location, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, properties, location, messageArgs); + + public static void ReportDiagnostic(this OperationAnalysisContext context, DiagnosticDescriptor descriptor, SyntaxReference syntaxReference, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, syntaxReference, messageArgs); + + public static void ReportDiagnostic(this OperationAnalysisContext context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, SyntaxReference syntaxReference, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, properties, syntaxReference, messageArgs); + + public static void ReportDiagnostic(this OperationAnalysisContext context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, IInvocationOperation operation, DiagnosticReportOptions options, params string?[] messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, properties, operation, options, messageArgs); + + public static void ReportDiagnostic(this OperationAnalysisContext context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, ILocalFunctionOperation operation, DiagnosticReportOptions options, params string?[] messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, properties, operation, options, messageArgs); + + public static void ReportDiagnostic(this OperationAnalysisContext context, DiagnosticDescriptor descriptor, IOperation operation, params string?[] messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, operation, messageArgs); + + public static void ReportDiagnostic(this OperationAnalysisContext context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, IOperation operation, params string?[] messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, properties, operation, messageArgs); + + public static void ReportDiagnostic(this OperationBlockAnalysisContext context, DiagnosticDescriptor descriptor, SyntaxToken syntaxToken, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, syntaxToken, messageArgs); + + public static void ReportDiagnostic(this OperationBlockAnalysisContext context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, SyntaxToken syntaxToken, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, properties, syntaxToken, messageArgs); + + public static void ReportDiagnostic(this OperationBlockAnalysisContext context, DiagnosticDescriptor descriptor, SyntaxNode syntaxNode, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, syntaxNode, messageArgs); + + public static void ReportDiagnostic(this OperationBlockAnalysisContext context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, SyntaxNode syntaxNode, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, properties, syntaxNode, messageArgs); + + public static void ReportDiagnostic(this OperationBlockAnalysisContext context, DiagnosticDescriptor descriptor, ISymbol symbol, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, ImmutableDictionary.Empty, symbol, messageArgs); + + public static void ReportDiagnostic(this OperationBlockAnalysisContext context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, ISymbol symbol, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, properties, symbol, messageArgs); + + public static void ReportDiagnostic(this OperationBlockAnalysisContext context, DiagnosticDescriptor descriptor, Location location, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, location, messageArgs); + + public static void ReportDiagnostic(this OperationBlockAnalysisContext context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, Location location, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, properties, location, messageArgs); + + public static void ReportDiagnostic(this OperationBlockAnalysisContext context, DiagnosticDescriptor descriptor, SyntaxReference syntaxReference, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, syntaxReference, messageArgs); + + public static void ReportDiagnostic(this OperationBlockAnalysisContext context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, SyntaxReference syntaxReference, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, properties, syntaxReference, messageArgs); + + public static void ReportDiagnostic(this OperationBlockAnalysisContext context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, IInvocationOperation operation, DiagnosticReportOptions options, params string?[] messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, properties, operation, options, messageArgs); + + public static void ReportDiagnostic(this OperationBlockAnalysisContext context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, ILocalFunctionOperation operation, DiagnosticReportOptions options, params string?[] messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, properties, operation, options, messageArgs); + + public static void ReportDiagnostic(this OperationBlockAnalysisContext context, DiagnosticDescriptor descriptor, IOperation operation, params string?[] messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, operation, messageArgs); + + public static void ReportDiagnostic(this OperationBlockAnalysisContext context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, IOperation operation, params string?[] messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, properties, operation, messageArgs); + + public static void ReportDiagnostic(this CompilationAnalysisContext context, DiagnosticDescriptor descriptor, SyntaxToken syntaxToken, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, syntaxToken, messageArgs); + + public static void ReportDiagnostic(this CompilationAnalysisContext context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, SyntaxToken syntaxToken, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, properties, syntaxToken, messageArgs); + + public static void ReportDiagnostic(this CompilationAnalysisContext context, DiagnosticDescriptor descriptor, SyntaxNode syntaxNode, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, syntaxNode, messageArgs); + + public static void ReportDiagnostic(this CompilationAnalysisContext context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, SyntaxNode syntaxNode, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, properties, syntaxNode, messageArgs); + + public static void ReportDiagnostic(this CompilationAnalysisContext context, DiagnosticDescriptor descriptor, ISymbol symbol, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, ImmutableDictionary.Empty, symbol, messageArgs); + + public static void ReportDiagnostic(this CompilationAnalysisContext context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, ISymbol symbol, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, properties, symbol, messageArgs); + + public static void ReportDiagnostic(this CompilationAnalysisContext context, DiagnosticDescriptor descriptor, Location location, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, location, messageArgs); + + public static void ReportDiagnostic(this CompilationAnalysisContext context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, Location location, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, properties, location, messageArgs); + + public static void ReportDiagnostic(this CompilationAnalysisContext context, DiagnosticDescriptor descriptor, SyntaxReference syntaxReference, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, syntaxReference, messageArgs); + + public static void ReportDiagnostic(this CompilationAnalysisContext context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, SyntaxReference syntaxReference, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, properties, syntaxReference, messageArgs); + + public static void ReportDiagnostic(this CompilationAnalysisContext context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, IInvocationOperation operation, DiagnosticReportOptions options, params string?[] messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, properties, operation, options, messageArgs); + + public static void ReportDiagnostic(this CompilationAnalysisContext context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, ILocalFunctionOperation operation, DiagnosticReportOptions options, params string?[] messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, properties, operation, options, messageArgs); + + public static void ReportDiagnostic(this CompilationAnalysisContext context, DiagnosticDescriptor descriptor, IOperation operation, params string?[] messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, operation, messageArgs); + + public static void ReportDiagnostic(this CompilationAnalysisContext context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, IOperation operation, params string?[] messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, properties, operation, messageArgs); + +} diff --git a/src/Meziantou.Analyzer/Internals/ContextExtensions.tt b/src/Meziantou.Analyzer/Internals/ContextExtensions.tt new file mode 100644 index 000000000..a12fe5026 --- /dev/null +++ b/src/Meziantou.Analyzer/Internals/ContextExtensions.tt @@ -0,0 +1,60 @@ +<#@ template debug="true" hostspecific="false" language="C#" #> +<#@ output extension=".g.cs" #> +<#@ assembly name="System.Linq" #> +<#@ import namespace="System.Linq" #> + +#nullable enable +using System; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; + +namespace Meziantou.Analyzer; + +internal static partial class ContextExtensions +{ +<# foreach (string type in new string[] { "SyntaxNodeAnalysisContext", "SymbolAnalysisContext", "OperationAnalysisContext", "OperationBlockAnalysisContext", "CompilationAnalysisContext" }) { #> + public static void ReportDiagnostic(this <#= type #> context, DiagnosticDescriptor descriptor, SyntaxToken syntaxToken, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, syntaxToken, messageArgs); + + public static void ReportDiagnostic(this <#= type #> context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, SyntaxToken syntaxToken, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, properties, syntaxToken, messageArgs); + + public static void ReportDiagnostic(this <#= type #> context, DiagnosticDescriptor descriptor, SyntaxNode syntaxNode, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, syntaxNode, messageArgs); + + public static void ReportDiagnostic(this <#= type #> context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, SyntaxNode syntaxNode, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, properties, syntaxNode, messageArgs); + + public static void ReportDiagnostic(this <#= type #> context, DiagnosticDescriptor descriptor, ISymbol symbol, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, ImmutableDictionary.Empty, symbol, messageArgs); + + public static void ReportDiagnostic(this <#= type #> context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, ISymbol symbol, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, properties, symbol, messageArgs); + + public static void ReportDiagnostic(this <#= type #> context, DiagnosticDescriptor descriptor, Location location, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, location, messageArgs); + + public static void ReportDiagnostic(this <#= type #> context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, Location location, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, properties, location, messageArgs); + + public static void ReportDiagnostic(this <#= type #> context, DiagnosticDescriptor descriptor, SyntaxReference syntaxReference, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, syntaxReference, messageArgs); + + public static void ReportDiagnostic(this <#= type #> context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, SyntaxReference syntaxReference, params string?[]? messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, properties, syntaxReference, messageArgs); + + public static void ReportDiagnostic(this <#= type #> context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, IInvocationOperation operation, DiagnosticReportOptions options, params string?[] messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, properties, operation, options, messageArgs); + + public static void ReportDiagnostic(this <#= type #> context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, ILocalFunctionOperation operation, DiagnosticReportOptions options, params string?[] messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, properties, operation, options, messageArgs); + + public static void ReportDiagnostic(this <#= type #> context, DiagnosticDescriptor descriptor, IOperation operation, params string?[] messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, operation, messageArgs); + + public static void ReportDiagnostic(this <#= type #> context, DiagnosticDescriptor descriptor, ImmutableDictionary? properties, IOperation operation, params string?[] messageArgs) + => ReportDiagnostic(new DiagnosticReporter(context), descriptor, properties, operation, messageArgs); +<# } #> +} diff --git a/src/Meziantou.Analyzer/Internals/DiagnosticReporter.cs b/src/Meziantou.Analyzer/Internals/DiagnosticReporter.cs new file mode 100644 index 000000000..06426833c --- /dev/null +++ b/src/Meziantou.Analyzer/Internals/DiagnosticReporter.cs @@ -0,0 +1,45 @@ +using System; +using System.Threading; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Meziantou.Analyzer; + +internal readonly struct DiagnosticReporter +{ + private readonly Action _reportDiagnostic; + + public DiagnosticReporter(SymbolAnalysisContext context) + { + _reportDiagnostic = context.ReportDiagnostic; + CancellationToken = context.CancellationToken; + } + + public DiagnosticReporter(OperationAnalysisContext context) + { + _reportDiagnostic = context.ReportDiagnostic; + CancellationToken = context.CancellationToken; + } + + public DiagnosticReporter(OperationBlockAnalysisContext context) + { + _reportDiagnostic = context.ReportDiagnostic; + CancellationToken = context.CancellationToken; + } + + public DiagnosticReporter(SyntaxNodeAnalysisContext context) + { + _reportDiagnostic = context.ReportDiagnostic; + CancellationToken = context.CancellationToken; + } + + public DiagnosticReporter(CompilationAnalysisContext context) + { + _reportDiagnostic = context.ReportDiagnostic; + CancellationToken = context.CancellationToken; + } + + public CancellationToken CancellationToken { get; } + + public void ReportDiagnostic(Diagnostic diagnostic) => _reportDiagnostic(diagnostic); +} diff --git a/src/Meziantou.Analyzer/Meziantou.Analyzer.csproj b/src/Meziantou.Analyzer/Meziantou.Analyzer.csproj index a384acb62..148ade081 100644 --- a/src/Meziantou.Analyzer/Meziantou.Analyzer.csproj +++ b/src/Meziantou.Analyzer/Meziantou.Analyzer.csproj @@ -42,4 +42,23 @@ + + + + TextTemplatingFileGenerator + ContextExtensions.g.cs + + + + + + + + + + True + True + ContextExtensions.tt + + \ No newline at end of file diff --git a/src/Meziantou.Analyzer/RuleIdentifiers.cs b/src/Meziantou.Analyzer/RuleIdentifiers.cs index ec74d0a82..aa96aae07 100644 --- a/src/Meziantou.Analyzer/RuleIdentifiers.cs +++ b/src/Meziantou.Analyzer/RuleIdentifiers.cs @@ -147,6 +147,8 @@ internal static class RuleIdentifiers public const string UsePatternMatchingForNullEquality = "MA0142"; public const string PrimaryConstructorParameterShouldBeReadOnly = "MA0143"; public const string UseOperatingSystemInsteadOfRuntimeInformation = "MA0144"; + public const string UnsafeAccessorAttribute_InvalidSignature = "MA0145"; + public const string UnsafeAccessorAttribute_NameMustBeSet = "MA0146"; public static string GetHelpUri(string identifier) { diff --git a/src/Meziantou.Analyzer/Rules/ValidateUnsafeAccessorAttributeUsageAnalyzer.cs b/src/Meziantou.Analyzer/Rules/ValidateUnsafeAccessorAttributeUsageAnalyzer.cs new file mode 100644 index 000000000..c2d5d35a4 --- /dev/null +++ b/src/Meziantou.Analyzer/Rules/ValidateUnsafeAccessorAttributeUsageAnalyzer.cs @@ -0,0 +1,220 @@ +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; + +namespace Meziantou.Analyzer.Rules; + +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public sealed class ValidateUnsafeAccessorAttributeUsageAnalyzer : DiagnosticAnalyzer +{ + private static readonly DiagnosticDescriptor s_ruleInvalidSignature = new( + RuleIdentifiers.UnsafeAccessorAttribute_InvalidSignature, + title: "Signature for [UnsafeAccessorAttribute] method is not valid", + messageFormat: "Signature for [UnsafeAccessorAttribute] method is not valid: {0}", + RuleCategories.Usage, + DiagnosticSeverity.Warning, + isEnabledByDefault: true, + description: "", + helpLinkUri: RuleIdentifiers.GetHelpUri(RuleIdentifiers.UnsafeAccessorAttribute_InvalidSignature)); + + private static readonly DiagnosticDescriptor s_ruleNameMustBeSet = new( + RuleIdentifiers.UnsafeAccessorAttribute_NameMustBeSet, + title: "Name must be set explicitly on local functions", + messageFormat: "Name must be set explicitly on local function", + RuleCategories.Usage, + DiagnosticSeverity.Warning, + isEnabledByDefault: true, + description: "", + helpLinkUri: RuleIdentifiers.GetHelpUri(RuleIdentifiers.UnsafeAccessorAttribute_NameMustBeSet)); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(s_ruleInvalidSignature, s_ruleNameMustBeSet); + + public override void Initialize(AnalysisContext context) + { + context.EnableConcurrentExecution(); + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + + context.RegisterCompilationStartAction(context => + { + var attributeSymbol = context.Compilation.GetBestTypeByMetadataName("System.Runtime.CompilerServices.UnsafeAccessorAttribute"); + if (attributeSymbol is null) + return; + + context.RegisterSymbolAction(context => AnalyzeMethodSymbol(context, attributeSymbol), SymbolKind.Method); + context.RegisterOperationAction(context => AnalyzeLocalFunctions(context, attributeSymbol), OperationKind.LocalFunction); + }); + } + + private static void AnalyzeLocalFunctions(OperationAnalysisContext context, INamedTypeSymbol attributeSymbol) + { + var operation = (ILocalFunctionOperation)context.Operation; + var symbol = operation.Symbol; + AnalyzeMethodSymbol(symbol, attributeSymbol, new(context)); + } + + private static void AnalyzeMethodSymbol(SymbolAnalysisContext context, INamedTypeSymbol attributeSymbol) + { + var symbol = (IMethodSymbol)context.Symbol; + AnalyzeMethodSymbol(symbol, attributeSymbol, new(context)); + } + + // https://learn.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.unsafeaccessorattribute + private static void AnalyzeMethodSymbol(IMethodSymbol methodSymbol, INamedTypeSymbol attributeSymbol, DiagnosticReporter diagnosticReporter) + { + var attribute = methodSymbol.GetAttribute(attributeSymbol, inherits: false); + if (attribute is null) + return; + + if (attribute.ConstructorArguments.IsEmpty) + return; // Invalid usage + + if (attribute.ConstructorArguments[0].Value is not int accessorKindInt) + return; + + if (!methodSymbol.IsExtern) + { + diagnosticReporter.ReportDiagnostic(s_ruleInvalidSignature, methodSymbol, messageArgs: ["method must be extern static"]); + return; + } + + var explicitName = GetName(attribute); + if (explicitName is null && methodSymbol.MethodKind is MethodKind.LocalFunction) + { + diagnosticReporter.ReportDiagnostic(s_ruleNameMustBeSet, methodSymbol); + return; + } + + var accessorKind = (UnsafeAccessorKind)accessorKindInt; + if (methodSymbol.Parameters.IsEmpty && accessorKind is UnsafeAccessorKind.Field or UnsafeAccessorKind.StaticField or UnsafeAccessorKind.Method or UnsafeAccessorKind.StaticMethod) + { + diagnosticReporter.ReportDiagnostic(s_ruleInvalidSignature, methodSymbol, messageArgs: ["method must have at least one parameter"]); + return; + } + + var type = methodSymbol.Parameters[0].Type; + + if (accessorKind is UnsafeAccessorKind.Field or UnsafeAccessorKind.StaticField && methodSymbol.ReturnsVoid) + { + diagnosticReporter.ReportDiagnostic(s_ruleInvalidSignature, methodSymbol, messageArgs: ["return type does not match the field type"]); + return; + } + + if (accessorKind is UnsafeAccessorKind.Field or UnsafeAccessorKind.StaticField && !IsRefOrRefReadOnly(methodSymbol.RefKind)) + { + diagnosticReporter.ReportDiagnostic(s_ruleInvalidSignature, methodSymbol, messageArgs: ["must return by ref"]); + return; + } + + if (accessorKind is UnsafeAccessorKind.Field or UnsafeAccessorKind.StaticField && methodSymbol.Parameters.Length != 1) + { + diagnosticReporter.ReportDiagnostic(s_ruleInvalidSignature, methodSymbol, messageArgs: ["method must have a single parameter"]); + return; + } + + if (accessorKind is UnsafeAccessorKind.Field && !methodSymbol.ReturnsVoid && !methodSymbol.ReturnsByRef && !methodSymbol.ReturnsByRefReadonly) + { + diagnosticReporter.ReportDiagnostic(s_ruleInvalidSignature, methodSymbol, messageArgs: ["method must be extern static"]); + return; + } + + // When struct, first parameter must be by ref if field or method + if (accessorKind is UnsafeAccessorKind.Method or UnsafeAccessorKind.Field && methodSymbol.Parameters[0].Type.IsValueType && !IsRefOrRefReadOnly(methodSymbol.Parameters[0].RefKind)) + { + diagnosticReporter.ReportDiagnostic(s_ruleInvalidSignature, methodSymbol, messageArgs: ["the first parameter must be ref"]); + return; + } + + // Roslyn doesn't expose private members from other assemblies + //switch (accessorKind) + //{ + // case UnsafeAccessorKind.Method: + // if (!type.GetMembers(memberName).Where(m => m is IMethodSymbol method && !method.IsStatic).Any()) + // { + // diagnosticReporter.ReportDiagnostic(s_ruleMemberNotFound, methodSymbol, messageArgs: [memberName, type.ToDisplayString()]); + // return; + // } + // break; + // case UnsafeAccessorKind.StaticMethod: + // if (!type.GetMembers(memberName).Where(m => m is IMethodSymbol method && method.IsStatic).Any()) + // { + // diagnosticReporter.ReportDiagnostic(s_ruleMemberNotFound, methodSymbol, messageArgs: [memberName, type.ToDisplayString()]); + // return; + // } + // break; + // case UnsafeAccessorKind.Field: + // if (!type.GetMembers(memberName).Where(m => m is IFieldSymbol method && !method.IsStatic).Any()) + // { + // diagnosticReporter.ReportDiagnostic(s_ruleMemberNotFound, methodSymbol, messageArgs: [memberName, type.ToDisplayString()]); + // return; + // } + // break; + // case UnsafeAccessorKind.StaticField: + // if (!type.GetMembers(memberName).Where(m => m is IFieldSymbol method && method.IsStatic).Any()) + // { + // diagnosticReporter.ReportDiagnostic(s_ruleMemberNotFound, methodSymbol, messageArgs: [memberName, type.ToDisplayString()]); + // return; + // } + // break; + //} + } + + private static bool IsRefOrRefReadOnly(RefKind kind) + { + if (kind is RefKind.Ref or RefKind.In) + return true; + +#if CSHARP12_OR_GREATER + if (kind is RefKind.RefReadOnlyParameter) + return true; +#endif + return false; + } + + private static string? GetName(AttributeData data) + { + foreach (var prop in data.NamedArguments) + { + if (prop.Key == "Name") + { + if (prop.Value.IsNull) + return null; + + if (prop.Value.Kind is TypedConstantKind.Primitive && prop.Value.Value is string str) + return str; + + break; + } + } + + return null; + } + + private enum UnsafeAccessorKind + { + /// + /// Provide access to a constructor. + /// + Constructor, + + /// + /// Provide access to a method. + /// + Method, + + /// + /// Provide access to a static method. + /// + StaticMethod, + + /// + /// Provide access to a field. + /// + Field, + + /// + /// Provide access to a static field. + /// + StaticField, + }; +} diff --git a/tests/Meziantou.Analyzer.Test/Helpers/ProjectBuilder.Validation.cs b/tests/Meziantou.Analyzer.Test/Helpers/ProjectBuilder.Validation.cs index df9cb9beb..a9d414dc6 100644 --- a/tests/Meziantou.Analyzer.Test/Helpers/ProjectBuilder.Validation.cs +++ b/tests/Meziantou.Analyzer.Test/Helpers/ProjectBuilder.Validation.cs @@ -273,7 +273,7 @@ private async Task GetSortedDiagnosticsFromDocuments(IList(); foreach (var project in projects) { - var options = new CSharpCompilationOptions(OutputKind, allowUnsafe: true); + var options = new CSharpCompilationOptions(OutputKind, allowUnsafe: true, metadataImportOptions: MetadataImportOptions.All); // TODO allow configuration // Enable diagnostic options = options.WithSpecificDiagnosticOptions(analyzers.SelectMany(analyzer => analyzer.SupportedDiagnostics.Select(diag => new KeyValuePair(diag.Id, GetReportDiagnostic(diag))))); @@ -533,7 +533,7 @@ private async Task ApplyFix(Document document, CodeAction codeAction, if (mustCompile) { - var options = new CSharpCompilationOptions(OutputKind, allowUnsafe: true); + var options = new CSharpCompilationOptions(OutputKind, allowUnsafe: true, metadataImportOptions: MetadataImportOptions.All); var project = solution.Projects.Single(); var compilation = (await project.GetCompilationAsync().ConfigureAwait(false)).WithOptions(options); diff --git a/tests/Meziantou.Analyzer.Test/Rules/ValidateUnsafeAccessorAttributeUsageAnalyzerTests.cs b/tests/Meziantou.Analyzer.Test/Rules/ValidateUnsafeAccessorAttributeUsageAnalyzerTests.cs new file mode 100644 index 000000000..f7ed61d6a --- /dev/null +++ b/tests/Meziantou.Analyzer.Test/Rules/ValidateUnsafeAccessorAttributeUsageAnalyzerTests.cs @@ -0,0 +1,157 @@ +using System.Threading.Tasks; +using Meziantou.Analyzer.Rules; +using TestHelper; +using Xunit; + +namespace Meziantou.Analyzer.Test.Rules; +public class ValidateUnsafeAccessorAttributeUsageAnalyzerTests +{ + private static ProjectBuilder CreateProjectBuilder() + { + return new ProjectBuilder() + .WithTargetFramework(TargetFramework.Net8_0) + .WithAnalyzer(); + } + + [Fact] + public async Task NotExternStaticMethod() + { + await CreateProjectBuilder() + .WithSourceCode(""" + using System.Runtime.CompilerServices; + class Sample + { + [System.Runtime.CompilerServices.UnsafeAccessor(System.Runtime.CompilerServices.UnsafeAccessorKind.StaticMethod)] + void [||]A() { } + } + """) + .ValidateAsync(); + } + + [Fact] + public async Task LocalFunction_WithoutNameParameter() + { + await CreateProjectBuilder() + .WithSourceCode(""" + using System.Runtime.CompilerServices; + class Sample + { + void A() + { + // Local function name are mangle by the compiler, so the Name property is required + [UnsafeAccessor(UnsafeAccessorKind.Field)] + extern static ref int [||]B(System.Version a); + } + } + """) + .ValidateAsync(); + } + + [Fact] + public async Task LocalFunction_WithNameProperty() + { + await CreateProjectBuilder() + .WithSourceCode(""" + using System.Runtime.CompilerServices; + class Sample + { + void A() + { + [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_Major")] + extern static ref int B(System.Version a); + } + } + """) + .ValidateAsync(); + } + + [Fact] + public async Task Field_TooManyParameters() + { + await CreateProjectBuilder() + .WithSourceCode(""" + using System.Runtime.CompilerServices; + class Sample + { + void A() + { + [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_Major")] + extern static ref int [||]B(System.Version a, int b); + } + } + """) + .ValidateAsync(); + } + + [Fact] + public async Task Field_ReturnVoid() + { + await CreateProjectBuilder() + .WithSourceCode(""" + using System.Runtime.CompilerServices; + class Sample + { + void A() + { + [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_Major")] + extern static void [||]B(System.Version a); + } + } + """) + .ValidateAsync(); + } + + [Fact] + public async Task Field_DoesNotReturnByRef() + { + await CreateProjectBuilder() + .WithSourceCode(""" + using System.Runtime.CompilerServices; + class Sample + { + void A() + { + [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_Major")] + extern static int [||]B(System.Version a); + } + } + """) + .ValidateAsync(); + } + + [Fact] + public async Task Field_FirstParameterNotByRefForStruct() + { + await CreateProjectBuilder() + .WithSourceCode(""" + using System.Runtime.CompilerServices; + class Sample + { + void A() + { + [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_Major")] + extern static ref int [||]B(System.Int32 a); + } + } + """) + .ValidateAsync(); + } + + [Fact] + public async Task Field_FirstParameterByRefForStruct() + { + await CreateProjectBuilder() + .WithSourceCode(""" + using System.Runtime.CompilerServices; + class Sample + { + void A() + { + [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_Major")] + extern static ref int B(ref System.Int32 a); + } + } + """) + .ValidateAsync(); + } +}