Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge release/6.0.1xx-preview2 to release/6.0.1xx-preview3 #4902

Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
; Please do not edit this file manually, it should only be updated through code fix application.

### New Rules
Rule ID | Category | Severity | Notes
--------|----------|----------|-------
CA1418 | Interoperability | Warning | UseValidPlatformString, [Documentation](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1418)
CA1839 | Performance | Info | UseEnvironmentMembers, [Documentation](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1839)
CA1840 | Performance | Info | UseEnvironmentMembers, [Documentation](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1840)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using Analyzer.Utilities;
using Analyzer.Utilities.Extensions;
using Analyzer.Utilities.PooledObjects;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;

namespace Microsoft.NetCore.Analyzers.InteropServices
{
[DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)]
public sealed class UseValidPlatformString : DiagnosticAnalyzer
{
internal const string RuleId = "CA1418";
private static readonly ImmutableArray<SymbolKind> s_symbols = ImmutableArray.Create(SymbolKind.NamedType, SymbolKind.Method, SymbolKind.Property, SymbolKind.Field, SymbolKind.Event);
private static readonly ImmutableArray<string> methodNames = ImmutableArray.Create("IsOSPlatform", "IsOSPlatformVersionAtLeast");
private const string IsPrefix = "Is";
private const string VersionSuffix = "VersionAtLeast";

private static readonly LocalizableString s_localizableTitle = new LocalizableResourceString(nameof(MicrosoftNetCoreAnalyzersResources.UseValidPlatformStringTitle), MicrosoftNetCoreAnalyzersResources.ResourceManager, typeof(MicrosoftNetCoreAnalyzersResources));
private static readonly LocalizableString s_localizableUnknownPlatform = new LocalizableResourceString(nameof(MicrosoftNetCoreAnalyzersResources.UseValidPlatformStringUnknownPlatform), MicrosoftNetCoreAnalyzersResources.ResourceManager, typeof(MicrosoftNetCoreAnalyzersResources));
private static readonly LocalizableString s_localizableInvalidVersion = new LocalizableResourceString(nameof(MicrosoftNetCoreAnalyzersResources.UseValidPlatformStringInvalidVersion), MicrosoftNetCoreAnalyzersResources.ResourceManager, typeof(MicrosoftNetCoreAnalyzersResources));
private static readonly LocalizableString s_localizableNoVersion = new LocalizableResourceString(nameof(MicrosoftNetCoreAnalyzersResources.UseValidPlatformStringNoVersion), MicrosoftNetCoreAnalyzersResources.ResourceManager, typeof(MicrosoftNetCoreAnalyzersResources));
private static readonly LocalizableString s_localizableDescription = new LocalizableResourceString(nameof(MicrosoftNetCoreAnalyzersResources.UseValidPlatformStringDescription), MicrosoftNetCoreAnalyzersResources.ResourceManager, typeof(MicrosoftNetCoreAnalyzersResources));

internal static DiagnosticDescriptor UnknownPlatform = DiagnosticDescriptorHelper.Create(RuleId,
s_localizableTitle,
s_localizableUnknownPlatform,
DiagnosticCategory.Interoperability,
RuleLevel.BuildWarning,
description: s_localizableDescription,
isPortedFxCopRule: false,
isDataflowRule: false);

internal static DiagnosticDescriptor InvalidVersion = DiagnosticDescriptorHelper.Create(RuleId,
s_localizableTitle,
s_localizableInvalidVersion,
DiagnosticCategory.Interoperability,
RuleLevel.BuildWarning,
description: s_localizableDescription,
isPortedFxCopRule: false,
isDataflowRule: false);

internal static DiagnosticDescriptor NoVersion = DiagnosticDescriptorHelper.Create(RuleId,
s_localizableTitle,
s_localizableNoVersion,
DiagnosticCategory.Interoperability,
RuleLevel.BuildWarning,
description: s_localizableDescription,
isPortedFxCopRule: false,
isDataflowRule: false);

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(UnknownPlatform, InvalidVersion, NoVersion);

public override void Initialize(AnalysisContext context)
{
context.EnableConcurrentExecution();
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);

context.RegisterCompilationStartAction(context =>
{
if (!context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemOperatingSystem, out var operatingSystemType) ||
!context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemRuntimeVersioningSupportedOSPlatformAttribute, out var supportedAttriubte) ||
!context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemRuntimeVersioningUnsupportedOSPlatformAttribute, out var unsupportedAttribute))
{
return;
}

var knownPlatforms = PooledDictionary<string, int>.GetInstance(StringComparer.OrdinalIgnoreCase);
AddPlatformsAndVersionCountFromGuardMethods(operatingSystemType, knownPlatforms);
AddPlatformsFromMsBuildOptions(knownPlatforms, context.Options.GetMSBuildItemMetadataValues(
MSBuildItemOptionNames.SupportedPlatform, context.Compilation, context.CancellationToken));

