diff --git a/analyzers/rspec/cs/S6962.html b/analyzers/rspec/cs/S6962.html new file mode 100644 index 00000000000..815180cfb5a --- /dev/null +++ b/analyzers/rspec/cs/S6962.html @@ -0,0 +1,94 @@ +

In frequently used code paths, such as controller actions, you should avoid using the HttpClient directly and opt for one of the IHttpClientFactory-based mechanisms instead. This +way, you avoid wasting resources and creating performance overhead.

+

Why is this an issue?

+

If a code path that creates and disposes of HttpClient objects is frequently used, then the following issues can occur:

+ +

How to fix it

+

The IHttpClientFactory was introduced in +ASP.NET Core 2.1 to solve these problems. It handles pooling HTTP connections to optimize performance and reliability.

+

There are several ways that you can use +IHttpClientFactory in your application:

+ +

Alternatively, you may cache the HttpClient in a singleton or a static field. You should be aware that by default, the HttpClient doesn’t respect +the DNS’s Time To Live (TTL) settings. If the IP address associated with a domain name changes, HttpClient might still use the old, cached IP address, +leading to failed requests.

+

Code examples

+

Noncompliant code example

+
+[ApiController]
+[Route("controller")]
+public class FooController : Controller
+{
+    [HttpGet]
+    public async Task<string> Foo()
+    {
+        using var client = new HttpClient();  //Noncompliant
+        return await client.GetStringAsync(_url);
+    }
+}
+
+

Compliant solution

+
+// File: Startup.cs
+public class Startup
+{
+    public void ConfigureServices(IServiceCollection services)
+    {
+        services.AddHttpClient();
+        // ...
+    }
+}
+
+[ApiController]
+[Route("controller")]
+public class FooController : Controller
+{
+    private readonly IHttpClientFactory _clientFactory;
+
+    public FooController(IHttpClientFactory clientFactory)
+    {
+        _clientFactory = clientFactory;
+    }
+
+    [HttpGet]
+    public async Task<string> Foo()
+    {
+        using var client = _clientFactory.CreateClient(); // Compliant (Basic usage)
+        return await client.GetStringAsync(_url);
+    }
+}
+
+

Resources

+

Documentation

