From 5cdb8e14c5dc67864f5da2eccd07158865d42522 Mon Sep 17 00:00:00 2001 From: Tim Pohlmann Date: Tue, 20 Jun 2023 09:40:31 +0200 Subject: [PATCH 1/5] Add scaffolding and tests --- analyzers/rspec/cs/S6640.html | 59 ++++++++++++++ analyzers/rspec/cs/S6640.json | 15 ++++ analyzers/rspec/cs/Sonar_way_profile.json | 3 +- .../Rules/Hotspots/UnsafeCodeBlocks.cs | 45 +++++++++++ .../PackagingTests/RuleTypeMappingCS.cs | 2 +- .../Rules/Hotspots/UnsafeCodeBlocksTest.cs | 33 ++++++++ .../TestCases/Hotspots/UnsafeCodeBlocks.cs | 79 +++++++++++++++++++ 7 files changed, 234 insertions(+), 2 deletions(-) create mode 100644 analyzers/rspec/cs/S6640.html create mode 100644 analyzers/rspec/cs/S6640.json create mode 100644 analyzers/src/SonarAnalyzer.CSharp/Rules/Hotspots/UnsafeCodeBlocks.cs create mode 100644 analyzers/tests/SonarAnalyzer.UnitTest/Rules/Hotspots/UnsafeCodeBlocksTest.cs create mode 100644 analyzers/tests/SonarAnalyzer.UnitTest/TestCases/Hotspots/UnsafeCodeBlocks.cs diff --git a/analyzers/rspec/cs/S6640.html b/analyzers/rspec/cs/S6640.html new file mode 100644 index 00000000000..45991606b56 --- /dev/null +++ b/analyzers/rspec/cs/S6640.html @@ -0,0 +1,59 @@ +

Using unsafe code blocks can lead to unintended security or stability risks.

+

unsafe code blocks allow developers to use features such as pointers, fixed buffers, function calls through pointers and manual memory +management. Such features may be necessary for interoperability with native libraries, as these often require pointers. It may also increase +performance in some critical areas, as certain bounds checks are not executed in an unsafe context.

+

unsafe code blocks aren’t necessarily dangerous, however, the contents of such blocks are not verified by the Common Language Runtime. +Therefore, it is up to the programmer to ensure that no bugs are introduced through manual memory management or casting. If not done correctly, then +those bugs can lead to memory corruption vulnerabilities such as stack overflows. unsafe code blocks should be used with caution because +of these security and stability risks.

+

Ask Yourself Whether

+ +

There is a risk if you answered yes to the question.

+

Recommended Secure Coding Practices

+

Unless absolutely necessary, do not use unsafe code blocks. If unsafe is used to increase performance, then the Span and Memory APIs may serve a similar purpose in a safe context.

+

If it is not possible to remove the code block, then it should be kept as short as possible. Doing so reduces risk, as there is less code that can +potentially introduce new bugs. Within the unsafe code block, make sure that:

+ +

Sensitive Code Example

+
+public unsafe int subarraySum(int[] array, int start, int end)  // Sensitive
+{
+    var sum = 0;
+
+    // Skip array bound checks for extra performance
+    fixed (int* firstNumber = array)
+    {
+        for (int i = start; i < end; i++)
+            sum += *(firstNumber + i);
+    }
+
+    return sum;
+}
+
+

Compliant Solution

+
+public int subarraySum(int[] array, int start, int end)
+{
+    var sum = 0;
+
+    Span<int> span = array.AsSpan();
+    for (int i = start; i < end; i++)
+        sum += span[i];
+
+    return sum;
+}
+
+

See

