diff --git a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md index ecd6860b49..a4b1a7ebbd 100644 --- a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md +++ b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md @@ -7,7 +7,8 @@ Rule ID | Category | Severity | Notes CA1865 | Performance | Info | UseStringMethodCharOverloadWithSingleCharacters, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1865) CA1866 | Performance | Info | UseStringMethodCharOverloadWithSingleCharacters, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1866) CA1867 | Performance | Disabled | UseStringMethodCharOverloadWithSingleCharacters, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1867) -CA1868 | Performance | Info | DoNotGuardSetAddOrRemoveByContains, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1865) +CA1868 | Performance | Info | DoNotGuardSetAddOrRemoveByContains, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1868) +CA1869 | Performance | Info | AvoidSingleUseOfLocalJsonSerializerOptions, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/CA1869) CA2261 | Usage | Warning | DoNotUseConfigureAwaitWithSuppressThrowing, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2250) CA1510 | Maintainability | Info | UseExceptionThrowHelpers, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1510) CA1511 | Maintainability | Info | UseExceptionThrowHelpers, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1511) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx index b4746a5b3e..01eac786fd 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx @@ -2129,4 +2129,13 @@ Widening and user defined conversions are not supported with generic types. Use char overload + + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. + + + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. Single use 'JsonSerializerOptions' instances can substantially degrade the performance of your application. + + + Cache and reuse 'JsonSerializerOptions' instances + \ No newline at end of file diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/AvoidSingleUseOfLocalJsonSerializerOptions.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/AvoidSingleUseOfLocalJsonSerializerOptions.cs new file mode 100644 index 0000000000..94f84bfc06 --- /dev/null +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/AvoidSingleUseOfLocalJsonSerializerOptions.cs @@ -0,0 +1,328 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis; +using System.Collections.Immutable; +using Analyzer.Utilities; +using Analyzer.Utilities.Extensions; +using Microsoft.CodeAnalysis.Operations; +using System.Diagnostics.CodeAnalysis; +using System; +using System.Collections.Generic; + +namespace Microsoft.NetCore.Analyzers.Performance +{ + using static MicrosoftNetCoreAnalyzersResources; + + [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] + public sealed class AvoidSingleUseOfLocalJsonSerializerOptions : DiagnosticAnalyzer + { + internal static readonly DiagnosticDescriptor s_Rule = DiagnosticDescriptorHelper.Create( + id: "CA1869", + title: CreateLocalizableResourceString(nameof(AvoidSingleUseOfLocalJsonSerializerOptionsTitle)), + messageFormat: CreateLocalizableResourceString(nameof(AvoidSingleUseOfLocalJsonSerializerOptionsMessage)), + category: DiagnosticCategory.Performance, + ruleLevel: RuleLevel.IdeSuggestion, + description: CreateLocalizableResourceString(nameof(AvoidSingleUseOfLocalJsonSerializerOptionsDescription)), + isPortedFxCopRule: false, + isDataflowRule: false); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(s_Rule); + + public override void Initialize(AnalysisContext context) + { + context.EnableConcurrentExecution(); + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.RegisterCompilationStartAction(OnCompilationStart); + } + + private static void OnCompilationStart(CompilationStartAnalysisContext context) + { + Compilation compilation = context.Compilation; + + compilation.TryGetOrCreateTypeByMetadataName( + WellKnownTypeNames.SystemTextJsonJsonSerializerOptions, out INamedTypeSymbol? jsonSerializerOptionsSymbol); + + compilation.TryGetOrCreateTypeByMetadataName( + WellKnownTypeNames.SystemTextJsonJsonSerializer, out INamedTypeSymbol? jsonSerializerSymbol); + + if (jsonSerializerOptionsSymbol is null || jsonSerializerSymbol is null) + { + return; + } + + context.RegisterOperationAction( + context => + { + var operation = (IObjectCreationOperation)context.Operation; + + INamedTypeSymbol? typeSymbol = operation.Constructor?.ContainingType; + if (SymbolEqualityComparer.Default.Equals(typeSymbol, jsonSerializerOptionsSymbol)) + { + if (IsCtorUsedAsArgumentForJsonSerializer(operation, jsonSerializerSymbol) || + IsLocalUsedAsArgumentForJsonSerializerOnly(operation, jsonSerializerSymbol)) + { + context.ReportDiagnostic(operation.CreateDiagnostic(s_Rule)); + } + } + }, + OperationKind.ObjectCreation); + } + + private static bool IsCtorUsedAsArgumentForJsonSerializer(IObjectCreationOperation objCreationOperation, INamedTypeSymbol jsonSerializerSymbol) + { + IOperation operation = WalkUpConditional(objCreationOperation); + + return operation.Parent is IArgumentOperation argumentOperation && + IsArgumentForJsonSerializer(argumentOperation, jsonSerializerSymbol); + } + + private static bool IsArgumentForJsonSerializer(IArgumentOperation argumentOperation, INamedTypeSymbol jsonSerializerSymbol) + { + return argumentOperation.Parent is IInvocationOperation invocationOperation && + SymbolEqualityComparer.Default.Equals(invocationOperation.TargetMethod.ContainingType, jsonSerializerSymbol); + } + + private static bool IsLocalUsedAsArgumentForJsonSerializerOnly(IObjectCreationOperation objCreation, INamedTypeSymbol jsonSerializerSymbol) + { + IOperation operation = WalkUpConditional(objCreation); + + if (!IsLocalAssignment(operation, out List? localSymbols)) + { + return false; + } + + IBlockOperation? localBlock = objCreation.GetFirstParentBlock(); + bool isSingleUseJsonSerializerInvocation = false; + + foreach (IOperation descendant in localBlock.Descendants()) + { + if (descendant is not ILocalReferenceOperation localRefOperation || + !localSymbols.Contains(localRefOperation.Local)) + { + continue; + } + + // Avoid cases that would potentially make the local escape current block scope. + if (IsArgumentOfJsonSerializer(descendant, jsonSerializerSymbol, out bool isArgumentOfInvocation)) + { + // Case: used more than once i.e: not single-use. + if (isSingleUseJsonSerializerInvocation) + { + return false; + } + + isSingleUseJsonSerializerInvocation = true; + } + + // Case: passed as argument of a non-JsonSerializer method. + else if (isArgumentOfInvocation) + { + return false; + } + + if (IsFieldOrPropertyAssignment(descendant)) + { + return false; + } + + // Case: deconstruction assignment. + if (IsTupleForDeconstructionTargetingFieldOrProperty(descendant)) + { + return false; + } + + // Case: local goes into closure. + if (IsClosureOnLambdaOrLocalFunction(descendant, localBlock!)) + { + return false; + } + } + + return isSingleUseJsonSerializerInvocation; + } + + [return: NotNullIfNotNull(nameof(operation))] + private static IOperation? WalkUpConditional(IOperation? operation) + { + if (operation is null) + return null; + + while (operation.Parent is IConditionalOperation conditionalOperation) + { + operation = conditionalOperation; + } + + return operation; + } + + private static bool IsArgumentOfJsonSerializer(IOperation operation, INamedTypeSymbol jsonSerializerSymbol, out bool isArgumentOfInvocation) + { + if (operation.Parent is IArgumentOperation arg && arg.Parent is IInvocationOperation inv) + { + isArgumentOfInvocation = true; + return SymbolEqualityComparer.Default.Equals(inv.TargetMethod.ContainingType, jsonSerializerSymbol); + } + + isArgumentOfInvocation = false; + return false; + } + + private static bool IsFieldOrPropertyAssignment(IOperation operation) + { + IOperation? current = operation.Parent; + + while (current is IAssignmentOperation assignment) + { + if (assignment.Target is IFieldReferenceOperation or IPropertyReferenceOperation) + { + return true; + } + + current = current.Parent; + } + + return false; + } + + private static bool IsTupleForDeconstructionTargetingFieldOrProperty(IOperation operation) + { + IOperation? current = operation.Parent; + + if (current is not ITupleOperation tuple) + { + return false; + } + + Stack depth = new Stack(); + depth.Push(tuple.Elements.IndexOf(operation)); + + // walk-up right-hand nested tuples. + while (tuple.Parent is ITupleOperation parent) + { + depth.Push(parent.Elements.IndexOf(tuple)); + tuple = parent; + } + + current = tuple.WalkUpConversion().Parent; + if (current is not IDeconstructionAssignmentOperation deconstruction) + { + return false; + } + + // walk-down left-hand nested tuples and see if it targets a field or property. + if (deconstruction.Target is not ITupleOperation deconstructionTarget) + { + return false; + } + + tuple = deconstructionTarget; + + IOperation? target = null; + while (depth.Count > 0) + { + int idx = depth.Pop(); + target = tuple.Elements[idx]; + + if (target is ITupleOperation targetAsTuple) + { + tuple = targetAsTuple; + } + else if (depth.Count > 0) + { + return false; + } + } + + return target is IFieldReferenceOperation or IPropertyReferenceOperation; + } + + private static bool IsClosureOnLambdaOrLocalFunction(IOperation operation, IBlockOperation localBlock) + { + if (!operation.IsWithinLambdaOrLocalFunction(out IOperation? lambdaOrLocalFunc)) + { + return false; + } + + IBlockOperation? block = lambdaOrLocalFunc switch + { + IAnonymousFunctionOperation lambda => lambda.Body, + ILocalFunctionOperation localFunc => localFunc.Body, + _ => throw new InvalidOperationException() + }; + + return block != localBlock; + } + + private static bool IsLocalAssignment(IOperation operation, [NotNullWhen(true)] out List? localSymbols) + { + localSymbols = null; + IOperation? currentOperation = operation.Parent; + + while (currentOperation is not null) + { + // for cases like: + // var options; + // options = new JsonSerializerOptions(); + if (currentOperation is IExpressionStatementOperation) + { + IOperation? tmpOperation = operation.Parent; + while (tmpOperation is IAssignmentOperation assignment) + { + if (assignment.Target is IFieldReferenceOperation or IPropertyReferenceOperation) + { + return false; + } + else if (assignment.Target is ILocalReferenceOperation localRef) + { + localSymbols ??= new List(); + localSymbols.Add(localRef.Local); + } + + tmpOperation = assignment.Parent; + } + + return localSymbols != null; + } + // For cases like: + // var options = new JsonSerializerOptions(); + else if (currentOperation is IVariableDeclarationOperation declaration) + { + if (operation.Parent is IAssignmentOperation assignment) + { + foreach (IOperation children in assignment.Children) + { + if (children is IFieldReferenceOperation or IPropertyReferenceOperation) + { + return false; + } + } + } + + var local = GetLocalSymbolFromDeclaration(declaration); + if (local != null) + { + localSymbols = new List { local }; + } + + return local != null; + } + + currentOperation = currentOperation.Parent; + } + + return false; + } + + private static ILocalSymbol? GetLocalSymbolFromDeclaration(IVariableDeclarationOperation declaration) + { + if (declaration.Declarators.Length != 1) + { + return null; + } + + IVariableDeclaratorOperation declarator = declaration.Declarators[0]; + return declarator.Symbol; + } + } +} diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf index b22b151ca5..624885ff9e 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf @@ -67,6 +67,21 @@ Vyhněte se konstantním polím jako argumentům + + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. Single use 'JsonSerializerOptions' instances can substantially degrade the performance of your application. + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. Single use 'JsonSerializerOptions' instances can substantially degrade the performance of your application. + + + + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. + + + + Cache and reuse 'JsonSerializerOptions' instances + Cache and reuse 'JsonSerializerOptions' instances + + Marshalling of 'StringBuilder' always creates a native buffer copy, resulting in multiple allocations for one marshalling operation. Zařazením parametru StringBuilder se vždy vytvoří kopie nativní vyrovnávací paměti, která bude mít za následek vícenásobné přidělení pro jednu operaci zařazování. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf index d50d97e0b7..216cd38324 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf @@ -67,6 +67,21 @@ Konstantenmatrizen als Argumente vermeiden + + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. Single use 'JsonSerializerOptions' instances can substantially degrade the performance of your application. + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. Single use 'JsonSerializerOptions' instances can substantially degrade the performance of your application. + + + + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. + + + + Cache and reuse 'JsonSerializerOptions' instances + Cache and reuse 'JsonSerializerOptions' instances + + Marshalling of 'StringBuilder' always creates a native buffer copy, resulting in multiple allocations for one marshalling operation. Beim Marshalling von "StringBuilder" wird immer eine native Pufferkopie erstellt, sodass mehrere Zuordnungen für einen Marshallingvorgang vorhanden sind. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf index 563f64c0b6..cacd29161e 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf @@ -67,6 +67,21 @@ Evitar matrices constantes como argumentos + + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. Single use 'JsonSerializerOptions' instances can substantially degrade the performance of your application. + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. Single use 'JsonSerializerOptions' instances can substantially degrade the performance of your application. + + + + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. + + + + Cache and reuse 'JsonSerializerOptions' instances + Cache and reuse 'JsonSerializerOptions' instances + + Marshalling of 'StringBuilder' always creates a native buffer copy, resulting in multiple allocations for one marshalling operation. Al serializar "StringBuilder" siempre se crea una copia del búfer nativo, lo que da lugar a varias asignaciones para una operación de serialización. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf index d089133fe7..a336bc908c 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf @@ -67,6 +67,21 @@ Éviter les tableaux constants en tant qu’arguments + + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. Single use 'JsonSerializerOptions' instances can substantially degrade the performance of your application. + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. Single use 'JsonSerializerOptions' instances can substantially degrade the performance of your application. + + + + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. + + + + Cache and reuse 'JsonSerializerOptions' instances + Cache and reuse 'JsonSerializerOptions' instances + + Marshalling of 'StringBuilder' always creates a native buffer copy, resulting in multiple allocations for one marshalling operation. Le marshaling de 'StringBuilder' crée toujours une copie de la mémoire tampon native, ce qui entraîne plusieurs allocations pour une seule opération de marshaling. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf index a2da573944..042c2dbccb 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf @@ -67,6 +67,21 @@ Evitare matrici costanti come argomenti + + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. Single use 'JsonSerializerOptions' instances can substantially degrade the performance of your application. + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. Single use 'JsonSerializerOptions' instances can substantially degrade the performance of your application. + + + + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. + + + + Cache and reuse 'JsonSerializerOptions' instances + Cache and reuse 'JsonSerializerOptions' instances + + Marshalling of 'StringBuilder' always creates a native buffer copy, resulting in multiple allocations for one marshalling operation. Il marshalling di 'StringBuilder' crea sempre una copia del buffer nativo, di conseguenza vengono generate più allocazioni per una singola operazione di marshalling. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf index 1f4415e4a0..e4a770a9a2 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf @@ -67,6 +67,21 @@ 引数として定数配列を使用しない + + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. Single use 'JsonSerializerOptions' instances can substantially degrade the performance of your application. + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. Single use 'JsonSerializerOptions' instances can substantially degrade the performance of your application. + + + + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. + + + + Cache and reuse 'JsonSerializerOptions' instances + Cache and reuse 'JsonSerializerOptions' instances + + Marshalling of 'StringBuilder' always creates a native buffer copy, resulting in multiple allocations for one marshalling operation. 'StringBuilder' をマーシャリングすると、ネイティブ バッファーのコピーが常に作成され、1 回のマーシャリング操作に対して複数の割り当てが発生します。 diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf index 06952811c8..bcf12ee730 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf @@ -67,6 +67,21 @@ 상수 배열을 인수로 사용하지 않습니다. + + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. Single use 'JsonSerializerOptions' instances can substantially degrade the performance of your application. + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. Single use 'JsonSerializerOptions' instances can substantially degrade the performance of your application. + + + + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. + + + + Cache and reuse 'JsonSerializerOptions' instances + Cache and reuse 'JsonSerializerOptions' instances + + Marshalling of 'StringBuilder' always creates a native buffer copy, resulting in multiple allocations for one marshalling operation. 'StringBuilder'를 마샬링하는 경우 항상 네이티브 버퍼 복사본이 만들어지므로 하나의 마샬링 작업에 대해 할당이 여러 번 이루어집니다. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf index 8ebfe15918..93ce237ccf 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf @@ -67,6 +67,21 @@ Unikaj tablic stałych jako argumentów + + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. Single use 'JsonSerializerOptions' instances can substantially degrade the performance of your application. + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. Single use 'JsonSerializerOptions' instances can substantially degrade the performance of your application. + + + + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. + + + + Cache and reuse 'JsonSerializerOptions' instances + Cache and reuse 'JsonSerializerOptions' instances + + Marshalling of 'StringBuilder' always creates a native buffer copy, resulting in multiple allocations for one marshalling operation. Marshalling elementu „StringBuilder” zawsze tworzy natywną kopię buforu, co powoduje powstanie wielu alokacji dla jednej operacji marshallingu. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf index 3f547cb20a..15d69fc4ee 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf @@ -67,6 +67,21 @@ Evite matrizes constantes como argumentos + + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. Single use 'JsonSerializerOptions' instances can substantially degrade the performance of your application. + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. Single use 'JsonSerializerOptions' instances can substantially degrade the performance of your application. + + + + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. + + + + Cache and reuse 'JsonSerializerOptions' instances + Cache and reuse 'JsonSerializerOptions' instances + + Marshalling of 'StringBuilder' always creates a native buffer copy, resulting in multiple allocations for one marshalling operation. O marshaling de 'StringBuilder' sempre cria uma cópia de buffer nativo, resultando em várias alocações para uma operação de marshalling. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf index 4c3c5eebc7..158d3a0b3d 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf @@ -67,6 +67,21 @@ Избегайте использования константных массивов в качестве аргументов + + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. Single use 'JsonSerializerOptions' instances can substantially degrade the performance of your application. + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. Single use 'JsonSerializerOptions' instances can substantially degrade the performance of your application. + + + + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. + + + + Cache and reuse 'JsonSerializerOptions' instances + Cache and reuse 'JsonSerializerOptions' instances + + Marshalling of 'StringBuilder' always creates a native buffer copy, resulting in multiple allocations for one marshalling operation. При маршалировании "StringBuilder" всегда создается собственная копия буфера, что приводит к множественным выделениям для одной операции маршалирования. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf index 4efaba1b55..82848fdd27 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf @@ -67,6 +67,21 @@ Sabit dizileri bağımsız değişkenler olarak kullanmaktan sakının + + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. Single use 'JsonSerializerOptions' instances can substantially degrade the performance of your application. + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. Single use 'JsonSerializerOptions' instances can substantially degrade the performance of your application. + + + + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. + + + + Cache and reuse 'JsonSerializerOptions' instances + Cache and reuse 'JsonSerializerOptions' instances + + Marshalling of 'StringBuilder' always creates a native buffer copy, resulting in multiple allocations for one marshalling operation. 'StringBuilder' öğesinin hazırlanması her zaman, bir hazırlama işlemi için birden çok ayırmaya neden olan yerel arabellek kopyası oluşturur. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf index a0ff941905..02d8aca805 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf @@ -67,6 +67,21 @@ 不要将常量数组作为参数 + + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. Single use 'JsonSerializerOptions' instances can substantially degrade the performance of your application. + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. Single use 'JsonSerializerOptions' instances can substantially degrade the performance of your application. + + + + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. + + + + Cache and reuse 'JsonSerializerOptions' instances + Cache and reuse 'JsonSerializerOptions' instances + + Marshalling of 'StringBuilder' always creates a native buffer copy, resulting in multiple allocations for one marshalling operation. "StringBuilder" 的封送处理总是会创建一个本机缓冲区副本,这导致一个封送处理操作出现多次分配。 diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf index 589f3ee319..e5a66cd4a6 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf @@ -67,6 +67,21 @@ 避免常數陣列作為引數 + + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. Single use 'JsonSerializerOptions' instances can substantially degrade the performance of your application. + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. Single use 'JsonSerializerOptions' instances can substantially degrade the performance of your application. + + + + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. + + + + Cache and reuse 'JsonSerializerOptions' instances + Cache and reuse 'JsonSerializerOptions' instances + + Marshalling of 'StringBuilder' always creates a native buffer copy, resulting in multiple allocations for one marshalling operation. 封送處理 'StringBuilder' 一律都會建立原生緩衝區複本,因而導致單一封送處理作業出現多重配置。 diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md index 5b4eae4792..ef63ae6f98 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md @@ -1788,6 +1788,18 @@ Do not guard 'Add(item)' or 'Remove(item)' with 'Contains(item)' for the set. Th |CodeFix|True| --- +## [CA1869](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1869): Cache and reuse 'JsonSerializerOptions' instances + +Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. Single use 'JsonSerializerOptions' instances can substantially degrade the performance of your application. + +|Item|Value| +|-|-| +|Category|Performance| +|Enabled|True| +|Severity|Info| +|CodeFix|False| +--- + ## [CA2000](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2000): Dispose objects before losing scope If a disposable object is not explicitly disposed before all references to it are out of scope, the object will be disposed at some indeterminate time when the garbage collector runs the finalizer of the object. Because an exceptional event might occur that will prevent the finalizer of the object from running, the object should be explicitly disposed instead. diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif index 368a728f59..b80746b7b8 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif @@ -3292,6 +3292,26 @@ ] } }, + "CA1869": { + "id": "CA1869", + "shortDescription": "Cache and reuse 'JsonSerializerOptions' instances", + "fullDescription": "Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. Single use 'JsonSerializerOptions' instances can substantially degrade the performance of your application.", + "defaultLevel": "note", + "helpUri": "https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1869", + "properties": { + "category": "Performance", + "isEnabledByDefault": true, + "typeName": "AvoidSingleUseOfLocalJsonSerializerOptions", + "languages": [ + "C#", + "Visual Basic" + ], + "tags": [ + "Telemetry", + "EnabledRuleInAggressiveMode" + ] + } + }, "CA2000": { "id": "CA2000", "shortDescription": "Dispose objects before losing scope", diff --git a/src/NetAnalyzers/RulesMissingDocumentation.md b/src/NetAnalyzers/RulesMissingDocumentation.md index 42df726b08..eb030b9cc5 100644 --- a/src/NetAnalyzers/RulesMissingDocumentation.md +++ b/src/NetAnalyzers/RulesMissingDocumentation.md @@ -13,5 +13,6 @@ CA1863 | | Use char overload | CA1866 | | Use char overload | CA1867 | | Use char overload | +CA1869 | | Cache and reuse 'JsonSerializerOptions' instances | CA2021 | | Do not call Enumerable.Cast\ or Enumerable.OfType\ with incompatible types | CA2261 | | Do not use ConfigureAwaitOptions.SuppressThrowing with Task\ | diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/AvoidSingleUseOfLocalJsonSerializerOptionsTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/AvoidSingleUseOfLocalJsonSerializerOptionsTests.cs new file mode 100644 index 0000000000..63ec104493 --- /dev/null +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/AvoidSingleUseOfLocalJsonSerializerOptionsTests.cs @@ -0,0 +1,536 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp; +using Xunit; +using VerifyCS = Test.Utilities.CSharpCodeFixVerifier< + Microsoft.NetCore.Analyzers.Performance.AvoidSingleUseOfLocalJsonSerializerOptions, + Microsoft.CodeAnalysis.Testing.EmptyCodeFixProvider>; +using VerifyVB = Test.Utilities.VisualBasicCodeFixVerifier< + Microsoft.NetCore.Analyzers.Performance.AvoidSingleUseOfLocalJsonSerializerOptions, + Microsoft.CodeAnalysis.Testing.EmptyCodeFixProvider>; + +namespace Microsoft.NetCore.Analyzers.Performance.UnitTests +{ + public class AvoidSingleUseOfLocalJsonSerializerOptionsTests + { + #region Diagnostic Tests + + [Fact] + public Task CS_UseNewOptionsAsArgument() + => VerifyCS.VerifyAnalyzerAsync(""" + using System; + using System.Text.Json; + + internal class Program + { + static void Main(string[] args) + { + string json = JsonSerializer.Serialize(args, {|CA1869:new JsonSerializerOptions { AllowTrailingCommas = true }|}); + Console.WriteLine(json); + } + } + """); + + [Fact] + public Task CS_UseNewLocalOptionsAsArgument() + => VerifyCS.VerifyAnalyzerAsync(""" + using System; + using System.Text.Json; + + internal class Program + { + static void Main(string[] args) + { + JsonSerializerOptions options = {|CA1869:new JsonSerializerOptions()|}; + options.AllowTrailingCommas = true; + + string json = JsonSerializer.Serialize(args, options); + Console.WriteLine(json); + } + } + """); + + [Fact] + public Task VB_UseNewOptionsAsArgument() + => VerifyVB.VerifyAnalyzerAsync(""" + Imports System + Imports System.Text.Json + + Module Program + Sub Main(args As String()) + Dim json = JsonSerializer.Serialize(args, {|CA1869:New JsonSerializerOptions With {.AllowTrailingCommas = True}|}) + Console.WriteLine(json) + End Sub + End Module + """); + + [Fact] + public Task VB_UseNewLocalOptionsAsArgument() + => VerifyVB.VerifyAnalyzerAsync(""" + Imports System + Imports System.Text.Json + + Module Program + Sub Main(args As String()) + Dim options = {|CA1869:New JsonSerializerOptions()|} + options.AllowTrailingCommas = True + + Dim json = JsonSerializer.Serialize(args, options) + Console.WriteLine(json) + End Sub + End Module + """); + + [Theory] + [InlineData("{|CA1869:new JsonSerializerOptions()|}")] + [InlineData("{|CA1869:new JsonSerializerOptions{}|}")] + [InlineData("({|CA1869:new JsonSerializerOptions()|})")] + [InlineData("(({|CA1869:new JsonSerializerOptions()|}))")] + [InlineData("1 == 1 ? {|CA1869:new JsonSerializerOptions()|} : null")] + [InlineData("1 == 1 ? null : 2 == 2 ? null : {|CA1869:new JsonSerializerOptions()|}")] + public Task CS_UseNewOptionsAsArgument_Variants(string expression) + => VerifyCS.VerifyAnalyzerAsync($$""" + using System.Text.Json; + + class Program + { + static string Serialize(T value) + { + return JsonSerializer.Serialize(value, {{expression}}); + } + } + """); + + [Theory] + [InlineData("{|CA1869:new JsonSerializerOptions()|}")] + [InlineData("{|CA1869:new JsonSerializerOptions{}|}")] + [InlineData("({|CA1869:new JsonSerializerOptions()|})")] + [InlineData("(({|CA1869:new JsonSerializerOptions()|}))")] + [InlineData("1 == 1 ? {|CA1869:new JsonSerializerOptions()|} : null")] + [InlineData("1 == 1 ? null : 2 == 2 ? null : {|CA1869:new JsonSerializerOptions()|}")] + public Task CS_UseNewLocalOptionsAsArgument_Variants(string expression) + => VerifyCS.VerifyAnalyzerAsync($$""" + using System.Text.Json; + + class Program + { + static string Serialize(T value) + { + var options = {{expression}}; + return JsonSerializer.Serialize(value, options); + } + } + """); + + [Fact] + public Task CS_UseNewLocalOptionsAsArgument_Assignment() + => VerifyCS.VerifyAnalyzerAsync(""" + using System.Text.Json; + + class Program + { + static string Serialize(T value) + { + JsonSerializerOptions opt; + opt = {|CA1869:new JsonSerializerOptions()|}; + + return JsonSerializer.Serialize(value, opt); + } + } + """); + + [Fact] + public Task CS_UseNewLocalOptionsAsArgument_SecondLocalReference() + => VerifyCS.VerifyAnalyzerAsync(""" + using System.Text.Json; + + class Program + { + static string Serialize(T value) + { + JsonSerializerOptions opt = {|CA1869:new JsonSerializerOptions()|}; + _ = opt; + + return JsonSerializer.Serialize(value, opt); + } + } + """); + + [Fact] // this could be better handled with data flow analysis. + public Task CS_UseNewLocalOptionsAsArgument_OverwriteLocal() + => VerifyCS.VerifyAnalyzerAsync(""" + using System.Text.Json; + + class Program + { + static JsonSerializerOptions s_options; + + static string Serialize(T value) + { + JsonSerializerOptions opt = {|CA1869:new JsonSerializerOptions()|}; + opt = s_options; + + return JsonSerializer.Serialize(value, opt); + } + } + """); + + [Theory] + [InlineData("opt1")] + [InlineData("opt2")] + public Task CS_UseNewLocalOptionsAsArgument_MultiAssignment(string expression) + => VerifyCS.VerifyAnalyzerAsync($$""" + using System.Text.Json; + + class Program + { + static string Serialize(T value) + { + JsonSerializerOptions opt1, opt2; + opt1 = opt2 = {|CA1869:new JsonSerializerOptions()|}; + + return JsonSerializer.Serialize(value, {{expression}}); + } + } + """); + + [Fact] + public Task CS_UseNewLocalOptionsAsArgument_Delegate() + => VerifyCS.VerifyAnalyzerAsync(""" + using System; + using System.Text.Json; + + class Program + { + static Action Serialize(T value) + { + Action lambda = () => + { + JsonSerializerOptions opt = {|CA1869:new JsonSerializerOptions()|}; + JsonSerializer.Serialize(value, opt); + }; + return lambda; + } + } + """); + + [Fact] + public Task CS_UseNewLocalOptionsAsArgument_LocalFunction() + => VerifyCS.VerifyAnalyzerAsync(""" + using System.Text.Json; + + class Program + { + static string Serialize(T value) + { + return LocalFunc(); + + string LocalFunc() + { + JsonSerializerOptions opt = {|CA1869:new JsonSerializerOptions()|}; + return JsonSerializer.Serialize(value, opt); + } + } + } + """); + + #endregion + + #region No Diagnostic Tests + [Fact] + public Task CS_UseNewOptionsAsArgument_NonSerializerMethod_NoWarn() + => VerifyCS.VerifyAnalyzerAsync(""" + using System.Text.Json; + + class Program + { + static string Serialize(T value) + { + return MyCustomSerializeMethod(value, new JsonSerializerOptions()); + } + + static string MyCustomSerializeMethod(T value, JsonSerializerOptions options) + => JsonSerializer.Serialize(value, options); + } + """); + + [Fact] + public Task CS_UseNewLocalOptionsAsArgument_NonSerializerMethod_NoWarn() + => VerifyCS.VerifyAnalyzerAsync(""" + using System.Text.Json; + + class Program + { + static string Serialize(T value) + { + var options = new JsonSerializerOptions(); + return MyCustomSerializeMethod(value, options); + } + + static string MyCustomSerializeMethod(T value, JsonSerializerOptions options) + => JsonSerializer.Serialize(value, options); + } + """); + + [Fact] + public Task CS_UseNewLocalOptionsAsArgument_EscapeCurrentScope_NonSerializerMethod_NoWarn() + => VerifyCS.VerifyAnalyzerAsync(""" + using System.Text.Json; + + class Program + { + static string Serialize(T value) + { + var options = new JsonSerializerOptions(); + string json1 = MyCustomSerializeMethod(value, options); + string json2 = JsonSerializer.Serialize(value, options); + return json1 + json2; + } + + static string MyCustomSerializeMethod(T value, JsonSerializerOptions options) + => JsonSerializer.Serialize(value, options); + } + """); + + [Theory] + [MemberData(nameof(CS_UseNewLocalOptionsAsArgument_FieldAssignment_NoWarn_TheoryData))] + public Task CS_UseNewLocalOptionsAsArgument_FieldAssignment_NoWarn(string snippet) + { + var test = new VerifyCS.Test(); + test.LanguageVersion = LanguageVersion.CSharp8; // needed for coalescing assignment. + test.TestCode = $$""" + using System; + using System.Text.Json; + + class Program + { + static JsonSerializerOptions s_options; + + static string Serialize(T value) + { + {{snippet}} + return JsonSerializer.Serialize(value, opt); + } + } + """; + + return test.RunAsync(); + } + + [Theory] + [MemberData(nameof(CS_UseNewLocalOptionsAsArgument_PropertyAssignment_NoWarn_TheoryData))] + public Task CS_UseNewLocalOptionsAsArgument_PropertyAssignment_NoWarn(string snippet) + { + var test = new VerifyCS.Test(); + test.LanguageVersion = LanguageVersion.CSharp8; // needed for coalescing assignment. + test.TestCode = $$""" + using System; + using System.Text.Json; + + class Program + { + private JsonSerializerOptions Options { get; set; } + + string Serialize(T value) + { + {{snippet}} + return JsonSerializer.Serialize(value, opt); + } + } + """; + return test.RunAsync(); + } + + public static IEnumerable CS_UseNewLocalOptionsAsArgument_FieldAssignment_NoWarn_TheoryData() + { + return CS_UseNewLocalOptionsAsArgument_Assignment_TheoryData(useField: true).Select(e => new object[] { e }); + } + + public static IEnumerable CS_UseNewLocalOptionsAsArgument_PropertyAssignment_NoWarn_TheoryData() + { + return CS_UseNewLocalOptionsAsArgument_Assignment_TheoryData(useField: false).Select(e => new object[] { e }); + } + + private static List CS_UseNewLocalOptionsAsArgument_Assignment_TheoryData(bool useField) + { + string target = useField ? "s_options" : "Options"; + + return new List() + { + $@"JsonSerializerOptions opt = new JsonSerializerOptions(); + {target} = opt;", + + $@"JsonSerializerOptions opt; + {target} = opt = new JsonSerializerOptions();", + + $@"JsonSerializerOptions opt = {target} = new JsonSerializerOptions();", + + $@"JsonSerializerOptions opt = {target} ??= new JsonSerializerOptions();", + + $@"JsonSerializerOptions opt = new JsonSerializerOptions(); + {target} ??= opt;", + + $@"JsonSerializerOptions opt = new JsonSerializerOptions(); + ({target}, _) = (opt, 42);", + + $@"JsonSerializerOptions opt = new JsonSerializerOptions(); + (({target}, _), _) = ((opt, 42), 42);" + }; + } + + [Fact] + public Task CS_UseNewLocalOptionsAsArgument_NotSingleUse_NoWarn() + => VerifyCS.VerifyAnalyzerAsync(""" + using System.Text.Json; + + class Program + { + static string SerializeTwice(T value) + { + JsonSerializerOptions opt = new JsonSerializerOptions(); + + string str1 = JsonSerializer.Serialize(value, opt); + string str2 = JsonSerializer.Serialize(value, opt); + + return str1 + str2; + } + } + """); + + [Theory] + [InlineData("opt1", "opt2")] + [InlineData("opt1", "opt3")] + [InlineData("opt2", "opt3")] + public Task CS_UseNewLocalOptionsAsArgument_MultiAssignment_NotSingleUse_NoWarn(string expression1, string expression2) + => VerifyCS.VerifyAnalyzerAsync($$""" + using System.Text.Json; + + class Program + { + static string Serialize(T value) + { + JsonSerializerOptions opt1, opt2, opt3; + opt1 = opt2 = opt3 = new JsonSerializerOptions(); + + string json1 = JsonSerializer.Serialize(value, {{expression1}}); + string json2 = JsonSerializer.Serialize(value, {{expression2}}); + + return json1 + json2; + } + } + """); + + [Theory] + [InlineData("opt1")] + [InlineData("opt2")] + [InlineData("opt3")] + public Task CS_UseNewLocalOptionsAsArgument_MultiAssignment_EscapeCurrentScope_FieldAssignment_NoWarn(string expression) + => VerifyCS.VerifyAnalyzerAsync($$""" + using System.Text.Json; + + class Program + { + static JsonSerializerOptions s_options; + + static string Serialize(T value) + { + JsonSerializerOptions opt1, opt2, opt3; + opt1 = opt2 = opt3 = new JsonSerializerOptions(); + + s_options = {{expression}}; + + return JsonSerializer.Serialize(value, opt1); + } + } + """); + + [Theory] + [InlineData("opt1 = opt2 = s_options")] + [InlineData("opt1 = s_options = opt2")] + [InlineData("s_options = opt1 = opt2")] + public Task CS_UseNewLocalOptionsAsArgument_MultiAssignment_EscapeCurrentScope_FieldInMultiAssignment_NoWarn(string expression) + => VerifyCS.VerifyAnalyzerAsync($$""" + using System.Text.Json; + + class Program + { + static JsonSerializerOptions s_options; + + static string Serialize(T value) + { + JsonSerializerOptions opt1, opt2; + {{expression}} = new JsonSerializerOptions(); + + return JsonSerializer.Serialize(value, opt1); + } + } + """); + + [Theory] + [InlineData("s_options = opt1 = opt2")] + [InlineData("opt1 = s_options = opt2")] + public Task CSharpUseNewOptionsAsLocalThenAsArgument_AssignmentOnNextStatement_Multiple_WithEscapeScopeOnAssignment_NoWarn(string expression) + => VerifyCS.VerifyAnalyzerAsync($$""" + using System.Text.Json; + + class Program + { + static JsonSerializerOptions s_options; + + static string Serialize(T value) + { + JsonSerializerOptions opt1, opt2; + opt1 = opt2 = new JsonSerializerOptions(); + + {{expression}}; + + return JsonSerializer.Serialize(value, opt1); + } + } + """); + + [Fact] + public Task CS_UseNewLocalOptionsAsArgument_EscapeCurrentScope_ClosureDelegate_NoWarn() + => VerifyCS.VerifyAnalyzerAsync(""" + using System; + using System.Text.Json; + + class Program + { + static Action Serialize(T value) + { + JsonSerializerOptions opt = new JsonSerializerOptions(); + Action lambda = () => + { + JsonSerializer.Serialize(value, opt); + }; + return lambda; + } + } + """); + + [Fact] + public Task CS_UseNewLocalOptionsAsArgument_EscapeCurrentScope_ClosureLocalFunction_NoWarn() + => VerifyCS.VerifyAnalyzerAsync(""" + using System.Text.Json; + + class Program + { + static string Serialize(T value) + { + JsonSerializerOptions opt = new JsonSerializerOptions(); + return LocalFunc(); + + string LocalFunc() + { + return JsonSerializer.Serialize(value, opt); + } + } + } + """); + #endregion + } +} diff --git a/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt b/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt index 1da70dbbb5..06f507268f 100644 --- a/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt +++ b/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt @@ -12,7 +12,7 @@ Design: CA2210, CA1000-CA1070 Globalization: CA2101, CA1300-CA1311 Mobility: CA1600-CA1601 -Performance: HA, CA1800-CA1868 +Performance: HA, CA1800-CA1869 Security: CA2100-CA2153, CA2300-CA2330, CA3000-CA3147, CA5300-CA5405 Usage: CA1801, CA1806, CA1816, CA2200-CA2209, CA2211-CA2261 Naming: CA1700-CA1727 diff --git a/src/Utilities/Compiler/WellKnownTypeNames.cs b/src/Utilities/Compiler/WellKnownTypeNames.cs index f6ff573c39..98c4ef1ff8 100644 --- a/src/Utilities/Compiler/WellKnownTypeNames.cs +++ b/src/Utilities/Compiler/WellKnownTypeNames.cs @@ -402,6 +402,8 @@ internal static class WellKnownTypeNames public const string SystemSystemException = "System.SystemException"; public const string SystemTextCompositeFormat = "System.Text.CompositeFormat"; public const string SystemTextEncoding = "System.Text.Encoding"; + public const string SystemTextJsonJsonSerializerOptions = "System.Text.Json.JsonSerializerOptions"; + public const string SystemTextJsonJsonSerializer = "System.Text.Json.JsonSerializer"; public const string SystemTextRegularExpressionsRegex = "System.Text.RegularExpressions.Regex"; public const string SystemTextStringBuilder = "System.Text.StringBuilder"; public const string SystemThreadStaticAttribute = "System.ThreadStaticAttribute";