Skip to content

Commit

Permalink
New Analyzer: Implement Generic Math Interfaces Correctly (#6126)
Browse files Browse the repository at this point in the history
  • Loading branch information
buyaa-n authored Sep 14, 2022
1 parent a26d1a2 commit f03b023
Show file tree
Hide file tree
Showing 23 changed files with 1,205 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.NetCore.Analyzers.Usage;

namespace Microsoft.NetCore.CSharp.Analyzers.Usage
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class CSharpImplementGenericMathInterfacesCorrectly : ImplementGenericMathInterfacesCorrectly
{
protected override SyntaxNode? FindTheTypeArgumentOfTheInterfaceFromTypeDeclaration(ISymbol typeSymbol, ISymbol anInterfaceSymbol)
{
foreach (SyntaxReference syntaxReference in typeSymbol.DeclaringSyntaxReferences)
{
SyntaxNode typeDefinition = syntaxReference.GetSyntax();
if (typeDefinition is BaseTypeDeclarationSyntax baseType &&
FindTypeArgumentFromBaseInterfaceList(baseType.BaseList.Types, anInterfaceSymbol) is { } node)
{
return node;
}
}

return null;
}

private static SyntaxNode? FindTypeArgumentFromBaseInterfaceList(SeparatedSyntaxList<BaseTypeSyntax> baseListTypes, ISymbol anInterfaceSymbol)
{
foreach (BaseTypeSyntax baseType in baseListTypes)
{
if (baseType is SimpleBaseTypeSyntax simpleBaseType &&
simpleBaseType.Type is GenericNameSyntax genericName &&
genericName.Identifier.ValueText == anInterfaceSymbol.Name)
{
return genericName.TypeArgumentList.Arguments[0];
}
}

return null;
}
}
}
1 change: 1 addition & 0 deletions src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ CA1854 | Performance | Info | PreferDictionaryTryGetValueAnalyzer, [Documentatio
CA1855 | Performance | Info | UseSpanClearInsteadOfFillAnalyzer, [Documentation](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1855)
CA2019 | Reliability | Info | UseThreadStaticCorrectly, [Documentation](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2019)
CA2259 | Usage | Warning | UseThreadStaticCorrectly, [Documentation](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2259)
CA2260 | Usage | Warning | ImplementGenericMathInterfacesCorrectly, [Documentation](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2260)
CA5404 | Security | Disabled | DoNotDisableTokenValidationChecks, [Documentation](https://docs.microsoft.com/visualstudio/code-quality/ca5404)
CA5405 | Security | Disabled | DoNotAlwaysSkipTokenValidationInDelegates, [Documentation](https://docs.microsoft.com/visualstudio/code-quality/ca5405)
Original file line number Diff line number Diff line change
Expand Up @@ -1959,6 +1959,15 @@
<data name="ParenthesisWithPlaceHolder" xml:space="preserve">
<value> ({0})</value>
</data>
<data name="ImplementGenericMathInterfacesCorrectlyDescription" xml:space="preserve">
<value>Generic math interfaces require the derived type itself to be used for the self recurring type parameter.</value>
</data>
<data name="ImplementGenericMathInterfacesCorrectlyMessage" xml:space="preserve">
<value>The '{0}' requires the '{1}' type parameter to be filled with the derived type '{2}'</value>
</data>
<data name="ImplementGenericMathInterfacesCorrectlyTitle" xml:space="preserve">
<value>Use correct type parameter</value>
</data>
<data name="UseSpanClearInsteadOfFillCodeFixTitle" xml:space="preserve">
<value>Use 'Clear()'</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
// 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 Analyzer.Utilities;
using Analyzer.Utilities.Extensions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;

namespace Microsoft.NetCore.Analyzers.Usage
{
using static MicrosoftNetCoreAnalyzersResources;

/// <summary>
/// CA2260: Some generic math interfaces require the derived type itself to be used for the self recurring type parameter, enforces that requirement
/// </summary>
public abstract class ImplementGenericMathInterfacesCorrectly : DiagnosticAnalyzer
{
private const string RuleId = "CA2260";

internal static readonly DiagnosticDescriptor GMIRule = DiagnosticDescriptorHelper.Create(
RuleId,
CreateLocalizableResourceString(nameof(ImplementGenericMathInterfacesCorrectlyTitle)),
CreateLocalizableResourceString(nameof(ImplementGenericMathInterfacesCorrectlyMessage)),
DiagnosticCategory.Usage,
RuleLevel.BuildWarning,
description: CreateLocalizableResourceString(nameof(ImplementGenericMathInterfacesCorrectlyDescription)),
isPortedFxCopRule: false,
isDataflowRule: false);

private static readonly ImmutableHashSet<string> s_knownInterfaces = ImmutableHashSet.Create("IParsable`1", "ISpanParsable`1", "IAdditionOperators`3", "IAdditiveIdentity`2",
"IBinaryFloatingPointIeee754`1", "IBinaryInteger`1", "IBinaryNumber`1", "IBitwiseOperators`3", "IComparisonOperators`3", "IDecrementOperators`1", "IDivisionOperators`3",
"IEqualityOperators`3", "IExponentialFunctions`1", "IFloatingPointIeee754`1", "IFloatingPoint`1", "IHyperbolicFunctions`1", "IIncrementOperators`1", "ILogarithmicFunctions`1",
"IMinMaxValue`1", "IModulusOperators`3", "IMultiplicativeIdentity`2", "IMultiplyOperators`3", "INumberBase`1", "INumber`1", "IPowerFunctions`1", "IRootFunctions`1", "IShiftOperators`3",
"ISignedNumber`1", "ISubtractionOperators`3", "ITrigonometricFunctions`1", "IUnaryNegationOperators`2", "IUnaryPlusOperators`2", "IUnsignedNumber`1");

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(GMIRule);

public override void Initialize(AnalysisContext context)
{
context.EnableConcurrentExecution();
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);

context.RegisterCompilationStartAction(context =>
{
if (!context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemIParsable1, out var iParsableInterface) ||
!context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemNumericsINumber1, out var iNumberInterface))
{
return;
}
context.RegisterSymbolAction(context =>
{
if (context.Symbol is INamedTypeSymbol ntSymbol)
{
AnalyzeSymbol(context, ntSymbol, iParsableInterface.ContainingNamespace, iNumberInterface.ContainingNamespace);
}
}, SymbolKind.NamedType);
});
}

private void AnalyzeSymbol(SymbolAnalysisContext context, INamedTypeSymbol symbol, INamespaceSymbol systemNS, INamespaceSymbol systemNumericsNS)
{
foreach (INamedTypeSymbol anInterface in symbol.Interfaces)
{
if (IsKnownInterfaceInTheChain(anInterface) &&
FirstTypeParameterNameIsNotTheSymbolName(symbol, anInterface) &&
(!symbol.IsGenericType || NotConstrainedToTheInterface(anInterface, symbol.TypeParameters)))
{
SyntaxNode? typeParameter = FindTheTypeArgumentOfTheInterfaceFromTypeDeclaration(symbol, anInterface);
context.ReportDiagnostic(CreateDiagnostic(GMIRule, typeParameter, anInterface.OriginalDefinition.ToDisplayString(
SymbolDisplayFormat.MinimallyQualifiedFormat), anInterface.OriginalDefinition.TypeParameters[0].Name, symbol));
}
}

INamedTypeSymbol? baseType = symbol.BaseType;
while (baseType != null && baseType.IsGenericType)
{
foreach (INamedTypeSymbol anInterface in baseType.Interfaces)
{
if (IsKnownInterfaceInTheChain(anInterface) &&
FirstTypeParameterNameIsNotTheSymbolName(symbol, anInterface) &&
(!symbol.IsGenericType || NotConstrainedToTheInterface(anInterface, symbol.TypeParameters)))
{
SyntaxNode? typeParameter = FindTheTypeArgumentOfTheInterfaceFromTypeDeclaration(symbol, symbol.BaseType);
context.ReportDiagnostic(CreateDiagnostic(GMIRule, typeParameter, symbol.BaseType.OriginalDefinition.ToDisplayString(
SymbolDisplayFormat.MinimallyQualifiedFormat), symbol.BaseType.OriginalDefinition.TypeParameters[0].Name, symbol));
}
}

baseType = baseType.BaseType;
}

bool IsKnownInterfaceInTheChain(INamedTypeSymbol anInterface)
{
if (anInterface.IsGenericType)
{
if (IsKnownInterface(anInterface, systemNS, systemNumericsNS))
{
return true;
}
else
{
foreach (INamedTypeSymbol parentInterface in anInterface.Interfaces)
{
if (IsKnownInterfaceInTheChain(parentInterface))
{
return true;
}
}
}
}

return false;
}

static bool NotConstrainedToTheInterface(INamedTypeSymbol anInterface, ImmutableArray<ITypeParameterSymbol> typeParameters)
{
foreach (var typeParameter in typeParameters)
{
foreach (var constraint in typeParameter.ConstraintTypes)
{
if (constraint.Equals(anInterface, SymbolEqualityComparer.Default))
{
return false;
}
}
}

return true;
}
}

private static Diagnostic CreateDiagnostic(DiagnosticDescriptor GMIRule, SyntaxNode? parameter,
string genericDeclaration, string parameterName, INamedTypeSymbol symbol) =>
parameter is null ? symbol.CreateDiagnostic(GMIRule, genericDeclaration, parameterName, symbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)) :
parameter.CreateDiagnostic(GMIRule, genericDeclaration, parameterName, symbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat));