+ + diff --git a/analyzers/rspec/cs/S6962.json b/analyzers/rspec/cs/S6962.json new file mode 100644 index 00000000000..7f75bbd5f10 --- /dev/null +++ b/analyzers/rspec/cs/S6962.json @@ -0,0 +1,21 @@ +{ + "title": "You should pool HTTP connections with HttpClientFactory", + "type": "CODE_SMELL", + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "1h" + }, + "tags": [], + "defaultSeverity": "Major", + "ruleSpecification": "RSPEC-6962", + "sqKey": "S6962", + "scope": "Main", + "quickfix": "infeasible", + "code": { + "impacts": { + "RELIABILITY": "MEDIUM" + }, + "attribute": "COMPLETE" + } +} diff --git a/analyzers/rspec/cs/Sonar_way_profile.json b/analyzers/rspec/cs/Sonar_way_profile.json index e408158e3fe..c21443d08e4 100644 --- a/analyzers/rspec/cs/Sonar_way_profile.json +++ b/analyzers/rspec/cs/Sonar_way_profile.json @@ -317,6 +317,7 @@ "S6931", "S6934", "S6961", + "S6962", "S6965" ] } diff --git a/analyzers/src/SonarAnalyzer.CSharp/ReuseClientBase.cs b/analyzers/src/SonarAnalyzer.CSharp/ReuseClientBase.cs new file mode 100644 index 00000000000..b420f615ba3 --- /dev/null +++ b/analyzers/src/SonarAnalyzer.CSharp/ReuseClientBase.cs @@ -0,0 +1,56 @@ +/* + * SonarAnalyzer for .NET + * Copyright (C) 2015-2024 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 +{ + public abstract class ReuseClientBase : SonarDiagnosticAnalyzer + { + protected abstract ImmutableArray ReusableClients { get; } + + protected static bool IsAssignedForReuse(SonarSyntaxNodeReportingContext context) => + !IsInVariableDeclaration(context.Node) + && (IsInConditionalCode(context.Node) || IsInFieldOrPropertyInitializer(context.Node) || IsAssignedToStaticFieldOrProperty(context)); + + protected bool IsReusableClient(SonarSyntaxNodeReportingContext context) + { + var objectCreation = ObjectCreationFactory.Create(context.Node); + return ReusableClients.Any(x => objectCreation.IsKnownType(x, context.SemanticModel)); + } + + private static bool IsInVariableDeclaration(SyntaxNode node) => + node.Parent is EqualsValueClauseSyntax { Parent: VariableDeclaratorSyntax { Parent: VariableDeclarationSyntax { Parent: LocalDeclarationStatementSyntax or UsingStatementSyntax } } }; + + private static bool IsInFieldOrPropertyInitializer(SyntaxNode node) => + node.Ancestors().Any(x => x.IsAnyKind(SyntaxKind.FieldDeclaration, SyntaxKind.PropertyDeclaration)) + && !node.Ancestors().Any(x => x.IsAnyKind(SyntaxKind.GetAccessorDeclaration, SyntaxKind.SetAccessorDeclaration)) + && !node.Parent.IsKind(SyntaxKind.ArrowExpressionClause); + + private static bool IsInConditionalCode(SyntaxNode node) => + node.Ancestors().Any(x => x.IsAnyKind(SyntaxKind.IfStatement, + SyntaxKind.SwitchStatement, + SyntaxKindEx.SwitchExpression, + SyntaxKind.ConditionalExpression, + SyntaxKindEx.CoalesceAssignmentExpression)); + + private static bool IsAssignedToStaticFieldOrProperty(SonarSyntaxNodeReportingContext context) => + context.Node.Parent.WalkUpParentheses() is AssignmentExpressionSyntax assignment + && context.SemanticModel.GetSymbolInfo(assignment.Left, context.Cancel).Symbol is { IsStatic: true, Kind: SymbolKind.Field or SymbolKind.Property }; + } +} diff --git a/analyzers/src/SonarAnalyzer.CSharp/Rules/AspNet/ControllersReuseClient.cs b/analyzers/src/SonarAnalyzer.CSharp/Rules/AspNet/ControllersReuseClient.cs new file mode 100644 index 00000000000..7553e397394 --- /dev/null +++ b/analyzers/src/SonarAnalyzer.CSharp/Rules/AspNet/ControllersReuseClient.cs @@ -0,0 +1,71 @@ +/* + * SonarAnalyzer for .NET + * Copyright (C) 2015-2024 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 ControllersReuseClient : ReuseClientBase +{ + private const string DiagnosticId = "S6962"; + private const string MessageFormat = "Reuse HttpClient instances rather than create new ones with each controller action invocation."; + + private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + + protected override ImmutableArray ReusableClients => ImmutableArray.Create(KnownType.System_Net_Http_HttpClient); + + protected override void Initialize(SonarAnalysisContext context) => + context.RegisterCompilationStartAction(compilationStartContext => + { + if (!compilationStartContext.Compilation.ReferencesControllers()) + { + return; + } + compilationStartContext.RegisterSymbolStartAction(symbolStartContext => + { + var symbol = (INamedTypeSymbol)symbolStartContext.Symbol; + if (symbol.IsControllerType()) + { + symbolStartContext.RegisterSyntaxNodeAction(nodeContext => + { + var node = nodeContext.Node; + if (IsInPublicMethod(node) + && IsReusableClient(nodeContext) + && !IsInsideConstructor(node) + && !IsAssignedForReuse(nodeContext)) + { + nodeContext.ReportIssue(Rule, node); + } + }, + SyntaxKind.ObjectCreationExpression, + SyntaxKindEx.ImplicitObjectCreationExpression); + } + }, SymbolKind.NamedType); + }); + + public static bool IsInPublicMethod(SyntaxNode node) => + node.FirstAncestorOrSelf() is not { } method + || (method.FirstAncestorOrSelf() is null + && method.Modifiers.Any(x => x.IsKind(SyntaxKind.PublicKeyword))); + + private static bool IsInsideConstructor(SyntaxNode node) => + node.Ancestors().Any(x => x.IsAnyKind(SyntaxKind.ConstructorDeclaration, SyntaxKindEx.PrimaryConstructorBaseType)); +} diff --git a/analyzers/src/SonarAnalyzer.CSharp/Rules/CloudNative/AzureFunctionsReuseClients.cs b/analyzers/src/SonarAnalyzer.CSharp/Rules/CloudNative/AzureFunctionsReuseClients.cs index c2f878c256e..768f037a433 100644 --- a/analyzers/src/SonarAnalyzer.CSharp/Rules/CloudNative/AzureFunctionsReuseClients.cs +++ b/analyzers/src/SonarAnalyzer.CSharp/Rules/CloudNative/AzureFunctionsReuseClients.cs @@ -21,14 +21,16 @@ namespace SonarAnalyzer.Rules.CSharp { [DiagnosticAnalyzer(LanguageNames.CSharp)] - public sealed class AzureFunctionsReuseClients : SonarDiagnosticAnalyzer + public sealed class AzureFunctionsReuseClients : ReuseClientBase { private const string DiagnosticId = "S6420"; private const string MessageFormat = "Reuse client instances rather than creating new ones with each function invocation."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); - private static readonly KnownType[] ReusableClients = - { + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + + protected override ImmutableArray ReusableClients => ImmutableArray.Create( KnownType.System_Net_Http_HttpClient, // ComosDb (DocumentClient is superseded by CosmosClient) KnownType.Microsoft_Azure_Documents_Client_DocumentClient, @@ -48,41 +50,18 @@ public sealed class AzureFunctionsReuseClients : SonarDiagnosticAnalyzer KnownType.Azure_Storage_Files_Shares_ShareServiceClient, KnownType.Azure_Storage_Files_DataLake_DataLakeServiceClient, // Resource manager - KnownType.Azure_ResourceManager_ArmClient - }; - - public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + KnownType.Azure_ResourceManager_ArmClient); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { if (c.AzureFunctionMethod() is not null - && IsResuableClient(c) + && IsReusableClient(c) && !IsAssignedForReuse(c)) { c.ReportIssue(Rule, c.Node); } }, SyntaxKind.ObjectCreationExpression, SyntaxKindEx.ImplicitObjectCreationExpression); - - private static bool IsAssignedForReuse(SonarSyntaxNodeReportingContext context) => - !IsInVariableDeclaration(context.Node) - && (IsInFieldOrPropertyInitializer(context.Node) || IsAssignedToStaticFieldOrProperty(context)); - - private static bool IsInVariableDeclaration(SyntaxNode node) => - node.Parent is EqualsValueClauseSyntax { Parent: VariableDeclaratorSyntax { Parent: VariableDeclarationSyntax { Parent: LocalDeclarationStatementSyntax or UsingStatementSyntax } } }; - - private static bool IsInFieldOrPropertyInitializer(SyntaxNode node) => - node.Ancestors().Any(x => x.IsAnyKind(SyntaxKind.FieldDeclaration, SyntaxKind.PropertyDeclaration)); - - private static bool IsAssignedToStaticFieldOrProperty(SonarSyntaxNodeReportingContext context) => - context.Node.Parent.WalkUpParentheses() is AssignmentExpressionSyntax assignment - && context.SemanticModel.GetSymbolInfo(assignment.Left, context.Cancel).Symbol is { IsStatic: true, Kind: SymbolKind.Field or SymbolKind.Property }; - - private static bool IsResuableClient(SonarSyntaxNodeReportingContext context) - { - var objectCreation = ObjectCreationFactory.Create(context.Node); - return ReusableClients.Any(x => objectCreation.IsKnownType(x, context.SemanticModel)); - } } } diff --git a/analyzers/tests/SonarAnalyzer.Test/PackagingTests/RuleTypeMappingCS.cs b/analyzers/tests/SonarAnalyzer.Test/PackagingTests/RuleTypeMappingCS.cs index 143e10c8e5f..688addf11ca 100644 --- a/analyzers/tests/SonarAnalyzer.Test/PackagingTests/RuleTypeMappingCS.cs +++ b/analyzers/tests/SonarAnalyzer.Test/PackagingTests/RuleTypeMappingCS.cs @@ -6886,7 +6886,7 @@ internal static class RuleTypeMappingCS // ["S6959"], // ["S6960"], ["S6961"] = "CODE_SMELL", - // ["S6962"], + ["S6962"] = "CODE_SMELL", // ["S6963"], // ["S6964"], ["S6965"] = "CODE_SMELL", diff --git a/analyzers/tests/SonarAnalyzer.Test/Rules/AspNet/ControllersReuseClientTest.cs b/analyzers/tests/SonarAnalyzer.Test/Rules/AspNet/ControllersReuseClientTest.cs new file mode 100644 index 00000000000..3aff4a12c57 --- /dev/null +++ b/analyzers/tests/SonarAnalyzer.Test/Rules/AspNet/ControllersReuseClientTest.cs @@ -0,0 +1,68 @@ +/* + * SonarAnalyzer for .NET + * Copyright (C) 2015-2024 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.Test.Rules; + +#if NET + +[TestClass] +public class ControllersReuseClientTest +{ + private readonly VerifierBuilder builder = new VerifierBuilder() + .WithBasePath("AspNet") + .AddReferences( + [ + AspNetCoreMetadataReference.MicrosoftAspNetCoreMvcAbstractions, + AspNetCoreMetadataReference.MicrosoftAspNetCoreMvcCore, + AspNetCoreMetadataReference.MicrosoftAspNetCoreMvcViewFeatures, + .. MetadataReferenceFacade.SystemThreadingTasks, + .. NuGetMetadataReference.SystemNetHttp(), + .. NuGetMetadataReference.MicrosoftExtensionsHttp() + ]); + + [TestMethod] + public void ControllersReuseClient_CS() => + builder + .AddPaths("ControllersReuseClient.cs") + .Verify(); + + [TestMethod] + public void ControllersReuseClient_CS8() => + builder + .AddPaths("ControllersReuseClient.CSharp8.cs") + .WithOptions(ParseOptionsHelper.FromCSharp8) + .Verify(); + + [TestMethod] + public void ControllersReuseClient_CS9() => + builder + .AddPaths("ControllersReuseClient.CSharp9.cs") + .WithOptions(ParseOptionsHelper.FromCSharp9) + .Verify(); + + [TestMethod] + public void ControllersReuseClient_CS12() => + builder + .AddPaths("ControllerReuseClient.CSharp12.cs") + .WithOptions(ParseOptionsHelper.FromCSharp12).Verify(); +} +#endif diff --git a/analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/ControllerReuseClient.CSharp12.cs b/analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/ControllerReuseClient.CSharp12.cs new file mode 100644 index 00000000000..4ca8def6418 --- /dev/null +++ b/analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/ControllerReuseClient.CSharp12.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Mvc; +using System; +using System.IO; +using System.Net.Http; +using System.Threading.Tasks; + +[ApiController] +[Route("SomeRoute")] +public class C(HttpClient client) : ControllerBase; + +[ApiController] +[Route("SomeRoute")] +public class D(HttpClient client) : C(new HttpClient()) // Compliant +{ + public D() : this(new HttpClient()) { } // Compliant +} + +[ApiController] +[Route("SomeRoute")] +public class E : C +{ + public E() : base(new HttpClient()) { } // Compliant +} diff --git a/analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/ControllersReuseClient.CSharp9.cs b/analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/ControllersReuseClient.CSharp9.cs new file mode 100644 index 00000000000..f8df9b09ec5 --- /dev/null +++ b/analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/ControllersReuseClient.CSharp9.cs @@ -0,0 +1,17 @@ +using Microsoft.AspNetCore.Mvc; +using System; +using System.IO; +using System.Net.Http; +using System.Threading.Tasks; + +[ApiController] +[Route("Hello")] +public class SomeController : ControllerBase +{ + [HttpGet("foo")] + public async Task Foo() + { + HttpClient client = new(); // Noncompliant + return "bar"; + } +} diff --git a/analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/ControllersReuseClient.Csharp8.cs b/analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/ControllersReuseClient.Csharp8.cs new file mode 100644 index 00000000000..c8a21cea663 --- /dev/null +++ b/analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/ControllersReuseClient.Csharp8.cs @@ -0,0 +1,25 @@ +using Microsoft.AspNetCore.Mvc; +using System; +using System.IO; +using System.Net.Http; +using System.Threading.Tasks; + +[ApiController] +public class SomeController : ControllerBase +{ + private readonly IHttpClientFactory _clientFactory; + private HttpClient clientField = new HttpClient(); + + [HttpGet("foo")] + public async Task Foo() + { + using var clientA = new HttpClient(); // Noncompliant + // ^^^^^^^^^^^^^^^^ + await clientA.GetStringAsync(""); + + clientField ??= new HttpClient(); // Compliant + using var pooledClient = _clientFactory.CreateClient(); // Compliant + _ = true switch { true => new HttpClient() }; // Compliant + return "bar"; + } +} diff --git a/analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/ControllersReuseClient.cs b/analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/ControllersReuseClient.cs new file mode 100644 index 00000000000..a2e0369887a --- /dev/null +++ b/analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/ControllersReuseClient.cs @@ -0,0 +1,74 @@ +using Microsoft.AspNetCore.Mvc; +using System; +using System.IO; +using System.Net.Http; +using System.Threading.Tasks; + +[ApiController] +[Route("Hello")] +public class SomeController : ControllerBase +{ + private readonly IHttpClientFactory _clientFactory; + private HttpClient clientField = new HttpClient(); // Compliant, it can be reused between actions + private HttpClient ClientProperty { get; set; } = new HttpClient(); // Compliant, it can be reused between actions + private HttpClient ClientPropertyAccessorArrowClause { get => new HttpClient(); } // Noncompliant + private HttpClient ClientPropertyAccessorMethodBody { get { var anotherStatement = 1; return new HttpClient(); } } // Noncompliant + private HttpClient ClientPropertyAccessorArrow => new HttpClient(); // Noncompliant + + public SomeController() + { + clientField = new HttpClient(); // Compliant + } + + [HttpGet("foo")] + public async Task Foo() + { + using (var clientB = new HttpClient()) //Noncompliant {{Reuse HttpClient instances rather than create new ones with each controller action invocation.}} + // ^^^^^^^^^^^^^^^^ + { + await clientB.GetStringAsync(""); + } + + var client = new HttpClient(); // Noncompliant + clientField = new HttpClient(); // Noncompliant + ClientProperty = new HttpClient(); // Noncompliant + var local = new HttpClient(); // Noncompliant + local = new System.Net.Http.HttpClient(); // Noncompliant + var fromStaticMethod = StaticCreateClient(); // FN - see https://github.com/SonarSource/rspec/pull/3847#discussion_r1559510167 + var fromMethod = CreateClient(); // FN - see https://github.com/SonarSource/rspec/pull/3847#discussion_r1559510167 + + local = ClientPropertyAccessorArrow; // Compliant + local = ClientPropertyAccessorArrow; // Compliant + + // Lambda + _ = new Lazy(() => new HttpClient()); // Noncompliant FP + + // Conditional code + if (true) + _ = new HttpClient(); // Compliant + switch (true) + { + case true: + _ = new HttpClient(); // Compliant + break; + } + _ = true ? new HttpClient() : null; // Compliant + + return "bar"; + } + + private static HttpClient StaticCreateClient() + { + return new HttpClient(); // Compliant, we raise only in actions + } + + private HttpClient CreateClient() + { + return new HttpClient(); // Compliant, we raise only in actions + } +} + +public class NotAController +{ + private HttpClient ClientPropertyAccessorArrow => new HttpClient(); // Compliant, it's not in a controller. +} diff --git a/analyzers/tests/SonarAnalyzer.Test/TestCases/CloudNative/AzureFunctionsReuseClients.HttpClient.CSharp9.cs b/analyzers/tests/SonarAnalyzer.Test/TestCases/CloudNative/AzureFunctionsReuseClients.HttpClient.CSharp9.cs index 92d67e8f011..9828c5e8062 100644 --- a/analyzers/tests/SonarAnalyzer.Test/TestCases/CloudNative/AzureFunctionsReuseClients.HttpClient.CSharp9.cs +++ b/analyzers/tests/SonarAnalyzer.Test/TestCases/CloudNative/AzureFunctionsReuseClients.HttpClient.CSharp9.cs @@ -19,7 +19,7 @@ public static void DifferentAssigments() field ??= new HttpClient(); // Compliant var local = default(HttpClient); - local ??= new HttpClient(); // Noncompliant + local ??= new HttpClient(); // FN, needs SE to be able to raise an issue. _ = new HttpClient(); // Noncompliant using var localUsingStatement = new HttpClient(); // Noncompliant diff --git a/analyzers/tests/SonarAnalyzer.Test/TestCases/CloudNative/AzureFunctionsReuseClients.HttpClient.cs b/analyzers/tests/SonarAnalyzer.Test/TestCases/CloudNative/AzureFunctionsReuseClients.HttpClient.cs index bf022b2a752..ecf1688523f 100644 --- a/analyzers/tests/SonarAnalyzer.Test/TestCases/CloudNative/AzureFunctionsReuseClients.HttpClient.cs +++ b/analyzers/tests/SonarAnalyzer.Test/TestCases/CloudNative/AzureFunctionsReuseClients.HttpClient.cs @@ -38,6 +38,9 @@ public class FunctionApp1 protected static HttpClient ClientProperty { get; set; } = new HttpClient(); // Compliant protected static Lazy LazyClientProperty { get; set; } = new Lazy(() => new HttpClient()); // Compliant + protected HttpClient ClientPropertyAccessor { get => new HttpClient(); } // FN + protected HttpClient ClientPropertyAccessorArrow => new HttpClient(); // FN + static FunctionApp1() { ClientProperty = new HttpClient(); // Compliant @@ -55,7 +58,7 @@ public static void Assignments() someField = (new HttpClient() as object); // Noncompliant FP client = PassThrough(new HttpClient()); // Noncompliant FP client = client ?? new HttpClient(); // Noncompliant FP - client = client == null ? new HttpClient() : client; // Noncompliant FP + client = client == null ? new HttpClient() : client; // Compliant PassThrough(new HttpClient()); // Noncompliant var local = new HttpClient(); // Noncompliant local = new System.Net.Http.HttpClient(); // Noncompliant