context.RegisterOperationAction(context => AnalyzeOperation(context.Operation, context, knownPlatforms), OperationKind.Invocation);
context.RegisterSymbolAction(context => AnalyzeSymbol(context.ReportDiagnostic, context.Symbol,
supportedAttriubte, unsupportedAttribute, knownPlatforms, context.CancellationToken), s_symbols);
context.RegisterCompilationEndAction(context => AnalyzeSymbol(context.ReportDiagnostic, context.Compilation.Assembly,
supportedAttriubte, unsupportedAttribute, knownPlatforms, context.CancellationToken));
});

static void AddPlatformsAndVersionCountFromGuardMethods(INamedTypeSymbol operatingSystemType, PooledDictionary<string, int> knownPlatforms)
{
var methods = operatingSystemType.GetMembers().OfType<IMethodSymbol>();
foreach (var m in methods)
{
if (m.IsStatic &&
m.ReturnType.SpecialType == SpecialType.System_Boolean &&
NameAndParametersValid(m))
{
var (platformName, versionPartsCount) = ExtractPlatformAndVersionCount(m);
if (!knownPlatforms.TryGetValue(platformName, out var count) ||
versionPartsCount > count)
{
knownPlatforms[platformName] = versionPartsCount; // only keep highest count
}
}
}
}

static void AddPlatformsFromMsBuildOptions(PooledDictionary<string, int> knownPlatforms, ImmutableArray<string> msBuildPlatforms)
{
foreach (var platform in msBuildPlatforms)
{
if (!knownPlatforms.ContainsKey(platform))
{
knownPlatforms.Add(platform, 4); // Default version count is 4
}
}
}

static (string platformName, int versionPartsCount) ExtractPlatformAndVersionCount(IMethodSymbol method)
{
var name = method.Name;
if (name.EndsWith(VersionSuffix, StringComparison.Ordinal))
{
return (name[2..(name.Length - VersionSuffix.Length)], method.Parameters.Length);
}

return (name[2..], 0);
}

static bool NameAndParametersValid(IMethodSymbol method) =>
method.Name.StartsWith(IsPrefix, StringComparison.Ordinal) &&
(method.Parameters.Length == 0 || method.Name.EndsWith(VersionSuffix, StringComparison.Ordinal));
}

private static void AnalyzeOperation(IOperation operation, OperationAnalysisContext context, PooledDictionary<string, int> knownPlatforms)
{
if (operation is IInvocationOperation invocation &&
methodNames.Contains(invocation.TargetMethod.Name) &&
invocation.Arguments.Length > 0 &&
invocation.Arguments[0].Value is { } argument &&
argument.ConstantValue.HasValue &&
argument.ConstantValue.Value is string platformName &&
IsNotKnownPlatform(knownPlatforms, platformName))
{
context.ReportDiagnostic(argument.Syntax.CreateDiagnostic(UnknownPlatform, platformName));
}
}

private static void AnalyzeSymbol(Action<Diagnostic> reportDiagnostic, ISymbol symbol, INamedTypeSymbol supportedAttrbute,
INamedTypeSymbol unsupportedAttribute, PooledDictionary<string, int> knownPlatforms, CancellationToken token)
{
foreach (var attribute in symbol.GetAttributes())
{
if (supportedAttrbute.Equals(attribute.AttributeClass.OriginalDefinition, SymbolEqualityComparer.Default) ||
unsupportedAttribute.Equals(attribute.AttributeClass.OriginalDefinition, SymbolEqualityComparer.Default))
{
AnalyzeAttribute(reportDiagnostic, attribute, knownPlatforms, token);
}
}
}

private static void AnalyzeAttribute(Action<Diagnostic> reportDiagnostic, AttributeData attributeData, PooledDictionary<string, int> knownPlatforms, CancellationToken token)
{
var constructorArguments = attributeData.ConstructorArguments;
var syntaxReference = attributeData.ApplicationSyntaxReference;

if (constructorArguments.Length == 1 && syntaxReference != null)
{
if (constructorArguments[0].Value is string value)
{
AnalyzeStringParameter(reportDiagnostic, syntaxReference.GetSyntax(token), knownPlatforms, value);
}
else
{
reportDiagnostic(syntaxReference.GetSyntax(token).CreateDiagnostic(UnknownPlatform, "null"));
}
}

static void AnalyzeStringParameter(Action<Diagnostic> reportDiagnostic, SyntaxNode syntax, PooledDictionary<string, int> knownPlatforms, string value)
{
if (TryParsePlatformNameAndVersion(value, out var platformName, out var versionPart, out var versionCount))
{
if (!knownPlatforms.TryGetValue(platformName, out var count))
{
reportDiagnostic(syntax.CreateDiagnostic(UnknownPlatform, platformName));
}
else if (count == 0 && versionCount != 0)
{
reportDiagnostic(syntax.CreateDiagnostic(NoVersion, versionPart, platformName));
}
else if (count < versionCount)
{
var maxCount = count == 2 ? string.Empty : $"-{count}";
reportDiagnostic(syntax.CreateDiagnostic(InvalidVersion, versionPart, platformName, maxCount));
}
}
else
{
// version were not parsable, check the platform name and version count
if (!knownPlatforms.TryGetValue(platformName, out var count))
{
reportDiagnostic(syntax.CreateDiagnostic(UnknownPlatform, platformName));
}
else if (count == 0 && versionPart.Length != 0)
{
reportDiagnostic(syntax.CreateDiagnostic(NoVersion, versionPart, platformName));
}
else
{
var maxCount = count == 2 ? string.Empty : $"-{count}";
reportDiagnostic(syntax.CreateDiagnostic(InvalidVersion, versionPart, platformName, maxCount));
}
}
}
}