+ + diff --git a/analyzers/rspec/cs/S6640.json b/analyzers/rspec/cs/S6640.json new file mode 100644 index 00000000000..7c73cac747c --- /dev/null +++ b/analyzers/rspec/cs/S6640.json @@ -0,0 +1,15 @@ +{ + "title": "Using unsafe code blocks is security-sensitive", + "type": "SECURITY_HOTSPOT", + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "60min" + }, + "tags": [], + "defaultSeverity": "Major", + "ruleSpecification": "RSPEC-6640", + "sqKey": "S6640", + "scope": "All", + "quickfix": "unknown" +} diff --git a/analyzers/rspec/cs/Sonar_way_profile.json b/analyzers/rspec/cs/Sonar_way_profile.json index 90c47c23402..76b14b10578 100644 --- a/analyzers/rspec/cs/Sonar_way_profile.json +++ b/analyzers/rspec/cs/Sonar_way_profile.json @@ -284,6 +284,7 @@ "S6612", "S6613", "S6617", - "S6618" + "S6618", + "S6640" ] } diff --git a/analyzers/src/SonarAnalyzer.CSharp/Rules/Hotspots/UnsafeCodeBlocks.cs b/analyzers/src/SonarAnalyzer.CSharp/Rules/Hotspots/UnsafeCodeBlocks.cs new file mode 100644 index 00000000000..29cb3a1cb25 --- /dev/null +++ b/analyzers/src/SonarAnalyzer.CSharp/Rules/Hotspots/UnsafeCodeBlocks.cs @@ -0,0 +1,45 @@ +/* + * SonarAnalyzer for .NET + * Copyright (C) 2015-2023 SonarSource SA + * mailto: contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +namespace SonarAnalyzer.Rules.CSharp; + +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public sealed class UnsafeCodeBlocks : HotspotDiagnosticAnalyzer +{ + private const string DiagnosticId = "S6640"; + private const string MessageFormat = "FIXME"; + + private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); + + public UnsafeCodeBlocks(IAnalyzerConfiguration configuration) : base(configuration) { } + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + + protected override void Initialize(SonarAnalysisContext context) => + context.RegisterNodeAction(c => + { + var node = c.Node; + if (false) + { + c.ReportIssue(Diagnostic.Create(Rule, node.GetLocation())); + } + }, + SyntaxKind.InvocationExpression); +} diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/PackagingTests/RuleTypeMappingCS.cs b/analyzers/tests/SonarAnalyzer.UnitTest/PackagingTests/RuleTypeMappingCS.cs index 2f289418aad..86fec7766de 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/PackagingTests/RuleTypeMappingCS.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/PackagingTests/RuleTypeMappingCS.cs @@ -6564,7 +6564,7 @@ internal static class RuleTypeMappingCS // ["S6637"], // ["S6638"], // ["S6639"], - // ["S6640"], + ["S6640"] = "SECURITY_HOTSPOT", // ["S6641"], // ["S6642"], // ["S6643"], diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/Hotspots/UnsafeCodeBlocksTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/Hotspots/UnsafeCodeBlocksTest.cs new file mode 100644 index 00000000000..54698aa4747 --- /dev/null +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/Hotspots/UnsafeCodeBlocksTest.cs @@ -0,0 +1,33 @@ +/* + * SonarAnalyzer for .NET + * Copyright (C) 2015-2023 SonarSource SA + * mailto: contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +using SonarAnalyzer.Rules.CSharp; + +namespace SonarAnalyzer.UnitTest.Rules; + +[TestClass] +public class UnsafeCodeBlocksTest +{ + private readonly VerifierBuilder builder = new VerifierBuilder().WithBasePath("Hotspots").AddAnalyzer(() => new UnsafeCodeBlocks(AnalyzerConfiguration.AlwaysEnabled)); + + [TestMethod] + public void UnsafeCodeBlocks() => + builder.AddPaths("UnsafeCodeBlocks.cs").Verify(); +} diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/Hotspots/UnsafeCodeBlocks.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/Hotspots/UnsafeCodeBlocks.cs new file mode 100644 index 00000000000..b5474d1a353 --- /dev/null +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/Hotspots/UnsafeCodeBlocks.cs @@ -0,0 +1,79 @@ +using System; + +public class Sample +{ + unsafe void MethodScope(byte* pointer) { } // FN {{Make sure that using "unsafe" is safe here.}} +// ~~~~~~ + + void BlockScope() + { + unsafe // FN +// ~~~~~~ + { + } + } + + void SafeMethod() { } // Compliant + + void LocalFunction() + { + unsafe void Local(byte* pointer) { } // FN + void SafeLocal(byte noPointer) { } // Compliant + } + + unsafe class UnsafeClass { } // FN + + unsafe struct UnsafeStruct // FN + { + unsafe fixed byte unsafeFixedBuffer[16]; // FN + } + + unsafe interface IUnsafeInterface { } // FN + + unsafe Sample(byte* pointer) { } // FN + + static unsafe Sample() { } // FN + + unsafe ~Sample() { } // FN + + unsafe byte* unsafeField; // FN + + unsafe byte* UnsafeProperty { get; } // FN + + unsafe event EventHandler UnsafeEvent; // FN + + unsafe delegate void UnsafeDelegate(byte* pointer); // FN + + unsafe int this[int i] => 5; // FN + + public unsafe static Sample operator +(Sample other) => other; // FN + + // from RSPEC + public unsafe int SubarraySum(int[] array, int start, int end) // FN + { + var sum = 0; + + // Skip array bound checks for extra performance + fixed (int* firstNumber = array) + { + for (int i = start; i < end; i++) + sum += *(firstNumber + i); + } + + return sum; + } + + // from C# docs + unsafe static void SquarePtrParam(int* p) // FN + { + *p *= *p; + } + + unsafe static void Main() // FN + { + int i = 5; + // Unsafe method: uses address-of operator (&). + SquarePtrParam(&i); + Console.WriteLine(i); + } +} From 1081e9ed17f243892019f0923e0e6a5872022ee6 Mon Sep 17 00:00:00 2001 From: Tim Pohlmann Date: Tue, 20 Jun 2023 16:23:09 +0200 Subject: [PATCH 2/5] Implement rule logic --- .../Extensions/SyntaxTokenListExtensions.cs | 29 +++++++++++++++++++ .../Rules/Hotspots/UnsafeCodeBlocks.cs | 25 ++++++++++++---- .../TestCases/Hotspots/UnsafeCodeBlocks.cs | 22 +++++++------- 3 files changed, 59 insertions(+), 17 deletions(-) create mode 100644 analyzers/src/SonarAnalyzer.CSharp/Extensions/SyntaxTokenListExtensions.cs diff --git a/analyzers/src/SonarAnalyzer.CSharp/Extensions/SyntaxTokenListExtensions.cs b/analyzers/src/SonarAnalyzer.CSharp/Extensions/SyntaxTokenListExtensions.cs new file mode 100644 index 00000000000..d18c1da6466 --- /dev/null +++ b/analyzers/src/SonarAnalyzer.CSharp/Extensions/SyntaxTokenListExtensions.cs @@ -0,0 +1,29 @@ +/* + * SonarAnalyzer for .NET + * Copyright (C) 2015-2023 SonarSource SA + * mailto: contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +namespace SonarAnalyzer.Extensions +{ + public static class SyntaxTokenListExtensions + { + public static SyntaxToken? Find(this SyntaxTokenList tokenList, SyntaxKind kind) => + tokenList.IndexOf(kind) is var index && index != -1 + ? tokenList[index] : null; + } +} diff --git a/analyzers/src/SonarAnalyzer.CSharp/Rules/Hotspots/UnsafeCodeBlocks.cs b/analyzers/src/SonarAnalyzer.CSharp/Rules/Hotspots/UnsafeCodeBlocks.cs index 29cb3a1cb25..390ef4f1027 100644 --- a/analyzers/src/SonarAnalyzer.CSharp/Rules/Hotspots/UnsafeCodeBlocks.cs +++ b/analyzers/src/SonarAnalyzer.CSharp/Rules/Hotspots/UnsafeCodeBlocks.cs @@ -24,22 +24,35 @@ namespace SonarAnalyzer.Rules.CSharp; public sealed class UnsafeCodeBlocks : HotspotDiagnosticAnalyzer { private const string DiagnosticId = "S6640"; - private const string MessageFormat = "FIXME"; + private const string MessageFormat = """Make sure that using "unsafe" is safe here."""; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); + public UnsafeCodeBlocks() : this(AnalyzerConfiguration.Hotspot) { } + public UnsafeCodeBlocks(IAnalyzerConfiguration configuration) : base(configuration) { } public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); - protected override void Initialize(SonarAnalysisContext context) => + protected override void Initialize(SonarAnalysisContext context) + { + context.RegisterNodeAction(c => Report(c, ((UnsafeStatementSyntax)c.Node).UnsafeKeyword), SyntaxKind.UnsafeStatement); context.RegisterNodeAction(c => { - var node = c.Node; - if (false) + if (c.Node is BaseMethodDeclarationSyntax { Modifiers: var modifiers } + && modifiers.Find(SyntaxKind.UnsafeKeyword) is { } unsafeModifier) { - c.ReportIssue(Diagnostic.Create(Rule, node.GetLocation())); + Report(c, unsafeModifier); } }, - SyntaxKind.InvocationExpression); + SyntaxKind.MethodDeclaration, SyntaxKind.ConstructorDeclaration, SyntaxKind.DestructorDeclaration, SyntaxKind.OperatorDeclaration); + } + + private void Report(SonarSyntaxNodeReportingContext context, SyntaxToken token) + { + if (IsEnabled(context.Options)) + { + context.ReportIssue(Diagnostic.Create(Rule, token.GetLocation())); + } + } } diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/Hotspots/UnsafeCodeBlocks.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/Hotspots/UnsafeCodeBlocks.cs index b5474d1a353..2d709a9f44c 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/Hotspots/UnsafeCodeBlocks.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/Hotspots/UnsafeCodeBlocks.cs @@ -2,13 +2,13 @@ public class Sample { - unsafe void MethodScope(byte* pointer) { } // FN {{Make sure that using "unsafe" is safe here.}} -// ~~~~~~ + unsafe void MethodScope(byte* pointer) { } // Noncompliant {{Make sure that using "unsafe" is safe here.}} +// ^^^^^^ void BlockScope() { - unsafe // FN -// ~~~~~~ + unsafe // Noncompliant +// ^^^^^^ { } } @@ -30,11 +30,11 @@ unsafe struct UnsafeStruct // FN unsafe interface IUnsafeInterface { } // FN - unsafe Sample(byte* pointer) { } // FN + unsafe Sample(byte* pointer) { } // Noncompliant - static unsafe Sample() { } // FN + static unsafe Sample() { } // Noncompliant - unsafe ~Sample() { } // FN + unsafe ~Sample() { } // Noncompliant unsafe byte* unsafeField; // FN @@ -46,10 +46,10 @@ static unsafe Sample() { } // FN unsafe int this[int i] => 5; // FN - public unsafe static Sample operator +(Sample other) => other; // FN + public unsafe static Sample operator +(Sample other) => other; // Noncompliant // from RSPEC - public unsafe int SubarraySum(int[] array, int start, int end) // FN + public unsafe int SubarraySum(int[] array, int start, int end) // Noncompliant { var sum = 0; @@ -64,12 +64,12 @@ public unsafe int SubarraySum(int[] array, int start, int end) // FN } // from C# docs - unsafe static void SquarePtrParam(int* p) // FN + unsafe static void SquarePtrParam(int* p) // Noncompliant { *p *= *p; } - unsafe static void Main() // FN + unsafe static void Main() // Noncompliant { int i = 5; // Unsafe method: uses address-of operator (&). From d497ea90530b2e320b689d094f79a8a23b713e43 Mon Sep 17 00:00:00 2001 From: Tim Pohlmann Date: Wed, 21 Jun 2023 10:48:28 +0200 Subject: [PATCH 3/5] Update ITs --- .../Akka.Remote--netstandard2.0-S6640.json | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 analyzers/its/expected/akka.net/Akka.Remote--netstandard2.0-S6640.json diff --git a/analyzers/its/expected/akka.net/Akka.Remote--netstandard2.0-S6640.json b/analyzers/its/expected/akka.net/Akka.Remote--netstandard2.0-S6640.json new file mode 100644 index 00000000000..0f7fca2f9f5 --- /dev/null +++ b/analyzers/its/expected/akka.net/Akka.Remote--netstandard2.0-S6640.json @@ -0,0 +1,17 @@ +{ +"issues": [ +{ +"id": "S6640", +"message": "Make sure that using "unsafe" is safe here.", +"location": { +"uri": "sources\akka.net\src\core\Akka.Remote\Serialization\LruBoundedCache.cs", +"region": { +"startLine": 61, +"startColumn": 13, +"endLine": 61, +"endColumn": 19 +} +} +} +] +} From bbd70a230827aad14fe835c8c3f18b6b0f8610db Mon Sep 17 00:00:00 2001 From: Tim Pohlmann Date: Wed, 21 Jun 2023 14:17:16 +0200 Subject: [PATCH 4/5] Review 1 --- analyzers/rspec/cs/S6640.html | 4 ++-- analyzers/rspec/cs/S6640.json | 2 +- .../Extensions/SyntaxTokenListExtensions.cs | 13 ++++++------- .../Rules/Hotspots/UnsafeCodeBlocksTest.cs | 15 +++++++++++++++ 4 files changed, 24 insertions(+), 10 deletions(-) diff --git a/analyzers/rspec/cs/S6640.html b/analyzers/rspec/cs/S6640.html index 45991606b56..51aa522bc66 100644 --- a/analyzers/rspec/cs/S6640.html +++ b/analyzers/rspec/cs/S6640.html @@ -23,7 +23,7 @@

Recommended Secure Coding Practices

Sensitive Code Example

-public unsafe int subarraySum(int[] array, int start, int end)  // Sensitive
+public unsafe int SubarraySum(int[] array, int start, int end)  // Sensitive
 {
     var sum = 0;
 
@@ -39,7 +39,7 @@ 

Sensitive Code Example

Compliant Solution

-public int subarraySum(int[] array, int start, int end)
+public int SubarraySum(int[] array, int start, int end)
 {
     var sum = 0;
 
diff --git a/analyzers/rspec/cs/S6640.json b/analyzers/rspec/cs/S6640.json
index 7c73cac747c..62cb24199f1 100644
--- a/analyzers/rspec/cs/S6640.json
+++ b/analyzers/rspec/cs/S6640.json
@@ -11,5 +11,5 @@
   "ruleSpecification": "RSPEC-6640",
   "sqKey": "S6640",
   "scope": "All",
-  "quickfix": "unknown"
+  "quickfix": "infeasible"
 }
diff --git a/analyzers/src/SonarAnalyzer.CSharp/Extensions/SyntaxTokenListExtensions.cs b/analyzers/src/SonarAnalyzer.CSharp/Extensions/SyntaxTokenListExtensions.cs
index d18c1da6466..2e3b21a4a4c 100644
--- a/analyzers/src/SonarAnalyzer.CSharp/Extensions/SyntaxTokenListExtensions.cs
+++ b/analyzers/src/SonarAnalyzer.CSharp/Extensions/SyntaxTokenListExtensions.cs
@@ -18,12 +18,11 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 
-namespace SonarAnalyzer.Extensions
+namespace SonarAnalyzer.Extensions;
+
+public static class SyntaxTokenListExtensions
 {
-    public static class SyntaxTokenListExtensions
-    {
-        public static SyntaxToken? Find(this SyntaxTokenList tokenList, SyntaxKind kind) =>
-            tokenList.IndexOf(kind) is var index && index != -1
-            ? tokenList[index] : null;
-    }
+    public static SyntaxToken? Find(this SyntaxTokenList tokenList, SyntaxKind kind) =>
+        tokenList.IndexOf(kind) is var index and >= 0
+        ? tokenList[index] : null;
 }
diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/Hotspots/UnsafeCodeBlocksTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/Hotspots/UnsafeCodeBlocksTest.cs
index 54698aa4747..542a076f13b 100644
--- a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/Hotspots/UnsafeCodeBlocksTest.cs
+++ b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/Hotspots/UnsafeCodeBlocksTest.cs
@@ -18,6 +18,7 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 
+using Microsoft.VisualStudio.TestTools.UnitTesting;
 using SonarAnalyzer.Rules.CSharp;
 
 namespace SonarAnalyzer.UnitTest.Rules;
@@ -30,4 +31,18 @@ public class UnsafeCodeBlocksTest
     [TestMethod]
     public void UnsafeCodeBlocks() =>
         builder.AddPaths("UnsafeCodeBlocks.cs").Verify();
+
+#if NET
+
+    [TestMethod]
+    public void UnsafeRecord() =>
+        builder.AddSnippet("""unsafe record MyRecord(byte* Pointer);        // FN""")
+        .WithOptions(ParseOptionsHelper.FromCSharp9).Verify();
+
+    [TestMethod]
+    public void UnsafeRecordStruct() =>
+        builder.AddSnippet("""unsafe record struct MyRecord(byte* Pointer); // FN""")
+        .WithOptions(ParseOptionsHelper.FromCSharp10).Verify();
+
+#endif
 }

From 92de3937a7e8d164df660d6e5793ee7bd6d8ce0f Mon Sep 17 00:00:00 2001
From: Tim Pohlmann 
Date: Wed, 21 Jun 2023 14:32:46 +0200
Subject: [PATCH 5/5] Extract modifier code to avoid code duplication in future
 PRs

---
 .../Rules/Hotspots/UnsafeCodeBlocks.cs              | 13 ++++++++++---
 1 file changed, 10 insertions(+), 3 deletions(-)

diff --git a/analyzers/src/SonarAnalyzer.CSharp/Rules/Hotspots/UnsafeCodeBlocks.cs b/analyzers/src/SonarAnalyzer.CSharp/Rules/Hotspots/UnsafeCodeBlocks.cs
index 390ef4f1027..e8a91bfc39c 100644
--- a/analyzers/src/SonarAnalyzer.CSharp/Rules/Hotspots/UnsafeCodeBlocks.cs
+++ b/analyzers/src/SonarAnalyzer.CSharp/Rules/Hotspots/UnsafeCodeBlocks.cs
@@ -39,15 +39,22 @@ protected override void Initialize(SonarAnalysisContext context)
         context.RegisterNodeAction(c => Report(c, ((UnsafeStatementSyntax)c.Node).UnsafeKeyword), SyntaxKind.UnsafeStatement);
         context.RegisterNodeAction(c =>
             {
-                if (c.Node is BaseMethodDeclarationSyntax { Modifiers: var modifiers }
-                    && modifiers.Find(SyntaxKind.UnsafeKeyword) is { } unsafeModifier)
+                if (c.Node is BaseMethodDeclarationSyntax { Modifiers: var modifiers })
                 {
-                    Report(c, unsafeModifier);
+                    ReportIfUnsafe(c, modifiers);
                 }
             },
             SyntaxKind.MethodDeclaration, SyntaxKind.ConstructorDeclaration, SyntaxKind.DestructorDeclaration, SyntaxKind.OperatorDeclaration);
     }
 
+    private void ReportIfUnsafe(SonarSyntaxNodeReportingContext context, SyntaxTokenList modifiers)
+    {
+        if (modifiers.Find(SyntaxKind.UnsafeKeyword) is { } unsafeModifier)
+        {
+            Report(context, unsafeModifier);
+        }
+    }
+
     private void Report(SonarSyntaxNodeReportingContext context, SyntaxToken token)
     {
         if (IsEnabled(context.Options))