forked from microsoft/vs-threading
-
Notifications
You must be signed in to change notification settings - Fork 0
/
CommonInterest.cs
343 lines (287 loc) · 15.7 KB
/
CommonInterest.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
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.VisualStudio.Threading.Analyzers
{
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Text;
internal static class CommonInterest
{
internal static readonly Regex FileNamePatternForLegacyThreadSwitchingMembers = new Regex(@"^vs-threading\.LegacyThreadSwitchingMembers(\..*)?.txt$", FileNamePatternRegexOptions);
internal static readonly Regex FileNamePatternForMembersRequiringMainThread = new Regex(@"^vs-threading\.MembersRequiringMainThread(\..*)?.txt$", FileNamePatternRegexOptions);
internal static readonly Regex FileNamePatternForMethodsThatAssertMainThread = new Regex(@"^vs-threading\.MainThreadAssertingMethods(\..*)?.txt$", FileNamePatternRegexOptions);
internal static readonly Regex FileNamePatternForMethodsThatSwitchToMainThread = new Regex(@"^vs-threading\.MainThreadSwitchingMethods(\..*)?.txt$", FileNamePatternRegexOptions);
internal static readonly IEnumerable<SyncBlockingMethod> JTFSyncBlockers = new[]
{
new SyncBlockingMethod(new QualifiedMember(new QualifiedType(Namespaces.MicrosoftVisualStudioThreading, Types.JoinableTaskFactory.TypeName), Types.JoinableTaskFactory.Run), Types.JoinableTaskFactory.RunAsync),
new SyncBlockingMethod(new QualifiedMember(new QualifiedType(Namespaces.MicrosoftVisualStudioThreading, Types.JoinableTask.TypeName), Types.JoinableTask.Join), Types.JoinableTask.JoinAsync),
};
internal static readonly IEnumerable<SyncBlockingMethod> ProblematicSyncBlockingMethods = new[]
{
new SyncBlockingMethod(new QualifiedMember(new QualifiedType(Namespaces.SystemThreadingTasks, nameof(Task)), nameof(Task.Wait)), null),
new SyncBlockingMethod(new QualifiedMember(new QualifiedType(Namespaces.SystemThreadingTasks, nameof(Task)), nameof(Task.WaitAll)), null),
new SyncBlockingMethod(new QualifiedMember(new QualifiedType(Namespaces.SystemThreadingTasks, nameof(Task)), nameof(Task.WaitAny)), null),
new SyncBlockingMethod(new QualifiedMember(new QualifiedType(Namespaces.SystemRuntimeCompilerServices, nameof(TaskAwaiter)), nameof(TaskAwaiter.GetResult)), null),
new SyncBlockingMethod(new QualifiedMember(new QualifiedType(Namespaces.SystemRuntimeCompilerServices, nameof(ValueTaskAwaiter)), nameof(ValueTaskAwaiter.GetResult)), null),
};
internal static readonly IEnumerable<SyncBlockingMethod> SyncBlockingMethods = JTFSyncBlockers.Concat(ProblematicSyncBlockingMethods).Concat(new[]
{
new SyncBlockingMethod(new QualifiedMember(new QualifiedType(Namespaces.MicrosoftVisualStudioShellInterop, "IVsTask"), "Wait"), extensionMethodNamespace: Namespaces.MicrosoftVisualStudioShell),
new SyncBlockingMethod(new QualifiedMember(new QualifiedType(Namespaces.MicrosoftVisualStudioShellInterop, "IVsTask"), "GetResult"), extensionMethodNamespace: Namespaces.MicrosoftVisualStudioShell),
});
internal static readonly IReadOnlyList<SyncBlockingMethod> SyncBlockingProperties = new[]
{
new SyncBlockingMethod(new QualifiedMember(new QualifiedType(Namespaces.SystemThreadingTasks, nameof(Task)), nameof(Task<int>.Result)), null),
new SyncBlockingMethod(new QualifiedMember(new QualifiedType(Namespaces.SystemThreadingTasks, nameof(ValueTask)), nameof(ValueTask<int>.Result)), null),
};
internal static readonly IEnumerable<QualifiedMember> ThreadAffinityTestingMethods = new[]
{
new QualifiedMember(new QualifiedType(Namespaces.MicrosoftVisualStudioShell, Types.ThreadHelper.TypeName), Types.ThreadHelper.CheckAccess),
};
internal static readonly ImmutableArray<QualifiedMember> TaskConfigureAwait = ImmutableArray.Create(
new QualifiedMember(new QualifiedType(Types.Task.Namespace, Types.Task.TypeName), nameof(Task.ConfigureAwait)),
new QualifiedMember(new QualifiedType(Types.AwaitExtensions.Namespace, Types.AwaitExtensions.TypeName), Types.AwaitExtensions.ConfigureAwaitRunInline));
private const RegexOptions FileNamePatternRegexOptions = RegexOptions.IgnoreCase | RegexOptions.Singleline;
private static readonly TimeSpan RegexMatchTimeout = TimeSpan.FromSeconds(5); // Prevent expensive CPU hang in Regex.Match if backtracking occurs due to pathological input (see #485).
private static readonly Regex NegatableTypeOrMemberReferenceRegex = new Regex(@"^(?<negated>!)?\[(?<typeName>[^\[\]\:]+)+\](?:\:\:(?<memberName>\S+))?\s*$", RegexOptions.Singleline | RegexOptions.CultureInvariant, RegexMatchTimeout);
private static readonly Regex MemberReferenceRegex = new Regex(@"^\[(?<typeName>[^\[\]\:]+)+\]::(?<memberName>\S+)\s*$", RegexOptions.Singleline | RegexOptions.CultureInvariant, RegexMatchTimeout);
/// <summary>
/// An array with '.' as its only element.
/// </summary>
private static readonly char[] QualifiedIdentifierSeparators = new[] { '.' };
internal static IEnumerable<QualifiedMember> ReadMethods(AnalyzerOptions analyzerOptions, Regex fileNamePattern, CancellationToken cancellationToken)
{
foreach (string line in ReadAdditionalFiles(analyzerOptions, fileNamePattern, cancellationToken))
{
yield return ParseAdditionalFileMethodLine(line);
}
}
internal static IEnumerable<TypeMatchSpec> ReadTypesAndMembers(AnalyzerOptions analyzerOptions, Regex fileNamePattern, CancellationToken cancellationToken)
{
foreach (string line in ReadAdditionalFiles(analyzerOptions, fileNamePattern, cancellationToken))
{
Match? match = null;
try
{
match = NegatableTypeOrMemberReferenceRegex.Match(line);
}
catch (RegexMatchTimeoutException)
{
throw new InvalidOperationException($"Regex.Match timeout when parsing line: {line}");
}
if (match == null || !match.Success)
{
throw new InvalidOperationException($"Parsing error on line: {line}");
}
bool inverted = match.Groups["negated"].Success;
string[] typeNameElements = match.Groups["typeName"].Value.Split(QualifiedIdentifierSeparators);
string typeName = typeNameElements[typeNameElements.Length - 1];
var containingNamespace = typeNameElements.Take(typeNameElements.Length - 1).ToImmutableArray();
var type = new QualifiedType(containingNamespace, typeName);
QualifiedMember member = match.Groups["memberName"].Success ? new QualifiedMember(type, match.Groups["memberName"].Value) : default(QualifiedMember);
yield return new TypeMatchSpec(type, member, inverted);
}
}
internal static IEnumerable<string> ReadAdditionalFiles(AnalyzerOptions analyzerOptions, Regex fileNamePattern, CancellationToken cancellationToken)
{
if (analyzerOptions is null)
{
throw new ArgumentNullException(nameof(analyzerOptions));
}
if (fileNamePattern is null)
{
throw new ArgumentNullException(nameof(fileNamePattern));
}
IEnumerable<SourceText>? docs = from file in analyzerOptions.AdditionalFiles.OrderBy(x => x.Path, StringComparer.Ordinal)
let fileName = Path.GetFileName(file.Path)
where fileNamePattern.IsMatch(fileName)
let text = file.GetText(cancellationToken)
select text;
return docs.SelectMany(ReadLinesFromAdditionalFile);
}
internal static bool Contains(this ImmutableArray<QualifiedMember> methods, ISymbol symbol)
{
foreach (QualifiedMember method in methods)
{
if (method.IsMatch(symbol))
{
return true;
}
}
return false;
}
internal static bool Contains(this ImmutableArray<TypeMatchSpec> types, [NotNullWhen(true)] ITypeSymbol? typeSymbol, ISymbol? memberSymbol)
{
TypeMatchSpec matching = default(TypeMatchSpec);
foreach (TypeMatchSpec type in types)
{
if (type.IsMatch(typeSymbol, memberSymbol))
{
if (matching.IsEmpty || matching.IsWildcard)
{
matching = type;
if (!matching.IsWildcard)
{
// It's an exact match, so return it immediately.
return !matching.InvertedLogic;
}
}
}
}
return !matching.IsEmpty && !matching.InvertedLogic;
}
internal static IEnumerable<string> ReadLinesFromAdditionalFile(SourceText text)
{
if (text is null)
{
throw new ArgumentNullException(nameof(text));
}
foreach (TextLine line in text.Lines)
{
string lineText = line.ToString();
if (!string.IsNullOrWhiteSpace(lineText) && !lineText.StartsWith("#", StringComparison.OrdinalIgnoreCase))
{
yield return lineText;
}
}
}
internal static QualifiedMember ParseAdditionalFileMethodLine(string line)
{
Match? match = null;
try
{
match = MemberReferenceRegex.Match(line);
}
catch (RegexMatchTimeoutException)
{
throw new InvalidOperationException($"Regex.Match timeout when parsing line: {line}");
}
if (match == null || !match.Success)
{
throw new InvalidOperationException($"Parsing error on line: {line}");
}
string methodName = match.Groups["memberName"].Value;
string[] typeNameElements = match.Groups["typeName"].Value.Split(QualifiedIdentifierSeparators);
string typeName = typeNameElements[typeNameElements.Length - 1];
var containingType = new QualifiedType(typeNameElements.Take(typeNameElements.Length - 1).ToImmutableArray(), typeName);
return new QualifiedMember(containingType, methodName);
}
internal readonly struct TypeMatchSpec
{
internal TypeMatchSpec(QualifiedType type, QualifiedMember member, bool inverted)
{
this.InvertedLogic = inverted;
this.Type = type;
this.Member = member;
if (this.IsWildcard && this.Member.Name is object)
{
throw new ArgumentException("Wildcard use is not allowed when member of type is specified.");
}
}
/// <summary>
/// Gets a value indicating whether this entry appeared in a file with a leading "!" character.
/// </summary>
internal bool InvertedLogic { get; }
/// <summary>
/// Gets the type described by this entry.
/// </summary>
internal QualifiedType Type { get; }
/// <summary>
/// Gets the member described by this entry.
/// </summary>
internal QualifiedMember Member { get; }
/// <summary>
/// Gets a value indicating whether a member match is reuqired.
/// </summary>
internal bool IsMember => this.Member.Name is object;
/// <summary>
/// Gets a value indicating whether the typename is a wildcard.
/// </summary>
internal bool IsWildcard => this.Type.Name == "*";
/// <summary>
/// Gets a value indicating whether this is an uninitialized (default) instance.
/// </summary>
internal bool IsEmpty => this.Type.Namespace is null;
/// <summary>
/// Tests whether a given symbol matches the description of a type (independent of its <see cref="InvertedLogic"/> property).
/// </summary>
internal bool IsMatch([NotNullWhen(true)] ITypeSymbol? typeSymbol, ISymbol? memberSymbol)
{
if (typeSymbol is null)
{
return false;
}
if (!this.IsMember
&& (this.IsWildcard || typeSymbol.Name == this.Type.Name)
&& typeSymbol.BelongsToNamespace(this.Type.Namespace))
{
return true;
}
if (this.IsMember
&& memberSymbol?.Name == this.Member.Name
&& typeSymbol.Name == this.Type.Name
&& typeSymbol.BelongsToNamespace(this.Type.Namespace))
{
return true;
}
return false;
}
}
internal readonly struct QualifiedType
{
public QualifiedType(IReadOnlyList<string> containingTypeNamespace, string typeName)
{
this.Namespace = containingTypeNamespace;
this.Name = typeName;
}
public IReadOnlyList<string> Namespace { get; }
public string Name { get; }
public bool IsMatch(ISymbol symbol)
{
return symbol?.Name == this.Name
&& symbol.BelongsToNamespace(this.Namespace);
}
public override string ToString() => string.Join(".", this.Namespace.Concat(new[] { this.Name }));
}
internal readonly struct QualifiedMember
{
public QualifiedMember(QualifiedType containingType, string methodName)
{
this.ContainingType = containingType;
this.Name = methodName;
}
public QualifiedType ContainingType { get; }
public string Name { get; }
public bool IsMatch(ISymbol symbol)
{
return symbol?.Name == this.Name
&& this.ContainingType.IsMatch(symbol.ContainingType);
}
public override string ToString() => this.ContainingType.ToString() + "." + this.Name;
}
[DebuggerDisplay("{" + nameof(Method) + "} -> {" + nameof(AsyncAlternativeMethodName) + "}")]
internal readonly struct SyncBlockingMethod
{
public SyncBlockingMethod(QualifiedMember method, string? asyncAlternativeMethodName = null, IReadOnlyList<string>? extensionMethodNamespace = null)
{
this.Method = method;
this.AsyncAlternativeMethodName = asyncAlternativeMethodName;
this.ExtensionMethodNamespace = extensionMethodNamespace;
}
public QualifiedMember Method { get; }
public string? AsyncAlternativeMethodName { get; }
public IReadOnlyList<string>? ExtensionMethodNamespace { get; }
}
}
}