| File: Microsoft.NetCore.Analyzers\Runtime\SerializationRulesDiagnosticAnalyzer.cs | Web Access |
| Project: src\sdk\src\Microsoft.CodeAnalysis.NetAnalyzers\src\Microsoft.CodeAnalysis.NetAnalyzers\Microsoft.CodeAnalysis.NetAnalyzers.csproj (Microsoft.CodeAnalysis.NetAnalyzers) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Immutable; using System.Linq; using Analyzer.Utilities; using Analyzer.Utilities.Extensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; namespace Microsoft.NetCore.Analyzers.Runtime { using static MicrosoftNetCoreAnalyzersResources; /// <summary> /// CA2237: <inheritdoc cref="MarkISerializableTypesWithSerializableTitle"/> /// CA2235: <inheritdoc cref="MarkAllNonSerializableFieldsTitle"/> /// </summary> [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public sealed class SerializationRulesDiagnosticAnalyzer : DiagnosticAnalyzer { // Mark ISerializable types with SerializableAttribute internal const string RuleCA2237Id = "CA2237"; internal static readonly DiagnosticDescriptor RuleCA2237 = DiagnosticDescriptorHelper.Create( RuleCA2237Id, CreateLocalizableResourceString(nameof(MarkISerializableTypesWithSerializableTitle)), CreateLocalizableResourceString(nameof(MarkISerializableTypesWithSerializableMessage)), DiagnosticCategory.Usage, RuleLevel.CandidateForRemoval, // Cannot implement this for .NET Core: https://github.com/dotnet/roslyn-analyzers/issues/1775#issuecomment-518457308 description: CreateLocalizableResourceString(nameof(MarkISerializableTypesWithSerializableDescription)), isPortedFxCopRule: true, isDataflowRule: false); // Mark all non-serializable fields internal const string RuleCA2235Id = "CA2235"; internal static readonly DiagnosticDescriptor RuleCA2235 = DiagnosticDescriptorHelper.Create( RuleCA2235Id, CreateLocalizableResourceString(nameof(MarkAllNonSerializableFieldsTitle)), CreateLocalizableResourceString(nameof(MarkAllNonSerializableFieldsMessage)), DiagnosticCategory.Usage, RuleLevel.CandidateForRemoval, // Cannot implement this for .NET Core: https://github.com/dotnet/roslyn-analyzers/issues/1775#issuecomment-518457308 description: CreateLocalizableResourceString(nameof(MarkAllNonSerializableFieldsDescription)), isPortedFxCopRule: true, isDataflowRule: false); public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(RuleCA2235, RuleCA2237); public override void Initialize(AnalysisContext context) { context.EnableConcurrentExecution(); context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); context.RegisterCompilationStartAction( (context) => { INamedTypeSymbol? iserializableTypeSymbol = context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemRuntimeSerializationISerializable); if (iserializableTypeSymbol == null) { return; } INamedTypeSymbol? serializableAttributeTypeSymbol = context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemSerializableAttribute); if (serializableAttributeTypeSymbol == null) { return; } INamedTypeSymbol? nonSerializedAttributeTypeSymbol = context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemNonSerializedAttribute); if (nonSerializedAttributeTypeSymbol == null) { return; } var isNetStandardAssembly = !context.Compilation.TargetsDotNetFramework(); var symbolAnalyzer = new SymbolAnalyzer(iserializableTypeSymbol, serializableAttributeTypeSymbol, nonSerializedAttributeTypeSymbol, isNetStandardAssembly); context.RegisterSymbolAction(symbolAnalyzer.AnalyzeSymbol, SymbolKind.NamedType); }); } private sealed class SymbolAnalyzer { private readonly INamedTypeSymbol _iserializableTypeSymbol; private readonly INamedTypeSymbol _serializableAttributeTypeSymbol; private readonly INamedTypeSymbol _nonSerializedAttributeTypeSymbol; private readonly bool _isNetStandardAssembly; public SymbolAnalyzer( INamedTypeSymbol iserializableTypeSymbol, INamedTypeSymbol serializableAttributeTypeSymbol, INamedTypeSymbol nonSerializedAttributeTypeSymbol, bool isNetStandardAssembly) { _iserializableTypeSymbol = iserializableTypeSymbol; _serializableAttributeTypeSymbol = serializableAttributeTypeSymbol; _nonSerializedAttributeTypeSymbol = nonSerializedAttributeTypeSymbol; _isNetStandardAssembly = isNetStandardAssembly; } public void AnalyzeSymbol(SymbolAnalysisContext context) { var namedTypeSymbol = (INamedTypeSymbol)context.Symbol; if (namedTypeSymbol.TypeKind is TypeKind.Delegate or TypeKind.Interface) { return; } var implementsISerializable = namedTypeSymbol.Interfaces.Contains(_iserializableTypeSymbol); var isSerializable = IsSerializable(namedTypeSymbol); // If the type is public and implements ISerializable if (namedTypeSymbol.DeclaredAccessibility == Accessibility.Public && implementsISerializable) { if (!isSerializable) { // CA2237 : Mark serializable types with the SerializableAttribute if (namedTypeSymbol.BaseType is { } baseType && (baseType.SpecialType == SpecialType.System_Object || IsSerializable(baseType))) { context.ReportDiagnostic(namedTypeSymbol.CreateDiagnostic(RuleCA2237, namedTypeSymbol.Name)); } } } // If this is type is marked Serializable and doesn't implement ISerializable, check its fields' types as well if (isSerializable && !implementsISerializable) { foreach (ISymbol member in namedTypeSymbol.GetMembers()) { // Only process field members if (member is not IFieldSymbol field) { continue; } // Only process instance fields if (field.IsStatic) { continue; } // Only process non-serializable fields if (IsSerializable(field.Type)) { continue; } // We bail out from reporting CA2235 in netstandard assemblies for types in metadata // due to missing support: https://github.com/dotnet/roslyn-analyzers/issues/1775#issuecomment-519686818 if (_isNetStandardAssembly && field.Type.Locations.All(l => !l.IsInSource)) { continue; } // Check for [NonSerialized] if (field.HasAnyAttribute(_nonSerializedAttributeTypeSymbol)) { continue; } // Handle compiler-generated fields (without source declaration) that have an associated symbol in code. // For example, auto-property backing fields. ISymbol targetSymbol = field.IsImplicitlyDeclared && field.AssociatedSymbol != null ? field.AssociatedSymbol : field; context.ReportDiagnostic( targetSymbol.CreateDiagnostic( RuleCA2235, targetSymbol.Name, namedTypeSymbol.Name, field.Type)); } } } private bool IsSerializable(ITypeSymbol type) { if (type.IsPrimitiveType()) { return true; } return type.TypeKind switch { TypeKind.Array => IsSerializable(((IArrayTypeSymbol)type).ElementType), TypeKind.Enum => IsSerializable(((INamedTypeSymbol)type).EnumUnderlyingType!), TypeKind.TypeParameter or TypeKind.Interface => true,// The concrete type can't be determined statically, // so we assume true to cut down on noise. TypeKind.Class or TypeKind.Struct => ((INamedTypeSymbol)type).IsSerializable,// Check SerializableAttribute or Serializable flag from metadata. TypeKind.Delegate => true,// delegates are always serializable, even if // they aren't actually marked [Serializable] _ => type.HasAnyAttribute(_serializableAttributeTypeSymbol), }; } } } }