private static bool IsNotKnownPlatform(PooledDictionary<string, int> knownPlatforms, string platformName) =>
platformName.Length == 0 || !knownPlatforms.ContainsKey(platformName);

private static bool TryParsePlatformNameAndVersion(string osString, out string osPlatformName, out string versionPart, out int versionCount)
{
versionCount = 0;
versionPart = string.Empty;

for (int i = 0; i < osString.Length; i++)
{
if (char.IsDigit(osString[i]))
{
osPlatformName = osString.Substring(0, i);
versionPart = osString[i..];
if (i > 0 && Version.TryParse(osString[i..], out Version _))
{
versionCount = osString.Count(ch => ch == '.') + 1;
return true;
}

return false;
}
}

osPlatformName = osString;
return true;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1534,4 +1534,20 @@
<value>and all other platforms</value>
<comment>This call site is reachable on: 'windows' 10.0.2000 and later, and all other platforms</comment>
</data>
<data name="UseValidPlatformStringDescription" xml:space="preserve">
<value>Platform compatibility analyzer requires a valid platform name and version.</value>
</data>
<data name="UseValidPlatformStringTitle" xml:space="preserve">
<value>Use valid platform string</value>
</data>
<data name="UseValidPlatformStringUnknownPlatform" xml:space="preserve">
<value>The platform '{0}' is not a known platform name</value>
</data>
<data name="UseValidPlatformStringInvalidVersion" xml:space="preserve">
<value>Version '{0}' is not valid for platform '{1}'. Use a version with 2{2} parts for this platform.</value>
<comment>Version '7' is not valid for platform 'windows'. Use a version with 2-4 parts for this platform.</comment>
</data>
<data name="UseValidPlatformStringNoVersion" xml:space="preserve">
<value>Version '{0}' is not valid for platform '{1}'. Do not use versions for this platform.</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -2277,6 +2277,31 @@
<target state="translated">Pokud je to možné, zvažte použití řízení přístupu Azure na základě role namísto sdíleného přístupového podpisu (SAS). Pokud i přesto potřebujete používat sdílený přístupový podpis, zadejte SharedAccessProtocol.HttpsOnly.</target>
<note />
</trans-unit>
<trans-unit id="UseValidPlatformStringDescription">
<source>Platform compatibility analyzer requires a valid platform name and version.</source>
<target state="new">Platform compatibility analyzer requires a valid platform name and version.</target>
<note />
</trans-unit>
<trans-unit id="UseValidPlatformStringInvalidVersion">
<source>Version '{0}' is not valid for platform '{1}'. Use a version with 2{2} parts for this platform.</source>
<target state="new">Version '{0}' is not valid for platform '{1}'. Use a version with 2{2} parts for this platform.</target>
<note>Version '7' is not valid for platform 'windows'. Use a version with 2-4 parts for this platform.</note>
</trans-unit>
<trans-unit id="UseValidPlatformStringNoVersion">
<source>Version '{0}' is not valid for platform '{1}'. Do not use versions for this platform.</source>
<target state="new">Version '{0}' is not valid for platform '{1}'. Do not use versions for this platform.</target>
<note />
</trans-unit>
<trans-unit id="UseValidPlatformStringTitle">
<source>Use valid platform string</source>
<target state="new">Use valid platform string</target>
<note />
</trans-unit>
<trans-unit id="UseValidPlatformStringUnknownPlatform">
<source>The platform '{0}' is not a known platform name</source>
<target state="new">The platform '{0}' is not a known platform name</target>
<note />
</trans-unit>
<trans-unit id="UseValueTasksCorrectlyDescription">
<source>ValueTasks returned from member invocations are intended to be directly awaited. Attempts to consume a ValueTask multiple times or to directly access one's result before it's known to be completed may result in an exception or corruption. Ignoring such a ValueTask is likely an indication of a functional bug and may degrade performance.</source>
<target state="translated">Hodnoty ValueTask vrácené z vyvolání členů jsou určené k tomu, aby byly přímo očekávané. Pokusy o vícenásobné využití ValueTask nebo o přímý přístup k výsledku úkolu před tím, než je známo, že je dokončený, můžou způsobit výjimku nebo poškození. Ignorování takové hodnoty ValueTask je pravděpodobně indikací funkční chyby a může snížit výkon.</target>
Expand Down
Loading