-
Notifications
You must be signed in to change notification settings - Fork 470
/
Copy pathSerializationRulesDiagnosticAnalyzer.cs
200 lines (173 loc) · 9.69 KB
/
SerializationRulesDiagnosticAnalyzer.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
// 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.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),
};
}
}
}
}