diff --git a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md
index 0dd5e1879d..6e16bd95c7 100644
--- a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md
+++ b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md
@@ -8,6 +8,7 @@ CA1514 | Maintainability | Info | AvoidLengthCheckWhenSlicingToEndAnalyzer, [Doc
CA1515 | Maintainability | Disabled | MakeTypesInternal, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1515)
CA1871 | Performance | Info | DoNotPassNonNullableValueToArgumentNullExceptionThrowIfNull, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1871)
CA1872 | Performance | Info | PreferConvertToHexStringOverBitConverterAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1872)
+CA2022 | Reliability | Warning | AvoidUnreliableStreamReadAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2022)
CA2262 | Usage | Info | ProvideHttpClientHandlerMaxResponseHeaderLengthValueCorrectly, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2262)
CA2263 | Usage | Info | PreferGenericOverloadsAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2263)
CA2264 | Usage | Warning | DoNotPassNonNullableValueToArgumentNullExceptionThrowIfNull, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2264)
diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx
index e998d4bee2..3c1daafb7a 100644
--- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx
+++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx
@@ -239,6 +239,18 @@
Prefer 'static readonly' fields over constant array arguments if the called method is called repeatedly and is not mutating the passed array
{Locked="static readonly"}
+
+ Avoid inexact read with 'Stream.Read'
+
+
+ Use 'Stream.ReadExactly'
+
+
+ A call to 'Stream.Read' may return fewer bytes than requested, resulting in unreliable code if the return value is not checked.
+
+
+ Avoid inexact read with '{0}'
+
Test for empty strings using string length
diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidUnreliableStreamRead.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidUnreliableStreamRead.Fixer.cs
new file mode 100644
index 0000000000..eb8e07b285
--- /dev/null
+++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidUnreliableStreamRead.Fixer.cs
@@ -0,0 +1,130 @@
+// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Immutable;
+using System.Composition;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Analyzer.Utilities;
+using Analyzer.Utilities.Extensions;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CodeActions;
+using Microsoft.CodeAnalysis.CodeFixes;
+using Microsoft.CodeAnalysis.Editing;
+using Microsoft.CodeAnalysis.Operations;
+
+namespace Microsoft.NetCore.Analyzers.Runtime
+{
+ using static MicrosoftNetCoreAnalyzersResources;
+
+ ///
+ /// CA2022:
+ ///
+ [ExportCodeFixProvider(LanguageNames.CSharp, LanguageNames.VisualBasic), Shared]
+ public sealed class AvoidUnreliableStreamReadFixer : CodeFixProvider
+ {
+ private const string Async = nameof(Async);
+ private const string ReadExactly = nameof(ReadExactly);
+ private const string ReadExactlyAsync = nameof(ReadExactlyAsync);
+
+ public sealed override ImmutableArray FixableDiagnosticIds { get; } =
+ ImmutableArray.Create(AvoidUnreliableStreamReadAnalyzer.RuleId);
+
+ public sealed override FixAllProvider GetFixAllProvider()
+ {
+ return WellKnownFixAllProviders.BatchFixer;
+ }
+
+ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
+ {
+ var root = await context.Document.GetRequiredSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
+ var node = root.FindNode(context.Span, getInnermostNodeForTie: true);
+
+ if (node is null)
+ {
+ return;
+ }
+
+ var semanticModel = await context.Document.GetRequiredSemanticModelAsync(context.CancellationToken).ConfigureAwait(false);
+ var operation = semanticModel.GetOperation(node, context.CancellationToken);
+
+ if (operation is not IInvocationOperation invocation ||
+ invocation.Instance is null)
+ {
+ return;
+ }
+
+ var compilation = semanticModel.Compilation;
+ var streamType = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemIOStream);
+
+ if (streamType is null)
+ {
+ return;
+ }
+
+ var readExactlyMethods = streamType.GetMembers(ReadExactly)
+ .OfType()
+ .ToImmutableArray();
+
+ if (readExactlyMethods.IsEmpty)
+ {
+ return;
+ }
+
+ var codeAction = CodeAction.Create(
+ AvoidUnreliableStreamReadCodeFixTitle,
+ ct => ReplaceWithReadExactlyCall(context.Document, ct),
+ nameof(AvoidUnreliableStreamReadCodeFixTitle));
+
+ context.RegisterCodeFix(codeAction, context.Diagnostics);
+
+ async Task ReplaceWithReadExactlyCall(Document document, CancellationToken cancellationToken)
+ {
+ var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false);
+ var generator = editor.Generator;
+ var arguments = invocation.Arguments.GetArgumentsInParameterOrder();
+
+ var isAsyncInvocation = invocation.TargetMethod.Name.EndsWith(Async, StringComparison.Ordinal);
+ var methodExpression = generator.MemberAccessExpression(
+ invocation.Instance.Syntax,
+ isAsyncInvocation ? ReadExactlyAsync : ReadExactly);
+ var methodInvocation = CanUseSpanOverload()
+ ? generator.InvocationExpression(
+ methodExpression,
+ isAsyncInvocation && arguments.Length == 4
+ // Stream.ReadExactlyAsync(buffer, ct)
+ ?[arguments[0].Syntax, arguments[3].Syntax]
+ // Stream.ReadExactly(buffer) and Stream.ReadExactlyAsync(buffer)
+ :[arguments[0].Syntax])
+ : generator.InvocationExpression(
+ methodExpression,
+ invocation.Arguments.Where(a => !a.IsImplicit).Select(a => a.Syntax));
+
+ editor.ReplaceNode(invocation.Syntax, methodInvocation.WithTriviaFrom(invocation.Syntax));
+
+ return document.WithSyntaxRoot(editor.GetChangedRoot());
+
+ bool CanUseSpanOverload()
+ {
+ return arguments.Length >= 3 &&
+ arguments[2].Value is IPropertyReferenceOperation propertyRef &&
+ propertyRef.Property.Name.Equals(WellKnownMemberNames.LengthPropertyName, StringComparison.Ordinal) &&
+ AreSameInstance(arguments[0].Value, propertyRef.Instance);
+ }
+
+ static bool AreSameInstance(IOperation? operation1, IOperation? operation2)
+ {
+ return (operation1, operation2) switch
+ {
+ (IFieldReferenceOperation fieldRef1, IFieldReferenceOperation fieldRef2) => fieldRef1.Member == fieldRef2.Member,
+ (IPropertyReferenceOperation propRef1, IPropertyReferenceOperation propRef2) => propRef1.Member == propRef2.Member,
+ (IParameterReferenceOperation paramRef1, IParameterReferenceOperation paramRef2) => paramRef1.Parameter == paramRef2.Parameter,
+ (ILocalReferenceOperation localRef1, ILocalReferenceOperation localRef2) => localRef1.Local == localRef2.Local,
+ _ => false,
+ };
+ }
+ }
+ }
+ }
+}
diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidUnreliableStreamRead.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidUnreliableStreamRead.cs
new file mode 100644
index 0000000000..8fe079a575
--- /dev/null
+++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidUnreliableStreamRead.cs
@@ -0,0 +1,153 @@
+// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.
+
+using System.Collections.Immutable;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using Analyzer.Utilities;
+using Analyzer.Utilities.Extensions;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Operations;
+
+namespace Microsoft.NetCore.Analyzers.Runtime
+{
+ using static MicrosoftNetCoreAnalyzersResources;
+
+ ///
+ /// CA2022:
+ ///
+ [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)]
+ public sealed class AvoidUnreliableStreamReadAnalyzer : DiagnosticAnalyzer
+ {
+ internal const string RuleId = "CA2022";
+
+ private const string Read = nameof(Read);
+ private const string ReadAsync = nameof(ReadAsync);
+
+ internal static readonly DiagnosticDescriptor Rule = DiagnosticDescriptorHelper.Create(
+ RuleId,
+ CreateLocalizableResourceString(nameof(AvoidUnreliableStreamReadTitle)),
+ CreateLocalizableResourceString(nameof(AvoidUnreliableStreamReadMessage)),
+ DiagnosticCategory.Reliability,
+ RuleLevel.BuildWarning,
+ CreateLocalizableResourceString(nameof(AvoidUnreliableStreamReadDescription)),
+ isPortedFxCopRule: false,
+ isDataflowRule: false);
+
+ public sealed override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule);
+
+ public sealed override void Initialize(AnalysisContext context)
+ {
+ context.EnableConcurrentExecution();
+ context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
+ context.RegisterCompilationStartAction(OnCompilationStart);
+ }
+
+ private void OnCompilationStart(CompilationStartAnalysisContext context)
+ {
+ if (!RequiredSymbols.TryGetSymbols(context.Compilation, out var symbols))
+ {
+ return;
+ }
+
+ context.RegisterOperationAction(AnalyzeInvocation, OperationKind.Invocation);
+
+ void AnalyzeInvocation(OperationAnalysisContext context)
+ {
+ var invocation = (IInvocationOperation)context.Operation;
+
+ if (symbols.IsAnyStreamReadMethod(invocation.TargetMethod))
+ {
+ if (invocation.Parent is not IExpressionStatementOperation)
+ {
+ return;
+ }
+ }
+ else if (symbols.IsAnyStreamReadAsyncMethod(invocation.TargetMethod))
+ {
+ if (invocation.Parent is not IAwaitOperation awaitOperation ||
+ awaitOperation.Parent is not IExpressionStatementOperation)
+ {
+ return;
+ }
+ }
+ else
+ {
+ return;
+ }
+
+ context.ReportDiagnostic(invocation.CreateDiagnostic(Rule, invocation.TargetMethod.ToDisplayString()));
+ }
+ }
+
+ internal sealed class RequiredSymbols
+ {
+ private RequiredSymbols(
+ ImmutableArray streamReadMethods,
+ ImmutableArray streamReadAsyncMethods)
+ {
+ _streamReadMethods = streamReadMethods;
+ _streamReadAsyncMethods = streamReadAsyncMethods;
+ }
+
+ public static bool TryGetSymbols(Compilation compilation, [NotNullWhen(true)] out RequiredSymbols? symbols)
+ {
+ symbols = default;
+
+ var streamType = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemIOStream);
+
+ if (streamType is null)
+ {
+ return false;
+ }
+
+ var streamReadMethods = streamType.GetMembers(Read)
+ .OfType()
+ .ToImmutableArray();
+ var streamReadAsyncMethods = streamType.GetMembers(ReadAsync)
+ .OfType()
+ .ToImmutableArray();
+
+ if (streamReadMethods.IsEmpty && streamReadAsyncMethods.IsEmpty)
+ {
+ return false;
+ }
+
+ symbols = new RequiredSymbols(streamReadMethods, streamReadAsyncMethods);
+
+ return true;
+ }
+
+ public bool IsAnyStreamReadMethod(IMethodSymbol method)
+ {
+ return _streamReadMethods.Any(m =>
+ SymbolEqualityComparer.Default.Equals(method, m) || IsOverrideOf(method, m));
+ }
+
+ public bool IsAnyStreamReadAsyncMethod(IMethodSymbol method)
+ {
+ return _streamReadAsyncMethods.Any(m =>
+ SymbolEqualityComparer.Default.Equals(method, m) || IsOverrideOf(method, m));
+ }
+
+ private static bool IsOverrideOf(IMethodSymbol method, IMethodSymbol baseMethod)
+ {
+ var overriddenMethod = method.OverriddenMethod;
+ while (overriddenMethod is not null)
+ {
+ if (SymbolEqualityComparer.Default.Equals(overriddenMethod, baseMethod))
+ {
+ return true;
+ }
+
+ overriddenMethod = overriddenMethod.OverriddenMethod;
+ }
+
+ return false;
+ }
+
+ private readonly ImmutableArray _streamReadMethods;
+ private readonly ImmutableArray _streamReadAsyncMethods;
+ }
+ }
+}
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 753404b3b3..44f949cb90 100644
--- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf
+++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf
@@ -97,6 +97,26 @@
Nepoužívejte parametry StringBuilder pro volání nespravovaného kódu
+
+ Use 'Stream.ReadExactly'
+ Use 'Stream.ReadExactly'
+
+
+
+ A call to 'Stream.Read' may return fewer bytes than requested, resulting in unreliable code if the return value is not checked.
+ A call to 'Stream.Read' may return fewer bytes than requested, resulting in unreliable code if the return value is not checked.
+
+
+
+ Avoid inexact read with '{0}'
+ Avoid inexact read with '{0}'
+
+
+
+ Avoid inexact read with 'Stream.Read'
+ Avoid inexact read with 'Stream.Read'
+
+
The .NET Framework class library provides methods for retrieving custom attributes. By default, these methods search the attribute inheritance hierarchy. Sealing the attribute eliminates the search through the inheritance hierarchy and can improve performance.
Knihovna tříd .NET Framework poskytuje metody pro načítání vlastních atributů. Ve výchozím nastavení tyto metody prohledávají hierarchii dědičnosti atributů. Zapečetění atributu eliminuje prohledávání hierarchie dědičnosti a může zvýšit výkon.
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 458e33fad5..ebab2065ed 100644
--- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf
+++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf
@@ -97,6 +97,26 @@
StringBuilder-Parameter für "P/Invokes" vermeiden
+
+ Use 'Stream.ReadExactly'
+ Use 'Stream.ReadExactly'
+
+
+
+ A call to 'Stream.Read' may return fewer bytes than requested, resulting in unreliable code if the return value is not checked.
+ A call to 'Stream.Read' may return fewer bytes than requested, resulting in unreliable code if the return value is not checked.
+
+
+
+ Avoid inexact read with '{0}'
+ Avoid inexact read with '{0}'
+
+
+
+ Avoid inexact read with 'Stream.Read'
+ Avoid inexact read with 'Stream.Read'
+
+
The .NET Framework class library provides methods for retrieving custom attributes. By default, these methods search the attribute inheritance hierarchy. Sealing the attribute eliminates the search through the inheritance hierarchy and can improve performance.
Die .NET Framework-Klassenbibliothek stellt Methoden zum Abrufen benutzerdefinierter Attribute bereit. Standardmäßig durchsuchen diese Methoden die Attributvererbungshierarchie. Durch das Versiegeln des Attributs entfällt das Durchsuchen der Vererbungshierarchie, und die Leistung kann gesteigert werden.
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 f945606df4..0264c64bcc 100644
--- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf
+++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf
@@ -97,6 +97,26 @@
Evitar los parámetros "StringBuilder" para los elementos P/Invoke
+
+ Use 'Stream.ReadExactly'
+ Use 'Stream.ReadExactly'
+
+
+
+ A call to 'Stream.Read' may return fewer bytes than requested, resulting in unreliable code if the return value is not checked.
+ A call to 'Stream.Read' may return fewer bytes than requested, resulting in unreliable code if the return value is not checked.
+
+
+
+ Avoid inexact read with '{0}'
+ Avoid inexact read with '{0}'
+
+
+
+ Avoid inexact read with 'Stream.Read'
+ Avoid inexact read with 'Stream.Read'
+
+
The .NET Framework class library provides methods for retrieving custom attributes. By default, these methods search the attribute inheritance hierarchy. Sealing the attribute eliminates the search through the inheritance hierarchy and can improve performance.
La biblioteca de clases de .NET Framework proporciona los métodos para recuperar los atributos personalizados. De forma predeterminada, estos métodos buscan la jerarquía de herencia del atributo. Al sellar el atributo, se elimina la búsqueda a través de la jerarquía de herencia y puede mejorarse el rendimiento.
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 3f329aeeae..4126f910d3 100644
--- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf
+++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf
@@ -97,6 +97,26 @@
Éviter les paramètres 'StringBuilder' pour les P/Invoke
+
+ Use 'Stream.ReadExactly'
+ Use 'Stream.ReadExactly'
+
+
+
+ A call to 'Stream.Read' may return fewer bytes than requested, resulting in unreliable code if the return value is not checked.
+ A call to 'Stream.Read' may return fewer bytes than requested, resulting in unreliable code if the return value is not checked.
+
+
+
+ Avoid inexact read with '{0}'
+ Avoid inexact read with '{0}'
+
+
+
+ Avoid inexact read with 'Stream.Read'
+ Avoid inexact read with 'Stream.Read'
+
+
The .NET Framework class library provides methods for retrieving custom attributes. By default, these methods search the attribute inheritance hierarchy. Sealing the attribute eliminates the search through the inheritance hierarchy and can improve performance.
La bibliothèque de classes .NET Framework fournit des méthodes pour récupérer les attributs personnalisés. Par défaut, ces méthodes effectuent des recherches dans la hiérarchie d'héritage des attributs. Le scellement de l'attribut permet d'éviter les recherches dans la hiérarchie d'héritage et contribue à l'amélioration des performances.
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 a104a1acf8..a5b66beea3 100644
--- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf
+++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf
@@ -97,6 +97,26 @@
Evitare i parametri 'StringBuilder' per i metodi P/Invoke
+
+ Use 'Stream.ReadExactly'
+ Use 'Stream.ReadExactly'
+
+
+
+ A call to 'Stream.Read' may return fewer bytes than requested, resulting in unreliable code if the return value is not checked.
+ A call to 'Stream.Read' may return fewer bytes than requested, resulting in unreliable code if the return value is not checked.
+
+
+
+ Avoid inexact read with '{0}'
+ Avoid inexact read with '{0}'
+
+
+
+ Avoid inexact read with 'Stream.Read'
+ Avoid inexact read with 'Stream.Read'
+
+
The .NET Framework class library provides methods for retrieving custom attributes. By default, these methods search the attribute inheritance hierarchy. Sealing the attribute eliminates the search through the inheritance hierarchy and can improve performance.
La libreria di classi .NET Framework fornisce i metodi per recuperare gli attributi personalizzati. Per impostazione predefinita, questi metodi eseguono ricerche nella gerarchia di ereditarietà degli attributi. L'uso di attributi sealed consente di evitare ricerche nella gerarchia di ereditarietà e può migliorare le prestazioni.
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 4500cc4b82..a1a41acc03 100644
--- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf
+++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf
@@ -97,6 +97,26 @@
P/Invoke の 'StringBuilder' パラメーターを使用しないでください
+
+ Use 'Stream.ReadExactly'
+ Use 'Stream.ReadExactly'
+
+
+
+ A call to 'Stream.Read' may return fewer bytes than requested, resulting in unreliable code if the return value is not checked.
+ A call to 'Stream.Read' may return fewer bytes than requested, resulting in unreliable code if the return value is not checked.
+
+
+
+ Avoid inexact read with '{0}'
+ Avoid inexact read with '{0}'
+
+
+
+ Avoid inexact read with 'Stream.Read'
+ Avoid inexact read with 'Stream.Read'
+
+
The .NET Framework class library provides methods for retrieving custom attributes. By default, these methods search the attribute inheritance hierarchy. Sealing the attribute eliminates the search through the inheritance hierarchy and can improve performance.
.NET Framework クラス ライブラリには、カスタム属性を取得するためのメソッドが用意されています。既定では、これらのメソッドは属性継承階層を検索します。属性をシールすると、継承階層全体を検索しなくなるため、パフォーマンスが向上します。
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 9c8a9e78bf..ed8990b649 100644
--- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf
+++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf
@@ -97,6 +97,26 @@
P/Invokes에는 'StringBuilder' 매개 변수를 사용하지 마세요.
+
+ Use 'Stream.ReadExactly'
+ Use 'Stream.ReadExactly'
+
+
+
+ A call to 'Stream.Read' may return fewer bytes than requested, resulting in unreliable code if the return value is not checked.
+ A call to 'Stream.Read' may return fewer bytes than requested, resulting in unreliable code if the return value is not checked.
+
+
+
+ Avoid inexact read with '{0}'
+ Avoid inexact read with '{0}'
+
+
+
+ Avoid inexact read with 'Stream.Read'
+ Avoid inexact read with 'Stream.Read'
+
+
The .NET Framework class library provides methods for retrieving custom attributes. By default, these methods search the attribute inheritance hierarchy. Sealing the attribute eliminates the search through the inheritance hierarchy and can improve performance.
.NET Framework 클래스 라이브러리에서 사용자 지정 특성을 검색하기 위한 메서드를 제공합니다. 기본적으로 이 메서드는 특성 상속 계층 구조를 검색합니다. 특성을 봉인하면 상속 계층 구조를 통한 검색을 중단하여 성능을 향상시킬 수 있습니다.
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 1a00c35eba..5790be6fd8 100644
--- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf
+++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf
@@ -97,6 +97,26 @@
Unikaj parametrów „StringBuilder” dla elementów P/Invoke
+
+ Use 'Stream.ReadExactly'
+ Use 'Stream.ReadExactly'
+
+
+
+ A call to 'Stream.Read' may return fewer bytes than requested, resulting in unreliable code if the return value is not checked.
+ A call to 'Stream.Read' may return fewer bytes than requested, resulting in unreliable code if the return value is not checked.
+
+
+
+ Avoid inexact read with '{0}'
+ Avoid inexact read with '{0}'
+
+
+
+ Avoid inexact read with 'Stream.Read'
+ Avoid inexact read with 'Stream.Read'
+
+
The .NET Framework class library provides methods for retrieving custom attributes. By default, these methods search the attribute inheritance hierarchy. Sealing the attribute eliminates the search through the inheritance hierarchy and can improve performance.
Biblioteka klas programu .NET Framework udostępnia metody umożliwiające pobieranie atrybutów niestandardowych. Domyślnie te metody przeszukują hierarchię dziedziczenia atrybutów. Zapieczętowanie atrybutu eliminuje potrzebę przeszukiwania hierarchii dziedziczenia i może podwyższyć wydajność.
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 f68472183c..e70d7b077f 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
@@ -97,6 +97,26 @@
Evite os parâmetros 'StringBuilder' para P/Invokes
+
+ Use 'Stream.ReadExactly'
+ Use 'Stream.ReadExactly'
+
+
+
+ A call to 'Stream.Read' may return fewer bytes than requested, resulting in unreliable code if the return value is not checked.
+ A call to 'Stream.Read' may return fewer bytes than requested, resulting in unreliable code if the return value is not checked.
+
+
+
+ Avoid inexact read with '{0}'
+ Avoid inexact read with '{0}'
+
+
+
+ Avoid inexact read with 'Stream.Read'
+ Avoid inexact read with 'Stream.Read'
+
+
The .NET Framework class library provides methods for retrieving custom attributes. By default, these methods search the attribute inheritance hierarchy. Sealing the attribute eliminates the search through the inheritance hierarchy and can improve performance.
A biblioteca de classes .NET Framework oferece métodos para recuperar atributos personalizados. Por padrão, esses métodos pesquisam a hierarquia de herança de atributos. Selar o atributo elimina a pesquisa por meio da hierarquia de herança e pode melhorar o desempenho.
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 161b4fb122..35b8cd82c3 100644
--- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf
+++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf
@@ -97,6 +97,26 @@
Избегайте использования параметров "StringBuilder" для P/Invokes
+
+ Use 'Stream.ReadExactly'
+ Use 'Stream.ReadExactly'
+
+
+
+ A call to 'Stream.Read' may return fewer bytes than requested, resulting in unreliable code if the return value is not checked.
+ A call to 'Stream.Read' may return fewer bytes than requested, resulting in unreliable code if the return value is not checked.
+
+
+
+ Avoid inexact read with '{0}'
+ Avoid inexact read with '{0}'
+
+
+
+ Avoid inexact read with 'Stream.Read'
+ Avoid inexact read with 'Stream.Read'
+
+
The .NET Framework class library provides methods for retrieving custom attributes. By default, these methods search the attribute inheritance hierarchy. Sealing the attribute eliminates the search through the inheritance hierarchy and can improve performance.
Эта библиотека классов платформы .NET Framework предоставляет методы для извлечения настраиваемых атрибутов. По умолчанию эти методы выполняют поиск по иерархии наследования атрибутов. Запечатывание атрибута устраняет поиск по иерархии наследования и позволяет повысить производительность.
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 8bf1ba3899..267b583b79 100644
--- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf
+++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf
@@ -97,6 +97,26 @@
P/Invoke'lar için 'StringBuilder' parametreleri kullanmaktan kaçının
+
+ Use 'Stream.ReadExactly'
+ Use 'Stream.ReadExactly'
+
+
+
+ A call to 'Stream.Read' may return fewer bytes than requested, resulting in unreliable code if the return value is not checked.
+ A call to 'Stream.Read' may return fewer bytes than requested, resulting in unreliable code if the return value is not checked.
+
+
+
+ Avoid inexact read with '{0}'
+ Avoid inexact read with '{0}'
+
+
+
+ Avoid inexact read with 'Stream.Read'
+ Avoid inexact read with 'Stream.Read'
+
+
The .NET Framework class library provides methods for retrieving custom attributes. By default, these methods search the attribute inheritance hierarchy. Sealing the attribute eliminates the search through the inheritance hierarchy and can improve performance.
.NET Framework sınıf kitaplığı, özel öznitelikleri almaya yönelik yöntemler sağlar. Varsayılan olarak bu yöntemler öznitelik devralma hiyerarşisinde arama yapar. Özniteliğin mühürlenmesi, devralma hiyerarşisinde arama yapılmasını engeller ve performansı artırabilir.
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 0dc3ce0b86..991044ea7f 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
@@ -97,6 +97,26 @@
不要对 P/Invoke 使用 "StringBuilder" 参数
+
+ Use 'Stream.ReadExactly'
+ Use 'Stream.ReadExactly'
+
+
+
+ A call to 'Stream.Read' may return fewer bytes than requested, resulting in unreliable code if the return value is not checked.
+ A call to 'Stream.Read' may return fewer bytes than requested, resulting in unreliable code if the return value is not checked.
+
+
+
+ Avoid inexact read with '{0}'
+ Avoid inexact read with '{0}'
+
+
+
+ Avoid inexact read with 'Stream.Read'
+ Avoid inexact read with 'Stream.Read'
+
+
The .NET Framework class library provides methods for retrieving custom attributes. By default, these methods search the attribute inheritance hierarchy. Sealing the attribute eliminates the search through the inheritance hierarchy and can improve performance.
.NET Framework 类库提供用于检索自定义特性的方法。默认情况下,这些方法搜索特性继承层次结构。通过密封特性,将无需搜索继承层次结构并可提高性能。
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 1f96176960..c1b45b434d 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
@@ -97,6 +97,26 @@
請勿對 P/Invoke 使用 'StringBuilder' 參數
+
+ Use 'Stream.ReadExactly'
+ Use 'Stream.ReadExactly'
+
+
+
+ A call to 'Stream.Read' may return fewer bytes than requested, resulting in unreliable code if the return value is not checked.
+ A call to 'Stream.Read' may return fewer bytes than requested, resulting in unreliable code if the return value is not checked.
+
+
+
+ Avoid inexact read with '{0}'
+ Avoid inexact read with '{0}'
+
+
+
+ Avoid inexact read with 'Stream.Read'
+ Avoid inexact read with 'Stream.Read'
+
+
The .NET Framework class library provides methods for retrieving custom attributes. By default, these methods search the attribute inheritance hierarchy. Sealing the attribute eliminates the search through the inheritance hierarchy and can improve performance.
.NET Framework 類別庫可提供擷取自訂屬性的方法。根據預設,這些方法會搜尋屬性繼承階層。使用密封屬性可免於搜尋整個繼承階層,因而能提升效能。
diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md
index 29f229828f..b3a6bc05d4 100644
--- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md
+++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md
@@ -2058,6 +2058,18 @@ Widening and user defined conversions are not supported with generic types.
|CodeFix|False|
---
+## [CA2022](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2022): Avoid inexact read with 'Stream.Read'
+
+A call to 'Stream.Read' may return fewer bytes than requested, resulting in unreliable code if the return value is not checked.
+
+|Item|Value|
+|-|-|
+|Category|Reliability|
+|Enabled|True|
+|Severity|Warning|
+|CodeFix|True|
+---
+
## [CA2100](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2100): Review SQL queries for security vulnerabilities
SQL queries that directly use user input can be vulnerable to SQL injection attacks. Review this SQL query for potential vulnerabilities, and consider using a parameterized SQL query.
diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif
index 6da7d6bf86..176cbcbd86 100644
--- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif
+++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif
@@ -3691,6 +3691,26 @@
]
}
},
+ "CA2022": {
+ "id": "CA2022",
+ "shortDescription": "Avoid inexact read with 'Stream.Read'",
+ "fullDescription": "A call to 'Stream.Read' may return fewer bytes than requested, resulting in unreliable code if the return value is not checked.",
+ "defaultLevel": "warning",
+ "helpUri": "https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2022",
+ "properties": {
+ "category": "Reliability",
+ "isEnabledByDefault": true,
+ "typeName": "AvoidUnreliableStreamReadAnalyzer",
+ "languages": [
+ "C#",
+ "Visual Basic"
+ ],
+ "tags": [
+ "Telemetry",
+ "EnabledRuleInAggressiveMode"
+ ]
+ }
+ },
"CA2100": {
"id": "CA2100",
"shortDescription": "Review SQL queries for security vulnerabilities",
diff --git a/src/NetAnalyzers/RulesMissingDocumentation.md b/src/NetAnalyzers/RulesMissingDocumentation.md
index d20851e050..f0385ea23d 100644
--- a/src/NetAnalyzers/RulesMissingDocumentation.md
+++ b/src/NetAnalyzers/RulesMissingDocumentation.md
@@ -3,6 +3,6 @@
Rule ID | Missing Help Link | Title |
--------|-------------------|-------|
CA1871 | | Do not pass a nullable struct to 'ArgumentNullException.ThrowIfNull' |
-CA1872 | | Prefer 'Convert.ToHexString' and 'Convert.ToHexStringLower' over call chains based on 'BitConverter.ToString' |
+CA2022 | | Avoid inexact read with 'Stream.Read' |
CA2264 | | Do not pass a non-nullable value to 'ArgumentNullException.ThrowIfNull' |
CA2265 | | Do not compare Span\ to 'null' or 'default' |
diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/AvoidUnreliableStreamReadTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/AvoidUnreliableStreamReadTests.cs
new file mode 100644
index 0000000000..d2f520351d
--- /dev/null
+++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/AvoidUnreliableStreamReadTests.cs
@@ -0,0 +1,819 @@
+// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.
+
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.Testing;
+using Test.Utilities;
+using Xunit;
+
+using VerifyCS = Test.Utilities.CSharpCodeFixVerifier<
+ Microsoft.NetCore.Analyzers.Runtime.AvoidUnreliableStreamReadAnalyzer,
+ Microsoft.NetCore.Analyzers.Runtime.AvoidUnreliableStreamReadFixer>;
+using VerifyVB = Test.Utilities.VisualBasicCodeFixVerifier<
+ Microsoft.NetCore.Analyzers.Runtime.AvoidUnreliableStreamReadAnalyzer,
+ Microsoft.NetCore.Analyzers.Runtime.AvoidUnreliableStreamReadFixer>;
+
+namespace Microsoft.NetCore.Analyzers.Runtime.UnitTests
+{
+ public class AvoidUnreliableStreamReadTests
+ {
+ [Fact]
+ public async Task EntireBuffer_OffersFixer_CS()
+ {
+ string source = """
+ using System.IO;
+ using System.Threading;
+
+ class C
+ {
+ async void M(Stream s, byte[] buffer, CancellationToken ct)
+ {
+ [|s.Read(buffer, 0, buffer.Length)|];
+ [|s.Read(buffer)|];
+ await [|s.ReadAsync(buffer, 0, buffer.Length)|];
+ await [|s.ReadAsync(buffer)|];
+ await [|s.ReadAsync(buffer, 0, buffer.Length, ct)|];
+ await [|s.ReadAsync(buffer, ct)|];
+ }
+ }
+ """;
+
+ string fixedSource = """
+ using System.IO;
+ using System.Threading;
+
+ class C
+ {
+ async void M(Stream s, byte[] buffer, CancellationToken ct)
+ {
+ s.ReadExactly(buffer);
+ s.ReadExactly(buffer);
+ await s.ReadExactlyAsync(buffer);
+ await s.ReadExactlyAsync(buffer);
+ await s.ReadExactlyAsync(buffer, ct);
+ await s.ReadExactlyAsync(buffer, ct);
+ }
+ }
+ """;
+
+ await VerifyCSharpCodeFixAsync(source, fixedSource);
+ }
+
+ [Fact]
+ public async Task WithCount_OffersFixer_CS()
+ {
+ string source = """
+ using System.IO;
+ using System.Threading;
+
+ class C
+ {
+ async void M(Stream s, byte[] buffer, int count, CancellationToken ct)
+ {
+ [|s.Read(buffer, 0, count)|];
+ await [|s.ReadAsync(buffer, 0, count)|];
+ await [|s.ReadAsync(buffer, 0, count, ct)|];
+ }
+ }
+ """;
+
+ string fixedSource = """
+ using System.IO;
+ using System.Threading;
+
+ class C
+ {
+ async void M(Stream s, byte[] buffer, int count, CancellationToken ct)
+ {
+ s.ReadExactly(buffer, 0, count);
+ await s.ReadExactlyAsync(buffer, 0, count);
+ await s.ReadExactlyAsync(buffer, 0, count, ct);
+ }
+ }
+ """;
+
+ await VerifyCSharpCodeFixAsync(source, fixedSource);
+ }
+
+ [Fact]
+ public async Task WithOffsetAndCount_OffersFixer_CS()
+ {
+ string source = """
+ using System.IO;
+ using System.Threading;
+
+ class C
+ {
+ async void M(Stream s, byte[] buffer, int offset, int count, CancellationToken ct)
+ {
+ [|s.Read(buffer, offset, count)|];
+ await [|s.ReadAsync(buffer, offset, count)|];
+ await [|s.ReadAsync(buffer, offset, count, ct)|];
+ }
+ }
+ """;
+
+ string fixedSource = """
+ using System.IO;
+ using System.Threading;
+
+ class C
+ {
+ async void M(Stream s, byte[] buffer, int offset, int count, CancellationToken ct)
+ {
+ s.ReadExactly(buffer, offset, count);
+ await s.ReadExactlyAsync(buffer, offset, count);
+ await s.ReadExactlyAsync(buffer, offset, count, ct);
+ }
+ }
+ """;
+
+ await VerifyCSharpCodeFixAsync(source, fixedSource);
+ }
+
+ [Fact]
+ public async Task DifferentBufferInstances_OffersFixer_CS()
+ {
+ string source = """
+ using System.IO;
+
+ class C
+ {
+ private readonly byte[] _bufferField;
+ public byte[] BufferProperty { get; set; }
+
+ async void M(Stream s, byte[] bufferParameter)
+ {
+ byte[] bufferLocal = { };
+
+ [|s.Read(bufferParameter, 0, bufferParameter.Length)|];
+ [|s.Read(bufferLocal, 0, bufferLocal.Length)|];
+ [|s.Read(_bufferField, 0, _bufferField.Length)|];
+ [|s.Read(BufferProperty, 0, BufferProperty.Length)|];
+
+ await [|s.ReadAsync(bufferParameter, 0, bufferParameter.Length)|];
+ await [|s.ReadAsync(bufferLocal, 0, bufferLocal.Length)|];
+ await [|s.ReadAsync(_bufferField, 0, _bufferField.Length)|];
+ await [|s.ReadAsync(BufferProperty, 0, BufferProperty.Length)|];
+ }
+ }
+ """;
+
+ string fixedSource = """
+ using System.IO;
+
+ class C
+ {
+ private readonly byte[] _bufferField;
+ public byte[] BufferProperty { get; set; }
+
+ async void M(Stream s, byte[] bufferParameter)
+ {
+ byte[] bufferLocal = { };
+
+ s.ReadExactly(bufferParameter);
+ s.ReadExactly(bufferLocal);
+ s.ReadExactly(_bufferField);
+ s.ReadExactly(BufferProperty);
+
+ await s.ReadExactlyAsync(bufferParameter);
+ await s.ReadExactlyAsync(bufferLocal);
+ await s.ReadExactlyAsync(_bufferField);
+ await s.ReadExactlyAsync(BufferProperty);
+ }
+ }
+ """;
+
+ await VerifyCSharpCodeFixAsync(source, fixedSource);
+ }
+
+ [Fact]
+ public async Task SpecificStreamType_OffersFixer_CS()
+ {
+ string source = """
+ using System.IO;
+
+ class C
+ {
+ async void M(MemoryStream s, byte[] buffer)
+ {
+ [|s.Read(buffer, 0, buffer.Length)|];
+ await [|s.ReadAsync(buffer, 0, buffer.Length)|];
+ }
+ }
+ """;
+
+ string fixedSource = """
+ using System.IO;
+
+ class C
+ {
+ async void M(MemoryStream s, byte[] buffer)
+ {
+ s.ReadExactly(buffer);
+ await s.ReadExactlyAsync(buffer);
+ }
+ }
+ """;
+
+ await VerifyCSharpCodeFixAsync(source, fixedSource);
+ }
+
+ [Fact]
+ public async Task NamedArguments_OffersFixer_CS()
+ {
+ string source = """
+ using System.IO;
+ using System.Threading;
+
+ class C
+ {
+ async void M(Stream s, byte[] buffer, int offset, int count, CancellationToken ct)
+ {
+ [|s.Read(buffer: buffer, offset: 0, count: buffer.Length)|];
+ [|s.Read(buffer: buffer)|];
+ [|s.Read(buffer: buffer, offset: offset, count: count)|];
+ await [|s.ReadAsync(buffer: buffer, offset: 0, count: buffer.Length)|];
+ await [|s.ReadAsync(buffer: buffer)|];
+ await [|s.ReadAsync(buffer: buffer, offset: offset, count: count)|];
+ await [|s.ReadAsync(buffer: buffer, offset: 0, count: buffer.Length, cancellationToken: ct)|];
+ await [|s.ReadAsync(buffer: buffer, cancellationToken: ct)|];
+ await [|s.ReadAsync(buffer: buffer, offset: offset, count: count, cancellationToken: ct)|];
+ }
+ }
+ """;
+
+ string fixedSource = """
+ using System.IO;
+ using System.Threading;
+
+ class C
+ {
+ async void M(Stream s, byte[] buffer, int offset, int count, CancellationToken ct)
+ {
+ s.ReadExactly(buffer: buffer);
+ s.ReadExactly(buffer: buffer);
+ s.ReadExactly(buffer: buffer, offset: offset, count: count);
+ await s.ReadExactlyAsync(buffer: buffer);
+ await s.ReadExactlyAsync(buffer: buffer);
+ await s.ReadExactlyAsync(buffer: buffer, offset: offset, count: count);
+ await s.ReadExactlyAsync(buffer: buffer, cancellationToken: ct);
+ await s.ReadExactlyAsync(buffer: buffer, cancellationToken: ct);
+ await s.ReadExactlyAsync(buffer: buffer, offset: offset, count: count, cancellationToken: ct);
+ }
+ }
+ """;
+
+ await VerifyCSharpCodeFixAsync(source, fixedSource);
+ }
+
+ [Fact]
+ public async Task NamedArgumentsSwapped_OffersFixer_CS()
+ {
+ string source = """
+ using System.IO;
+ using System.Threading;
+
+ class C
+ {
+ async void M(Stream s, byte[] buffer, int offset, int count, CancellationToken ct)
+ {
+ [|s.Read(count: buffer.Length, buffer: buffer, offset: 0)|];
+ [|s.Read(count: count, offset: offset, buffer: buffer)|];
+ await [|s.ReadAsync(offset: 0, buffer: buffer, count: buffer.Length)|];
+ await [|s.ReadAsync(buffer: buffer, count: count, offset: offset)|];
+ await [|s.ReadAsync(count: buffer.Length, buffer: buffer, offset: 0, cancellationToken: ct)|];
+ await [|s.ReadAsync(cancellationToken: ct, buffer: buffer)|];
+ await [|s.ReadAsync(count: count, cancellationToken: ct, buffer: buffer, offset: offset)|];
+ }
+ }
+ """;
+
+ string fixedSource = """
+ using System.IO;
+ using System.Threading;
+
+ class C
+ {
+ async void M(Stream s, byte[] buffer, int offset, int count, CancellationToken ct)
+ {
+ s.ReadExactly(buffer: buffer);
+ s.ReadExactly(count: count, offset: offset, buffer: buffer);
+ await s.ReadExactlyAsync(buffer: buffer);
+ await s.ReadExactlyAsync(buffer: buffer, count: count, offset: offset);
+ await s.ReadExactlyAsync(buffer: buffer, cancellationToken: ct);
+ await s.ReadExactlyAsync(cancellationToken: ct, buffer: buffer);
+ await s.ReadExactlyAsync(count: count, cancellationToken: ct, buffer: buffer, offset: offset);
+ }
+ }
+ """;
+
+ await VerifyCSharpCodeFixAsync(source, fixedSource);
+ }
+
+ [Fact]
+ public async Task TriviaIsPreserved_OffersFixer_CS()
+ {
+ string source = """
+ using System.IO;
+ using System.Threading;
+
+ class C
+ {
+ async void M(Stream s, byte[] buffer, int offset, int count, CancellationToken ct)
+ {
+ // reticulates the splines
+ [|s.Read(buffer, 0, buffer.Length)|];
+ // reticulates the splines
+ await [|s.ReadAsync(buffer, 0, buffer.Length, ct)|];
+ // reticulates the splines
+ [|s.Read(buffer, offset, count)|];
+ // reticulates the splines
+ await [|s.ReadAsync(buffer, offset, count, ct)|];
+ }
+ }
+ """;
+
+ string fixedSource = """
+ using System.IO;
+ using System.Threading;
+
+ class C
+ {
+ async void M(Stream s, byte[] buffer, int offset, int count, CancellationToken ct)
+ {
+ // reticulates the splines
+ s.ReadExactly(buffer);
+ // reticulates the splines
+ await s.ReadExactlyAsync(buffer, ct);
+ // reticulates the splines
+ s.ReadExactly(buffer, offset, count);
+ // reticulates the splines
+ await s.ReadExactlyAsync(buffer, offset, count, ct);
+ }
+ }
+ """;
+
+ await VerifyCSharpCodeFixAsync(source, fixedSource);
+ }
+
+ [Fact]
+ public async Task ReadExactlyNotAvailable_ReportsDiagnostic_CS()
+ {
+ string source = """
+ using System.IO;
+
+ class C
+ {
+ async void M(Stream s, byte[] buffer)
+ {
+ [|s.Read(buffer)|];
+ await [|s.ReadAsync(buffer)|];
+ }
+ }
+ """;
+
+ // No code fix because ReadExactly is not available until .NET 7.
+ await VerifyCSharpCodeFixAsync(source, source, ReferenceAssemblies.Net.Net60);
+ }
+
+ [Fact]
+ public async Task ReturnValueIsUsed_NoDiagnostic_CS()
+ {
+ string source = """
+ using System.IO;
+ using System.Threading;
+
+ class C
+ {
+ async void M(Stream s, byte[] buffer, int offset, int count, CancellationToken ct)
+ {
+ _ = s.Read(buffer);
+ _ = s.Read(buffer, 0, buffer.Length);
+ _ = s.Read(buffer, offset, count);
+ _ = await s.ReadAsync(buffer, 0, buffer.Length);
+ _ = await s.ReadAsync(buffer);
+ _ = await s.ReadAsync(buffer, offset, count);
+ _ = await s.ReadAsync(buffer, 0, buffer.Length, ct);
+ _ = await s.ReadAsync(buffer, ct);
+ _ = await s.ReadAsync(buffer, offset, count, ct);
+ }
+ }
+ """;
+
+ await VerifyCSharpCodeFixAsync(source, source);
+ }
+
+ [Fact]
+ public async Task DifferentRead_NoDiagnostic_CS()
+ {
+ string source = """
+ using System.IO;
+
+ class C
+ {
+ async void M(Stream s, byte[] buffer, int count)
+ {
+ s.ReadByte();
+ s.ReadAtLeast(buffer, count);
+ await s.ReadAtLeastAsync(buffer, count);
+ }
+ }
+ """;
+
+ await VerifyCSharpCodeFixAsync(source, source);
+ }
+
+ //
+
+ [Fact]
+ public async Task EntireBuffer_OffersFixer_VB()
+ {
+ string source = """
+ Imports System.IO
+ Imports System.Threading
+
+ Class C
+ Async Sub M(s As Stream, buffer As Byte(), ct As CancellationToken)
+ [|s.Read(buffer, 0, buffer.Length)|]
+ [|s.Read(buffer)|]
+ Await [|s.ReadAsync(buffer, 0, buffer.Length)|]
+ Await [|s.ReadAsync(buffer)|]
+ Await [|s.ReadAsync(buffer, 0, buffer.Length, ct)|]
+ Await [|s.ReadAsync(buffer, ct)|]
+ End Sub
+ End Class
+ """;
+
+ string fixedSource = """
+ Imports System.IO
+ Imports System.Threading
+
+ Class C
+ Async Sub M(s As Stream, buffer As Byte(), ct As CancellationToken)
+ s.ReadExactly(buffer)
+ s.ReadExactly(buffer)
+ Await [|s.ReadExactlyAsync(buffer)|]
+ Await [|s.ReadExactlyAsync(buffer)|]
+ Await [|s.ReadExactlyAsync(buffer, ct)|]
+ Await [|s.ReadExactlyAsync(buffer, ct)|]
+ End Sub
+ End Class
+ """;
+
+ await VerifyBasicCodeFixAsync(source, fixedSource);
+ }
+
+ [Fact]
+ public async Task WithCount_OffersFixer_VB()
+ {
+ string source = """
+ Imports System.IO
+ Imports System.Threading
+
+ Class C
+ Async Sub M(s As Stream, buffer As Byte(), count As Integer, ct As CancellationToken)
+ [|s.Read(buffer, 0, count)|]
+ Await [|s.ReadAsync(buffer, 0, count)|]
+ Await [|s.ReadAsync(buffer, 0, count, ct)|]
+ End Sub
+ End Class
+ """;
+
+ string fixedSource = """
+ Imports System.IO
+ Imports System.Threading
+
+ Class C
+ Async Sub M(s As Stream, buffer As Byte(), count As Integer, ct As CancellationToken)
+ s.ReadExactly(buffer, 0, count)
+ Await s.ReadExactlyAsync(buffer, 0, count)
+ Await s.ReadExactlyAsync(buffer, 0, count, ct)
+ End Sub
+ End Class
+ """;
+
+ await VerifyBasicCodeFixAsync(source, fixedSource);
+ }
+
+ [Fact]
+ public async Task WithOffsetAndCount_OffersFixer_VB()
+ {
+ string source = """
+ Imports System.IO
+ Imports System.Threading
+
+ Class C
+ Async Sub M(s As Stream, buffer As Byte(), count As Integer, offset As Integer, ct As CancellationToken)
+ [|s.Read(buffer, offset, count)|]
+ Await [|s.ReadAsync(buffer, offset, count)|]
+ Await [|s.ReadAsync(buffer, offset, count, ct)|]
+ End Sub
+ End Class
+ """;
+
+ string fixedSource = """
+ Imports System.IO
+ Imports System.Threading
+
+ Class C
+ Async Sub M(s As Stream, buffer As Byte(), count As Integer, offset As Integer, ct As CancellationToken)
+ s.ReadExactly(buffer, offset, count)
+ Await s.ReadExactlyAsync(buffer, offset, count)
+ Await s.ReadExactlyAsync(buffer, offset, count, ct)
+ End Sub
+ End Class
+ """;
+
+ await VerifyBasicCodeFixAsync(source, fixedSource);
+ }
+
+ [Fact]
+ public async Task DifferentBufferInstances_OffersFixer_VB()
+ {
+ string source = """
+ Imports System.IO
+
+ Class C
+ Private ReadOnly _bufferField as Byte()
+ Public Property BufferProperty as Byte()
+
+ Async Sub M(s As Stream, bufferParameter As Byte())
+ Dim bufferLocal as Byte() = { }
+
+ [|s.Read(bufferParameter, 0, bufferParameter.Length)|]
+ [|s.Read(bufferLocal, 0, bufferLocal.Length)|]
+ [|s.Read(_bufferField, 0, _bufferField.Length)|]
+ [|s.Read(BufferProperty, 0, BufferProperty.Length)|]
+
+ Await [|s.ReadAsync(bufferParameter, 0, bufferParameter.Length)|]
+ Await [|s.ReadAsync(bufferLocal, 0, bufferLocal.Length)|]
+ Await [|s.ReadAsync(_bufferField, 0, _bufferField.Length)|]
+ Await [|s.ReadAsync(BufferProperty, 0, BufferProperty.Length)|]
+ End Sub
+ End Class
+ """;
+
+ string fixedSource = """
+ Imports System.IO
+
+ Class C
+ Private ReadOnly _bufferField as Byte()
+ Public Property BufferProperty as Byte()
+
+ Async Sub M(s As Stream, bufferParameter As Byte())
+ Dim bufferLocal as Byte() = { }
+
+ s.ReadExactly(bufferParameter)
+ s.ReadExactly(bufferLocal)
+ s.ReadExactly(_bufferField)
+ s.ReadExactly(BufferProperty)
+
+ Await s.ReadExactlyAsync(bufferParameter)
+ Await s.ReadExactlyAsync(bufferLocal)
+ Await s.ReadExactlyAsync(_bufferField)
+ Await s.ReadExactlyAsync(BufferProperty)
+ End Sub
+ End Class
+ """;
+
+ await VerifyBasicCodeFixAsync(source, fixedSource);
+ }
+
+ [Fact]
+ public async Task SpecificStreamType_OffersFixer_VB()
+ {
+ string source = """
+ Imports System.IO
+
+ Class C
+ Async Sub M(s As MemoryStream, buffer As Byte())
+ [|s.Read(buffer, 0, buffer.Length)|]
+ Await [|s.ReadAsync(buffer, 0, buffer.Length)|]
+ End Sub
+ End Class
+ """;
+
+ string fixedSource = """
+ Imports System.IO
+
+ Class C
+ Async Sub M(s As MemoryStream, buffer As Byte())
+ s.ReadExactly(buffer)
+ Await s.ReadExactlyAsync(buffer)
+ End Sub
+ End Class
+ """;
+
+ await VerifyBasicCodeFixAsync(source, fixedSource);
+ }
+
+ [Fact]
+ public async Task NamedArguments_OffersFixer_VB()
+ {
+ string source = """
+ Imports System.IO
+ Imports System.Threading
+
+ Class C
+ Async Sub M(s As Stream, buffer As Byte(), count As Integer, offset As Integer, ct As CancellationToken)
+ [|s.Read(buffer:=buffer, offset:=0, count:=buffer.Length)|]
+ [|s.Read(buffer:=buffer)|]
+ [|s.Read(buffer:=buffer, offset:=offset, count:=count)|]
+ Await [|s.ReadAsync(buffer:=buffer, offset:=0, count:=buffer.Length)|]
+ Await [|s.ReadAsync(buffer:=buffer)|]
+ Await [|s.ReadAsync(buffer:=buffer, offset:=offset, count:=count)|]
+ Await [|s.ReadAsync(buffer:=buffer, offset:=0, count:=buffer.Length, cancellationToken:=ct)|]
+ Await [|s.ReadAsync(buffer:=buffer, cancellationToken:=ct)|]
+ Await [|s.ReadAsync(buffer:=buffer, offset:=offset, count:=count, cancellationToken:=ct)|]
+ End Sub
+ End Class
+ """;
+
+ string fixedSource = """
+ Imports System.IO
+ Imports System.Threading
+
+ Class C
+ Async Sub M(s As Stream, buffer As Byte(), count As Integer, offset As Integer, ct As CancellationToken)
+ s.ReadExactly(buffer:=buffer)
+ s.ReadExactly(buffer:=buffer)
+ s.ReadExactly(buffer:=buffer, offset:=offset, count:=count)
+ Await s.ReadExactlyAsync(buffer:=buffer)
+ Await s.ReadExactlyAsync(buffer:=buffer)
+ Await s.ReadExactlyAsync(buffer:=buffer, offset:=offset, count:=count)
+ Await s.ReadExactlyAsync(buffer:=buffer, cancellationToken:=ct)
+ Await s.ReadExactlyAsync(buffer:=buffer, cancellationToken:=ct)
+ Await s.ReadExactlyAsync(buffer:=buffer, offset:=offset, count:=count, cancellationToken:=ct)
+ End Sub
+ End Class
+ """;
+
+ await VerifyBasicCodeFixAsync(source, fixedSource);
+ }
+
+ // Order of arguments in IOperation is based on evaluation order, which seems to be parameter order for VB.
+ [Fact, WorkItem(3655, "https://github.com/dotnet/roslyn-analyzers/issues/3655")]
+ public async Task NamedArgumentsSwapped_OffersFixer_VB()
+ {
+ string source = """
+ Imports System.IO
+ Imports System.Threading
+
+ Class C
+ Async Sub M(s As Stream, buffer As Byte(), count As Integer, offset As Integer, ct As CancellationToken)
+ [|s.Read(count:=buffer.Length, buffer:=buffer, offset:=0)|]
+ [|s.Read(count:=count, offset:=offset, buffer:=buffer)|]
+ Await [|s.ReadAsync(offset:=0, buffer:=buffer, count:=buffer.Length)|]
+ Await [|s.ReadAsync(buffer:=buffer, count:=count, offset:=offset)|]
+ Await [|s.ReadAsync(count:=buffer.Length, buffer:=buffer, offset:=0, cancellationToken:=ct)|]
+ Await [|s.ReadAsync(cancellationToken:=ct, buffer:=buffer)|]
+ Await [|s.ReadAsync(count:=count, cancellationToken:=ct, buffer:=buffer, offset:=offset)|]
+ End Sub
+ End Class
+ """;
+
+ string fixedSource = """
+ Imports System.IO
+ Imports System.Threading
+
+ Class C
+ Async Sub M(s As Stream, buffer As Byte(), count As Integer, offset As Integer, ct As CancellationToken)
+ s.ReadExactly(buffer:=buffer)
+ s.ReadExactly(buffer:=buffer, offset:=offset, count:=count)
+ Await s.ReadExactlyAsync(buffer:=buffer)
+ Await s.ReadExactlyAsync(buffer:=buffer, offset:=offset, count:=count)
+ Await s.ReadExactlyAsync(buffer:=buffer, cancellationToken:=ct)
+ Await s.ReadExactlyAsync(buffer:=buffer, cancellationToken:=ct)
+ Await s.ReadExactlyAsync(buffer:=buffer, offset:=offset, count:=count, cancellationToken:=ct)
+ End Sub
+ End Class
+ """;
+
+ await VerifyBasicCodeFixAsync(source, fixedSource);
+ }
+
+ [Fact]
+ public async Task TriviaIsPreserved_OffersFixer_VB()
+ {
+ string source = """
+ Imports System.IO
+ Imports System.Threading
+
+ Class C
+ Async Sub M(s As Stream, buffer As Byte(), count As Integer, offset As Integer, ct As CancellationToken)
+ ' reticulates the splines
+ [|s.Read(buffer, 0, buffer.Length)|]
+ ' reticulates the splines
+ Await [|s.ReadAsync(buffer, 0, buffer.Length, ct)|]
+ ' reticulates the splines
+ [|s.Read(buffer, offset, count)|]
+ ' reticulates the splines
+ Await [|s.ReadAsync(buffer, offset, count, ct)|]
+ End Sub
+ End Class
+ """;
+
+ string fixedSource = """
+ Imports System.IO
+ Imports System.Threading
+
+ Class C
+ Async Sub M(s As Stream, buffer As Byte(), count As Integer, offset As Integer, ct As CancellationToken)
+ ' reticulates the splines
+ s.ReadExactly(buffer)
+ ' reticulates the splines
+ Await s.ReadExactlyAsync(buffer, ct)
+ ' reticulates the splines
+ s.ReadExactly(buffer, offset, count)
+ ' reticulates the splines
+ Await s.ReadExactlyAsync(buffer, offset, count, ct)
+ End Sub
+ End Class
+ """;
+
+ await VerifyBasicCodeFixAsync(source, fixedSource);
+ }
+
+ [Fact]
+ public async Task ReadExactlyNotAvailable_ReportsDiagnostic_VB()
+ {
+ string source = """
+ Imports System.IO
+
+ Class C
+ Async Sub M(s As Stream, buffer As Byte())
+ [|s.Read(buffer)|]
+ Await [|s.ReadAsync(buffer)|]
+ End Sub
+ End Class
+ """;
+
+ // No code fix because ReadExactly is not available until .NET 7.
+ await VerifyBasicCodeFixAsync(source, source, ReferenceAssemblies.Net.Net60);
+ }
+
+ [Fact]
+ public async Task ReturnValueIsUsed_NoDiagnostic_VB()
+ {
+ string source = """
+ Imports System.IO
+ Imports System.Threading
+
+ Class C
+ Async Sub M(s As Stream, buffer As Byte(), count As Integer, offset As Integer, ct As CancellationToken)
+ Dim bytesRead As Integer
+ bytesRead = s.Read(buffer)
+ bytesRead = s.Read(buffer, 0, buffer.Length)
+ bytesRead = s.Read(buffer, offset, count)
+ bytesRead = Await s.ReadAsync(buffer, 0, buffer.Length)
+ bytesRead = Await s.ReadAsync(buffer)
+ bytesRead = Await s.ReadAsync(buffer, offset, count)
+ bytesRead = Await s.ReadAsync(buffer, 0, buffer.Length, ct)
+ bytesRead = Await s.ReadAsync(buffer, ct)
+ bytesRead = Await s.ReadAsync(buffer, offset, count, ct)
+ End Sub
+ End Class
+ """;
+
+ await VerifyBasicCodeFixAsync(source, source);
+ }
+
+ [Fact]
+ public async Task DifferentRead_NoDiagnostic_VB()
+ {
+ string source = """
+ Imports System.IO
+
+ Class C
+ Async Sub M(s As Stream, buffer As Byte(), count As Integer)
+ s.ReadByte()
+ s.ReadAtLeast(buffer, count)
+ Await s.ReadAtLeastAsync(buffer, count)
+ End Sub
+ End Class
+ """;
+
+ await VerifyBasicCodeFixAsync(source, source);
+ }
+
+ private static async Task VerifyCSharpCodeFixAsync(string source, string fixedSource, ReferenceAssemblies referenceAssemblies = null)
+ {
+ await new VerifyCS.Test
+ {
+ TestCode = source,
+ FixedCode = fixedSource,
+ ReferenceAssemblies = referenceAssemblies ?? ReferenceAssemblies.Net.Net70
+ }.RunAsync();
+ }
+
+ private static async Task VerifyBasicCodeFixAsync(string source, string fixedSource, ReferenceAssemblies referenceAssemblies = null)
+ {
+ await new VerifyVB.Test
+ {
+ TestCode = source,
+ FixedCode = fixedSource,
+ ReferenceAssemblies = referenceAssemblies ?? ReferenceAssemblies.Net.Net70
+ }.RunAsync();
+ }
+ }
+}
diff --git a/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt b/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt
index 57eface7b5..0b07345960 100644
--- a/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt
+++ b/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt
@@ -18,7 +18,7 @@ Usage: CA1801, CA1806, CA1816, CA2200-CA2209, CA2211-CA2265
Naming: CA1700-CA1727
Interoperability: CA1400-CA1422
Maintainability: CA1500-CA1515
-Reliability: CA9998-CA9999, CA2000-CA2021
+Reliability: CA9998-CA9999, CA2000-CA2022
Documentation: CA1200-CA1200
# Microsoft CodeAnalysis API rules