protected abstract SyntaxNode? FindTheTypeArgumentOfTheInterfaceFromTypeDeclaration(ISymbol typeSymbol, ISymbol theInterfaceSymbol);

private static bool IsKnownInterface(INamedTypeSymbol anInterface, INamespaceSymbol systemNS, INamespaceSymbol systemNumericsNS)
{
var iNamespace = anInterface.ContainingNamespace;

return s_knownInterfaces.Contains(anInterface.MetadataName) &&
(iNamespace.Equals(systemNS, SymbolEqualityComparer.Default) ||
iNamespace.Equals(systemNumericsNS, SymbolEqualityComparer.Default));
}

private static bool FirstTypeParameterNameIsNotTheSymbolName(INamedTypeSymbol symbol, INamedTypeSymbol anInterface) =>
anInterface.TypeArguments[0].Name != symbol.Name;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1242,6 +1242,21 @@
<target state="translated">Nepoužívat pevně zakódované hodnoty SslProtocols</target>
<note />
</trans-unit>
<trans-unit id="ImplementGenericMathInterfacesCorrectlyDescription">
<source>Generic math interfaces require the derived type itself to be used for the self recurring type parameter.</source>
<target state="new">Generic math interfaces require the derived type itself to be used for the self recurring type parameter.</target>
<note />
</trans-unit>
<trans-unit id="ImplementGenericMathInterfacesCorrectlyMessage">
<source>The '{0}' requires the '{1}' type parameter to be filled with the derived type '{2}'</source>
<target state="new">The '{0}' requires the '{1}' type parameter to be filled with the derived type '{2}'</target>
<note />
</trans-unit>
<trans-unit id="ImplementGenericMathInterfacesCorrectlyTitle">
<source>Use correct type parameter</source>
<target state="new">Use correct type parameter</target>
<note />
</trans-unit>
<trans-unit id="ImplementISerializableCorrectlyDescription">
<source>To fix a violation of this rule, make the GetObjectData method visible and overridable, and make sure that all instance fields are included in the serialization process or explicitly marked by using the NonSerializedAttribute attribute.</source>
<target state="translated">Pokud chcete porušení tohoto pravidla opravit, nastavte metodu GetObjectData jako viditelnou a přepsatelnou a ujistěte se, že všechna pole instance jsou součástí procesu serializace nebo explicitně označená atributem NonSerializedAttribute.</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1242,6 +1242,21 @@
<target state="translated">Hartcodierte SslProtocols-Werte vermeiden</target>
<note />
</trans-unit>
<trans-unit id="ImplementGenericMathInterfacesCorrectlyDescription">
<source>Generic math interfaces require the derived type itself to be used for the self recurring type parameter.</source>
<target state="new">Generic math interfaces require the derived type itself to be used for the self recurring type parameter.</target>
<note />
</trans-unit>
<trans-unit id="ImplementGenericMathInterfacesCorrectlyMessage">
<source>The '{0}' requires the '{1}' type parameter to be filled with the derived type '{2}'</source>
<target state="new">The '{0}' requires the '{1}' type parameter to be filled with the derived type '{2}'</target>
<note />
</trans-unit>
<trans-unit id="ImplementGenericMathInterfacesCorrectlyTitle">
<source>Use correct type parameter</source>
<target state="new">Use correct type parameter</target>
<note />
</trans-unit>
<trans-unit id="ImplementISerializableCorrectlyDescription">
<source>To fix a violation of this rule, make the GetObjectData method visible and overridable, and make sure that all instance fields are included in the serialization process or explicitly marked by using the NonSerializedAttribute attribute.</source>
<target state="translated">Um einen Verstoß gegen diese Regel zu beheben, legen Sie die GetObjectData-Methode als sichtbar und überschreibbar fest, und stellen Sie sicher, dass alle Instanzfelder in den Serialisierungsvorgang eingeschlossen oder durch das NonSerializedAttribute-Attribut explizit markiert werden.</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1242,6 +1242,21 @@
<target state="translated">Evitar valores de SslProtocols codificados de forma rígida</target>
<note />
</trans-unit>
<trans-unit id="ImplementGenericMathInterfacesCorrectlyDescription">
<source>Generic math interfaces require the derived type itself to be used for the self recurring type parameter.</source>
<target state="new">Generic math interfaces require the derived type itself to be used for the self recurring type parameter.</target>
<note />
</trans-unit>
<trans-unit id="ImplementGenericMathInterfacesCorrectlyMessage">
<source>The '{0}' requires the '{1}' type parameter to be filled with the derived type '{2}'</source>
<target state="new">The '{0}' requires the '{1}' type parameter to be filled with the derived type '{2}'</target>
<note />
</trans-unit>
<trans-unit id="ImplementGenericMathInterfacesCorrectlyTitle">
<source>Use correct type parameter</source>
<target state="new">Use correct type parameter</target>
<note />
</trans-unit>
<trans-unit id="ImplementISerializableCorrectlyDescription">
<source>To fix a violation of this rule, make the GetObjectData method visible and overridable, and make sure that all instance fields are included in the serialization process or explicitly marked by using the NonSerializedAttribute attribute.</source>
<target state="translated">Para corregir una infracción de esta regla, haga que el método GetObjectData sea visible y se pueda reemplazar; además, asegúrese de que todos los campos de la instancia se incluyen en el proceso de serialización o se marcan explícitamente con el atributo NonSerializedAttribute.</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1242,6 +1242,21 @@
<target state="translated">Évitez de coder en dur les valeurs de SslProtocols</target>
<note />
</trans-unit>
<trans-unit id="ImplementGenericMathInterfacesCorrectlyDescription">
<source>Generic math interfaces require the derived type itself to be used for the self recurring type parameter.</source>
<target state="new">Generic math interfaces require the derived type itself to be used for the self recurring type parameter.</target>
<note />
</trans-unit>
<trans-unit id="ImplementGenericMathInterfacesCorrectlyMessage">
<source>The '{0}' requires the '{1}' type parameter to be filled with the derived type '{2}'</source>
<target state="new">The '{0}' requires the '{1}' type parameter to be filled with the derived type '{2}'</target>
<note />
</trans-unit>
<trans-unit id="ImplementGenericMathInterfacesCorrectlyTitle">
<source>Use correct type parameter</source>
<target state="new">Use correct type parameter</target>
<note />
</trans-unit>
<trans-unit id="ImplementISerializableCorrectlyDescription">
<source>To fix a violation of this rule, make the GetObjectData method visible and overridable, and make sure that all instance fields are included in the serialization process or explicitly marked by using the NonSerializedAttribute attribute.</source>
<target state="translated">Pour corriger toute violation de cette règle, rendez la méthode GetObjectData visible et substituable. De plus, vérifiez que tous les champs d'instance sont inclus dans le processus de sérialisation ou qu'ils sont marqués explicitement avec l'attribut NonSerializedAttribute.</target>
Expand Down
Loading

0 comments on commit f03b023

Please sign in to comment.