Merge pull request #308 from AArnott/fix255

Enhance VSTHRD003 to report any awaiting of tasks scheduled in another context
This commit is contained in:
Andrew Arnott 2018-07-11 22:42:18 -07:00 коммит произвёл GitHub
Родитель ca9150004c a2b5f3b337
Коммит bb4b94eac9
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
28 изменённых файлов: 478 добавлений и 296 удалений

Просмотреть файл

@ -1,10 +1,20 @@
# VSTHRD003 Avoid awaiting non-joinable tasks in join contexts
# VSTHRD003 Avoid awaiting foreign Tasks
Tasks created from async methods executed outside of a JoinableTaskFactory.Run
delegate cannot be safely awaited inside one.
Tasks that are created and run from another context (not within the currently running method or delegate)
should not be returned or awaited on. Doing so can result in deadlocks because awaiting a `Task`
does not result in the awaiter "joining" the effort such that access to the main thread is shared.
If the awaited `Task` requires the main thread, and the caller that is awaiting it is blocking the
main thread, a deadlock will result.
When required to await a task that was started earlier, start it within a delegate passed to
`JoinableTaskFactory.RunAsync`, storing the resulting `JoinableTask` in a field or variable.
You can safely await the `JoinableTask` later.
## Examples of patterns that are flagged by this analyzer
The following example would likely deadlock if `MyMethod` were called on the main thread,
since `SomeOperationAsync` cannot gain access to the main thread in order to complete.
```csharp
void MyMethod()
{
@ -16,6 +26,31 @@ void MyMethod()
}
```
In the next example, `WaitForMyMethod` may deadlock when `this.task` has not completed
and needs the main thread to complete.
```csharp
class SomeClass
{
System.Threading.Tasks.Task task;
SomeClass()
{
this.task = SomeOperationAsync();
}
async Task MyMethodAsync()
{
await this.task; /* This analyzer will report warning on this line. */
}
void WaitForMyMethod()
{
joinableTaskFactory.Run(() => MyMethodAsync());
}
}
```
## Solution
To await the result of an async method from with a JoinableTaskFactory.Run delegate,
@ -35,12 +70,23 @@ void MyMethod()
Alternatively wrap the original method invocation with JoinableTaskFactory.RunAsync:
```csharp
void MyMethod()
class SomeClass
{
JoinableTask joinableTask = joinableTaskFactory.RunAsync(() => SomeOperationAsync());
joinableTaskFactory.Run(async delegate
JoinableTask joinableTask;
SomeClass()
{
await joinableTask;
});
this.joinableTask = joinableTaskFactory.RunAsync(() => SomeOperationAsync());
}
async Task MyMethodAsync()
{
await this.joinableTask;
}
void WaitForMyMethod()
{
joinableTaskFactory.Run(() => MyMethodAsync());
}
}
```

Просмотреть файл

@ -7,7 +7,7 @@ ID | Title | Severity | Supports
---- | --- | --- | --- |
[VSTHRD001](VSTHRD001.md) | Use JTF.SwitchToMainThread to switch | Critical | [1st rule](../threading_rules.md#Rule1)
[VSTHRD002](VSTHRD002.md) | Use JTF.Run to block | Critical | [2nd rule](../threading_rules.md#Rule2)
[VSTHRD003](VSTHRD003.md) | Use JTF.RunAsync to block later | Critical | [3rd rule](../threading_rules.md#Rule3)
[VSTHRD003](VSTHRD003.md) | Avoid awaiting foreign Tasks | Critical | [3rd rule](../threading_rules.md#Rule3)
[VSTHRD004](VSTHRD004.md) | Await SwitchToMainThreadAsync | Critical | [1st rule](../threading_rules.md#Rule1)
[VSTHRD010](VSTHRD010.md) | Invoke single-threaded types on Main thread | Critical | [1st rule](../threading_rules.md#Rule1)
[VSTHRD011](VSTHRD011.md) | Use `AsyncLazy<T>` | Critical | [3rd rule](../threading_rules.md#Rule3)

Просмотреть файл

@ -128,6 +128,46 @@ class Tests
this.VerifyCSharpDiagnostic(test, this.expect);
}
[Fact]
public void ReportWarningWhenTaskIsReturnedDirectlyFromMethod()
{
var test = @"
using System.Threading.Tasks;
class Tests
{
private Task task;
public Task GetTask()
{
return task;
}
}
";
var expect = this.CreateDiagnostic(10, 16, 4);
this.VerifyCSharpDiagnostic(test, expect);
}
[Fact]
public void ReportWarningWhenTaskIsReturnedAwaitedFromMethod()
{
var test = @"
using System.Threading.Tasks;
class Tests
{
private Task<int> task;
public async Task<int> AwaitAndGetResult()
{
return await task;
}
}
";
var expect = this.CreateDiagnostic(10, 22, 4);
this.VerifyCSharpDiagnostic(test, expect);
}
[Fact]
public void ReportWarningWhenTaskTIsReturnedDirectlyWithCancellation()
{
@ -150,7 +190,30 @@ class Tests
}
[Fact]
public void DoNotReportWarningWhenTaskTIsPassedAsArgument()
public void DoNotReportWarningWhenTaskTIsPassedAsArgumentAndNoTaskIsReturned()
{
var test = @"
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Threading;
using Task = System.Threading.Tasks.Task;
class Tests
{
public static int WaitAndGetResult(Task task)
{
return DoSomethingWith(task);
}
private static int DoSomethingWith(Task t) => 3;
}
";
this.VerifyCSharpDiagnostic(test);
}
[Fact]
public void ReportWarningWhenTaskTIsPassedAsArgumentAndTaskIsReturned()
{
var test = @"
using System.Threading;
@ -169,7 +232,8 @@ class Tests
private static Task DoSomethingWith(Task t) => null;
}
";
this.VerifyCSharpDiagnostic(test);
var expect = this.CreateDiagnostic(12, 68, 4);
this.VerifyCSharpDiagnostic(test, expect);
}
[Fact]
@ -270,6 +334,44 @@ class Tests
this.VerifyCSharpDiagnostic(test);
}
[Fact]
public void DoNotReportWarningWhenReturnedTaskIsDirectlyReturnedFromInvocation()
{
var test = @"
using System.Threading.Tasks;
class Tests
{
public Task Test()
{
return SomeOperationAsync();
}
public Task SomeOperationAsync() => Task.CompletedTask;
}
";
this.VerifyCSharpDiagnostic(test);
}
[Fact]
public void DoNotReportWarningWhenReturnedTaskIsAwaitedReturnedFromInvocation()
{
var test = @"
using System.Threading.Tasks;
class Tests
{
public async Task<int> Test()
{
return await SomeOperationAsync();
}
public Task<int> SomeOperationAsync() => Task.FromResult(3);
}
";
this.VerifyCSharpDiagnostic(test);
}
[Fact]
public void DoNotReportWarningWhenTaskIsDefinedWithinDelegateInSubblock()
{
@ -720,53 +822,83 @@ class Tests
}
[Fact]
public void DoNotReportWarningForDerivedJoinableTaskFactoryWhenRunIsOverride()
public void ReportWarningWhenAwaitingTaskInField()
{
var test = @"
using System;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Threading;
using Task = System.Threading.Tasks.Task;
public class MyJoinableTaskFactory : JoinableTaskFactory
{
public MyJoinableTaskFactory(JoinableTaskFactory innerFactory) : base(innerFactory.Context)
{
}
new public void Run(Func<System.Threading.Tasks.Task> asyncMethod)
{
class Tests {
Task task;
public void Test() {
ThreadHelper.JoinableTaskFactory.Run(async delegate {
await task;
});
}
}
";
var expect = this.CreateDiagnostic(12, 19, 4);
this.VerifyCSharpDiagnostic(test, expect);
}
class Tests
{
public void Test()
{
MyJoinableTaskFactory myjtf = new MyJoinableTaskFactory(ThreadHelper.JoinableTaskFactory);
System.Threading.Tasks.Task<int> task = SomeOperationAsync();
myjtf.Run(async () =>
[Fact]
public void ReportWarningWhenAwaitingTaskInField_WithThisQualifier()
{
var test = @"
using System.Threading.Tasks;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Threading;
using Task = System.Threading.Tasks.Task;
class Tests {
Task task;
public void Test() {
ThreadHelper.JoinableTaskFactory.Run(async delegate {
await this.task;
});
}
}
";
var expect = this.CreateDiagnostic(12, 19, 9);
this.VerifyCSharpDiagnostic(test, expect);
}
[Fact]
public void DoNotReportWarningWhenAwaitingTaskInFieldThatIsAssignedLocally()
{
var test = @"
using System.Threading.Tasks;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Threading;
using Task = System.Threading.Tasks.Task;
class Tests {
Task task;
public void Test() {
ThreadHelper.JoinableTaskFactory.Run(async delegate {
task = SomeOperationAsync();
await task;
});
}
public async Task<int> SomeOperationAsync()
{
await System.Threading.Tasks.Task.Delay(1000);
return 100;
}
Task SomeOperationAsync() => Task.CompletedTask;
}
";
// We decided not to report warning in this case, because we don't know if our assumptions about the Run implementation are still valid for user's implementation
this.VerifyCSharpDiagnostic(test);
}
private DiagnosticResult CreateDiagnostic(int line, int column, int length, string messagePattern = null) =>
new DiagnosticResult
{
Id = this.expect.Id,
MessagePattern = messagePattern ?? this.expect.MessagePattern,
Severity = this.expect.Severity,
Locations = new[] { new DiagnosticResultLocation("Test0.cs", line, column, line, column + length) },
};
}
}

Просмотреть файл

@ -58,14 +58,16 @@
<target state="translated">Použití InvokeAsync k vyvolání asynchronních událostí</target>
</trans-unit>
<trans-unit id="VSTHRD003_MessageFormat" translate="yes" xml:space="preserve">
<source>Calling await on a Task inside a JoinableTaskFactory.Run, when the task is initialized outside the delegate can cause potential deadlocks.
You can avoid this problem by ensuring the task is initialized within the delegate or by using JoinableTask instead of Task.</source>
<target state="translated">Volání operátoru await u položky typu Task uvnitř JoinableTaskFactory.Run v případě, že je úloha inicializovaná mimo delegáta, může způsobit potenciální vzájemná blokování.
<source>Calling await on a Task when the task is initialized in another context can cause potential deadlocks.
You can avoid this problem by ensuring the task is initialized within the same method or by using JoinableTask instead of Task.</source>
<target state="needs-review-translation">Volání operátoru await u položky typu Task uvnitř JoinableTaskFactory.Run v případě, že je úloha inicializovaná mimo delegáta, může způsobit potenciální vzájemná blokování.
Tomuto problému můžete předejít tak, že zajistíte inicializaci úlohy v rámci delegáta nebo místo položky typu Task použijete JoinableTask.</target>
<note from="MultilingualUpdate" annotates="source" priority="2">Please verify the translations accuracy as the source string was updated after it was translated.</note>
</trans-unit>
<trans-unit id="VSTHRD003_Title" translate="yes" xml:space="preserve">
<source>Avoid awaiting non-joinable tasks in join contexts</source>
<target state="translated">Vyhněte se použití operátoru await u nespojitelných úloh v kontextech spojení</target>
<source>Avoid awaiting foreign Tasks</source>
<target state="needs-review-translation">Vyhněte se použití operátoru await u nespojitelných úloh v kontextech spojení</target>
<note from="MultilingualUpdate" annotates="source" priority="2">Please verify the translations accuracy as the source string was updated after it was translated.</note>
</trans-unit>
<trans-unit id="VSTHRD011_MessageFormat" translate="yes" xml:space="preserve">
<source>Lazy&lt;Task&lt;T&gt;&gt;.Value can deadlock.

Просмотреть файл

@ -58,14 +58,16 @@
<target state="translated">InvokeAsync zum Auslösen asynchroner Ereignisse verwenden</target>
</trans-unit>
<trans-unit id="VSTHRD003_MessageFormat" translate="yes" xml:space="preserve">
<source>Calling await on a Task inside a JoinableTaskFactory.Run, when the task is initialized outside the delegate can cause potential deadlocks.
You can avoid this problem by ensuring the task is initialized within the delegate or by using JoinableTask instead of Task.</source>
<target state="translated">Das Aufrufen von "await" für eine Aufgabe innerhalb von "JoinableTaskFactory.Run" führt unter Umständen zu Deadlocks, wenn die Aufgabe außerhalb des Delegaten initialisiert wird.
Sie können dies vermeiden, indem Sie sicherstellen, dass die Aufgabe innerhalb des Delegaten initialisiert wird, oder "JoinableTask" anstelle der Aufgabe verwenden.</target>
<source>Calling await on a Task when the task is initialized in another context can cause potential deadlocks.
You can avoid this problem by ensuring the task is initialized within the same method or by using JoinableTask instead of Task.</source>
<target state="needs-review-translation">Das Aufrufen von "await" für einen Task innerhalb von JoinableTaskFactory.Run führt unter Umständen zu Deadlocks, wenn der Task außerhalb des Delegaten initialisiert wird.
Sie können dies vermeiden, indem Sie sicherstellen, dass der Task innerhalb des Delegaten initialisiert wird, oder JoinableTask anstelle von Task verwenden.</target>
<note from="MultilingualUpdate" annotates="source" priority="2">Please verify the translations accuracy as the source string was updated after it was translated.</note>
</trans-unit>
<trans-unit id="VSTHRD003_Title" translate="yes" xml:space="preserve">
<source>Avoid awaiting non-joinable tasks in join contexts</source>
<target state="translated">Warten auf Tasks, für die kein Beitritt möglich ist, in Join-Kontexten vermeiden</target>
<source>Avoid awaiting foreign Tasks</source>
<target state="needs-review-translation">Warten auf Tasks, für die kein Beitritt möglich ist, in Join-Kontexten vermeiden</target>
<note from="MultilingualUpdate" annotates="source" priority="2">Please verify the translations accuracy as the source string was updated after it was translated.</note>
</trans-unit>
<trans-unit id="VSTHRD011_MessageFormat" translate="yes" xml:space="preserve">
<source>Lazy&lt;Task&lt;T&gt;&gt;.Value can deadlock.

Просмотреть файл

@ -58,14 +58,16 @@
<target state="translated">Use InvokeAsync para desencadenar eventos asincrónicos</target>
</trans-unit>
<trans-unit id="VSTHRD003_MessageFormat" translate="yes" xml:space="preserve">
<source>Calling await on a Task inside a JoinableTaskFactory.Run, when the task is initialized outside the delegate can cause potential deadlocks.
You can avoid this problem by ensuring the task is initialized within the delegate or by using JoinableTask instead of Task.</source>
<target state="translated">La llamada a await en un elemento Task dentro de JoinableTaskFactory.Run, cuando la tarea se ha iniciado fuera del delegado, puede provocar posibles interbloqueos.
<source>Calling await on a Task when the task is initialized in another context can cause potential deadlocks.
You can avoid this problem by ensuring the task is initialized within the same method or by using JoinableTask instead of Task.</source>
<target state="needs-review-translation">La llamada a await en un elemento Task dentro de JoinableTaskFactory.Run, cuando la tarea se ha iniciado fuera del delegado, puede provocar potenciales interbloqueos.
Puede evitar este problema asegurándose de que la tarea se ha iniciado dentro del delegado o usando JoinableTask en lugar de Task.</target>
<note from="MultilingualUpdate" annotates="source" priority="2">Please verify the translations accuracy as the source string was updated after it was translated.</note>
</trans-unit>
<trans-unit id="VSTHRD003_Title" translate="yes" xml:space="preserve">
<source>Avoid awaiting non-joinable tasks in join contexts</source>
<target state="translated">Evite usar await para tareas que no sean de unión en contextos de unión</target>
<source>Avoid awaiting foreign Tasks</source>
<target state="needs-review-translation">Evite usar await para tareas que no sean de unión en contextos de unión</target>
<note from="MultilingualUpdate" annotates="source" priority="2">Please verify the translations accuracy as the source string was updated after it was translated.</note>
</trans-unit>
<trans-unit id="VSTHRD011_MessageFormat" translate="yes" xml:space="preserve">
<source>Lazy&lt;Task&lt;T&gt;&gt;.Value can deadlock.

Просмотреть файл

@ -58,14 +58,16 @@
<target state="translated">Utiliser InvokeAsync pour déclencher des événements async</target>
</trans-unit>
<trans-unit id="VSTHRD003_MessageFormat" translate="yes" xml:space="preserve">
<source>Calling await on a Task inside a JoinableTaskFactory.Run, when the task is initialized outside the delegate can cause potential deadlocks.
You can avoid this problem by ensuring the task is initialized within the delegate or by using JoinableTask instead of Task.</source>
<target state="translated">Le fait d'appeler await sur une tâche dans une méthode JoinableTaskFactory.Run quand la tâche est initialisée en dehors du délégué peut entraîner des blocages.
<source>Calling await on a Task when the task is initialized in another context can cause potential deadlocks.
You can avoid this problem by ensuring the task is initialized within the same method or by using JoinableTask instead of Task.</source>
<target state="needs-review-translation">Le fait dappeler await sur une tâche dans une méthode JoinableTaskFactory.Run quand la tâche est initialisée en dehors du délégué peut entraîner des blocages.
Vous pouvez éviter ce problème en vérifiant que la tâche est initialisée dans le délégué ou en utilisant JoinableTask à la place de Task.</target>
<note from="MultilingualUpdate" annotates="source" priority="2">Please verify the translations accuracy as the source string was updated after it was translated.</note>
</trans-unit>
<trans-unit id="VSTHRD003_Title" translate="yes" xml:space="preserve">
<source>Avoid awaiting non-joinable tasks in join contexts</source>
<target state="translated">Éviter dattendre des tâches non joignables dans des contextes de jointure</target>
<source>Avoid awaiting foreign Tasks</source>
<target state="needs-review-translation">Éviter dattendre des tâches non joignables dans des contextes de jointure</target>
<note from="MultilingualUpdate" annotates="source" priority="2">Please verify the translations accuracy as the source string was updated after it was translated.</note>
</trans-unit>
<trans-unit id="VSTHRD011_MessageFormat" translate="yes" xml:space="preserve">
<source>Lazy&lt;Task&lt;T&gt;&gt;.Value can deadlock.

Просмотреть файл

@ -58,14 +58,16 @@
<target state="translated">Usa InvokeAsync per generare eventi asincroni</target>
</trans-unit>
<trans-unit id="VSTHRD003_MessageFormat" translate="yes" xml:space="preserve">
<source>Calling await on a Task inside a JoinableTaskFactory.Run, when the task is initialized outside the delegate can cause potential deadlocks.
You can avoid this problem by ensuring the task is initialized within the delegate or by using JoinableTask instead of Task.</source>
<target state="translated">La chiamata di await su un elemento Task all'interno di un elemento JoinableTaskFactory.Run quando Task è inizializzato all'esterno del delegato può causare possibili deadlock.
<source>Calling await on a Task when the task is initialized in another context can cause potential deadlocks.
You can avoid this problem by ensuring the task is initialized within the same method or by using JoinableTask instead of Task.</source>
<target state="needs-review-translation">La chiamata di await su un elemento Task all'interno di un elemento JoinableTaskFactory.Run quando Task è inizializzato all'esterno del delegato può causare possibili deadlock.
Per evitare il problema, assicurarsi che Task venga inizializzato all'interno del delegato oppure usare JoinableTask invece di Task.</target>
<note from="MultilingualUpdate" annotates="source" priority="2">Please verify the translations accuracy as the source string was updated after it was translated.</note>
</trans-unit>
<trans-unit id="VSTHRD003_Title" translate="yes" xml:space="preserve">
<source>Avoid awaiting non-joinable tasks in join contexts</source>
<target state="translated">Evita l'uso di await Task non joinable in contesti di join</target>
<source>Avoid awaiting foreign Tasks</source>
<target state="needs-review-translation">Evita attesa di Task non joinable in contesti di join</target>
<note from="MultilingualUpdate" annotates="source" priority="2">Please verify the translations accuracy as the source string was updated after it was translated.</note>
</trans-unit>
<trans-unit id="VSTHRD011_MessageFormat" translate="yes" xml:space="preserve">
<source>Lazy&lt;Task&lt;T&gt;&gt;.Value can deadlock.

Просмотреть файл

@ -58,14 +58,16 @@
<target state="translated">InvokeAsync を使用して非同期イベントを発生させる</target>
</trans-unit>
<trans-unit id="VSTHRD003_MessageFormat" translate="yes" xml:space="preserve">
<source>Calling await on a Task inside a JoinableTaskFactory.Run, when the task is initialized outside the delegate can cause potential deadlocks.
You can avoid this problem by ensuring the task is initialized within the delegate or by using JoinableTask instead of Task.</source>
<target state="translated">タスクがデリゲート外部で初期化されている場合、JoinableTaskFactory.Run 内の Task で await を呼び出すと、デッドロックが引き起こされる可能性があります。
<source>Calling await on a Task when the task is initialized in another context can cause potential deadlocks.
You can avoid this problem by ensuring the task is initialized within the same method or by using JoinableTask instead of Task.</source>
<target state="needs-review-translation">タスクがデリゲート外部で初期化されている場合、JoinableTaskFactory.Run 内の Task で Await を呼び出すと、デッドロックが引き起こされる可能性があります。
この問題は、タスクが確実にデリゲート内で初期化されるようにするか、Task の代わりに JoinableTask を使用することによって回避できます。</target>
<note from="MultilingualUpdate" annotates="source" priority="2">Please verify the translations accuracy as the source string was updated after it was translated.</note>
</trans-unit>
<trans-unit id="VSTHRD003_Title" translate="yes" xml:space="preserve">
<source>Avoid awaiting non-joinable tasks in join contexts</source>
<target state="translated">結合コンテキストで結合できないタスクを待機しない</target>
<source>Avoid awaiting foreign Tasks</source>
<target state="needs-review-translation">結合コンテキストで結合できないタスクを待機しない</target>
<note from="MultilingualUpdate" annotates="source" priority="2">Please verify the translations accuracy as the source string was updated after it was translated.</note>
</trans-unit>
<trans-unit id="VSTHRD011_MessageFormat" translate="yes" xml:space="preserve">
<source>Lazy&lt;Task&lt;T&gt;&gt;.Value can deadlock.

Просмотреть файл

@ -63,14 +63,16 @@
<target state="translated">비동기 이벤트를 발생하는 InvokeAsync 사용</target>
</trans-unit>
<trans-unit id="VSTHRD003_MessageFormat" translate="yes" xml:space="preserve">
<source>Calling await on a Task inside a JoinableTaskFactory.Run, when the task is initialized outside the delegate can cause potential deadlocks.
You can avoid this problem by ensuring the task is initialized within the delegate or by using JoinableTask instead of Task.</source>
<target state="translated">작업이 외부에서 초기화되면 대리자가 잠재적인 교착 상태를 일으킬 수 있으므로 JoinableTaskFactory.Run 내 Task에서 호출을 대기합니다.
<source>Calling await on a Task when the task is initialized in another context can cause potential deadlocks.
You can avoid this problem by ensuring the task is initialized within the same method or by using JoinableTask instead of Task.</source>
<target state="needs-review-translation">작업이 외부에서 초기화되면 대리자가 잠재적인 교착 상태를 일으킬 수 있으므로 JoinableTaskFactory.Run 내 Task에서 호출을 대기합니다.
작업이 대리자 내에서 초기화되도록 하거나 Task 대신 JoinableTask를 사용하여 이 문제를 방지할 수 있습니다.</target>
<note from="MultilingualUpdate" annotates="source" priority="2">Please verify the translations accuracy as the source string was updated after it was translated.</note>
</trans-unit>
<trans-unit id="VSTHRD003_Title" translate="yes" xml:space="preserve">
<source>Avoid awaiting non-joinable tasks in join contexts</source>
<target state="translated">조인 컨텍스트에서 조인할 수 없는 작업을 대기하지 않습니다.</target>
<source>Avoid awaiting foreign Tasks</source>
<target state="needs-review-translation">조인 컨텍스트에서 조인할 수 없는 작업을 대기하지 않습니다.</target>
<note from="MultilingualUpdate" annotates="source" priority="2">Please verify the translations accuracy as the source string was updated after it was translated.</note>
</trans-unit>
<trans-unit id="VSTHRD011_MessageFormat" translate="yes" xml:space="preserve">
<source>Lazy&lt;Task&lt;T&gt;&gt;.Value can deadlock.

Просмотреть файл

@ -58,14 +58,16 @@
<target state="translated">Zdarzenia asynchroniczne wywołuj za pomocą metody InvokeAsync</target>
</trans-unit>
<trans-unit id="VSTHRD003_MessageFormat" translate="yes" xml:space="preserve">
<source>Calling await on a Task inside a JoinableTaskFactory.Run, when the task is initialized outside the delegate can cause potential deadlocks.
You can avoid this problem by ensuring the task is initialized within the delegate or by using JoinableTask instead of Task.</source>
<target state="translated">Wywołanie oczekiwania dla obiektu Task wewnątrz metody JoinableTaskFactory.Run, gdy dane zadanie zainicjowano poza delegatem, może spowodować zakleszczenia.
<source>Calling await on a Task when the task is initialized in another context can cause potential deadlocks.
You can avoid this problem by ensuring the task is initialized within the same method or by using JoinableTask instead of Task.</source>
<target state="needs-review-translation">Wywołanie oczekiwania dla obiektu Task wewnątrz metody JoinableTaskFactory.Run, gdy dane zadanie zainicjowano poza delegatem, może spowodować zakleszczenia.
Tego problemu można uniknąć, zapewniając, że zadanie zostanie zainicjowane wewnątrz delegata lub za pomocą klasy JoinableTask (a nie klasy Task).</target>
<note from="MultilingualUpdate" annotates="source" priority="2">Please verify the translations accuracy as the source string was updated after it was translated.</note>
</trans-unit>
<trans-unit id="VSTHRD003_Title" translate="yes" xml:space="preserve">
<source>Avoid awaiting non-joinable tasks in join contexts</source>
<target state="translated">Unikaj oczekiwania na zadania bez możliwości dołączenia w kontekstach dołączania</target>
<source>Avoid awaiting foreign Tasks</source>
<target state="needs-review-translation">Unikaj oczekiwania na zadania bez możliwości dołączenia w kontekstach dołączania</target>
<note from="MultilingualUpdate" annotates="source" priority="2">Please verify the translations accuracy as the source string was updated after it was translated.</note>
</trans-unit>
<trans-unit id="VSTHRD011_MessageFormat" translate="yes" xml:space="preserve">
<source>Lazy&lt;Task&lt;T&gt;&gt;.Value can deadlock.

Просмотреть файл

@ -58,14 +58,16 @@
<target state="translated">Use InvokeAsync para acionar eventos assíncronos</target>
</trans-unit>
<trans-unit id="VSTHRD003_MessageFormat" translate="yes" xml:space="preserve">
<source>Calling await on a Task inside a JoinableTaskFactory.Run, when the task is initialized outside the delegate can cause potential deadlocks.
You can avoid this problem by ensuring the task is initialized within the delegate or by using JoinableTask instead of Task.</source>
<target state="translated">Chamar await em uma Task em JoinableTaskFactory.Run, quando a tarefa for inicializada fora do delegado pode causar deadlocks em potencial.
<source>Calling await on a Task when the task is initialized in another context can cause potential deadlocks.
You can avoid this problem by ensuring the task is initialized within the same method or by using JoinableTask instead of Task.</source>
<target state="needs-review-translation">Chamar await em uma Task em JoinableTaskFactory.Run, quando a tarefa for inicializada fora do delegado pode causar deadlocks em potencial.
Você pode evitar esse problema assegurando que a tarefa seja inicializada no delegado ou usando JoinableTask em vez de Task.</target>
<note from="MultilingualUpdate" annotates="source" priority="2">Please verify the translations accuracy as the source string was updated after it was translated.</note>
</trans-unit>
<trans-unit id="VSTHRD003_Title" translate="yes" xml:space="preserve">
<source>Avoid awaiting non-joinable tasks in join contexts</source>
<target state="translated">Evite aguardar tarefas não unidas em contextos de junção</target>
<source>Avoid awaiting foreign Tasks</source>
<target state="needs-review-translation">Evite aguardar tarefas não unidas em contextos de junção</target>
<note from="MultilingualUpdate" annotates="source" priority="2">Please verify the translations accuracy as the source string was updated after it was translated.</note>
</trans-unit>
<trans-unit id="VSTHRD011_MessageFormat" translate="yes" xml:space="preserve">
<source>Lazy&lt;Task&lt;T&gt;&gt;.Value can deadlock.

Просмотреть файл

@ -58,14 +58,16 @@
<target state="translated">Использование InvokeAsync для вызова асинхронных событий</target>
</trans-unit>
<trans-unit id="VSTHRD003_MessageFormat" translate="yes" xml:space="preserve">
<source>Calling await on a Task inside a JoinableTaskFactory.Run, when the task is initialized outside the delegate can cause potential deadlocks.
You can avoid this problem by ensuring the task is initialized within the delegate or by using JoinableTask instead of Task.</source>
<target state="translated">При вызове await в объекте Task в JoinableTaskFactory.Run (и при условии, что задача инициализирована за пределами делегата) могут произойти взаимоблокировки.
<source>Calling await on a Task when the task is initialized in another context can cause potential deadlocks.
You can avoid this problem by ensuring the task is initialized within the same method or by using JoinableTask instead of Task.</source>
<target state="needs-review-translation">При вызове await в объекте Task в JoinableTaskFactory.Run (и при условии, что задача инициализирована за пределами делегата), могут произойти взаимоблокировки.
Вы можете избежать этой проблемы, инициализировав задачу в делегате или использовав JoinableTask вместо Task.</target>
<note from="MultilingualUpdate" annotates="source" priority="2">Please verify the translations accuracy as the source string was updated after it was translated.</note>
</trans-unit>
<trans-unit id="VSTHRD003_Title" translate="yes" xml:space="preserve">
<source>Avoid awaiting non-joinable tasks in join contexts</source>
<target state="translated">Избегание await в неприсоединяемых задачах в контекстах соединения</target>
<source>Avoid awaiting foreign Tasks</source>
<target state="needs-review-translation">Избегание await в неприсоединяемых задачах в контекстах соединения</target>
<note from="MultilingualUpdate" annotates="source" priority="2">Please verify the translations accuracy as the source string was updated after it was translated.</note>
</trans-unit>
<trans-unit id="VSTHRD011_MessageFormat" translate="yes" xml:space="preserve">
<source>Lazy&lt;Task&lt;T&gt;&gt;.Value can deadlock.

Просмотреть файл

@ -58,14 +58,16 @@
<target state="translated">Async olayları tetiklemek için InvokeAsync kullanın</target>
</trans-unit>
<trans-unit id="VSTHRD003_MessageFormat" translate="yes" xml:space="preserve">
<source>Calling await on a Task inside a JoinableTaskFactory.Run, when the task is initialized outside the delegate can cause potential deadlocks.
You can avoid this problem by ensuring the task is initialized within the delegate or by using JoinableTask instead of Task.</source>
<target state="translated">JoinableTaskFactory.Run içindeki bir Task için await çağırmak, görevin temsilci dışında başlatılması durumunda olası kilitlenmelere neden olabilir.
Görevin temsilci içinde başlatılmasını sağlayarak veya Task yerine JoinableTask kullanarak bu sorunu önleyebilirsiniz.</target>
<source>Calling await on a Task when the task is initialized in another context can cause potential deadlocks.
You can avoid this problem by ensuring the task is initialized within the same method or by using JoinableTask instead of Task.</source>
<target state="needs-review-translation">JoinableTaskFactory.Run içindeki bir Task için await çağırmak, görevin temsilci dışında başlatılması durumunda kilitlenmelere neden olabilir.
Görevin temsilcinin içinde başlatılmasını sağlayarak veya Task yerine JoinableTask kullanarak bu sorunu önleyebilirsiniz.</target>
<note from="MultilingualUpdate" annotates="source" priority="2">Please verify the translations accuracy as the source string was updated after it was translated.</note>
</trans-unit>
<trans-unit id="VSTHRD003_Title" translate="yes" xml:space="preserve">
<source>Avoid awaiting non-joinable tasks in join contexts</source>
<target state="translated">Birleşik olmayan görevleri birleşik ortamlarda beklemeyin</target>
<source>Avoid awaiting foreign Tasks</source>
<target state="needs-review-translation">Birleşik olmayan görevleri birleşik ortamlarda beklemeyin</target>
<note from="MultilingualUpdate" annotates="source" priority="2">Please verify the translations accuracy as the source string was updated after it was translated.</note>
</trans-unit>
<trans-unit id="VSTHRD011_MessageFormat" translate="yes" xml:space="preserve">
<source>Lazy&lt;Task&lt;T&gt;&gt;.Value can deadlock.

Просмотреть файл

@ -58,14 +58,16 @@
<target state="translated">使用 InvokeAsync 引发异步事件</target>
</trans-unit>
<trans-unit id="VSTHRD003_MessageFormat" translate="yes" xml:space="preserve">
<source>Calling await on a Task inside a JoinableTaskFactory.Run, when the task is initialized outside the delegate can cause potential deadlocks.
You can avoid this problem by ensuring the task is initialized within the delegate or by using JoinableTask instead of Task.</source>
<target state="translated">在委托外初始化任务时,对 JoinableTaskFactory.Run 内部 Task 调用 await 可能导致死锁。
<source>Calling await on a Task when the task is initialized in another context can cause potential deadlocks.
You can avoid this problem by ensuring the task is initialized within the same method or by using JoinableTask instead of Task.</source>
<target state="needs-review-translation">在委托外初始化任务时,对 JoinableTaskFactory.Run 内部 Task 调用 await 可能导致死锁。
通过确保在委托内部初始化任务或使用 JoinableTask 取代 Task可以避免此问题。</target>
<note from="MultilingualUpdate" annotates="source" priority="2">Please verify the translations accuracy as the source string was updated after it was translated.</note>
</trans-unit>
<trans-unit id="VSTHRD003_Title" translate="yes" xml:space="preserve">
<source>Avoid awaiting non-joinable tasks in join contexts</source>
<target state="translated">避免等待联接上下文中的非可加入任务</target>
<source>Avoid awaiting foreign Tasks</source>
<target state="needs-review-translation">避免等待联接上下文中的非可加入任务</target>
<note from="MultilingualUpdate" annotates="source" priority="2">Please verify the translations accuracy as the source string was updated after it was translated.</note>
</trans-unit>
<trans-unit id="VSTHRD011_MessageFormat" translate="yes" xml:space="preserve">
<source>Lazy&lt;Task&lt;T&gt;&gt;.Value can deadlock.

Просмотреть файл

@ -58,14 +58,16 @@
<target state="translated">使用 InvokeAsync 引發非同步事件</target>
</trans-unit>
<trans-unit id="VSTHRD003_MessageFormat" translate="yes" xml:space="preserve">
<source>Calling await on a Task inside a JoinableTaskFactory.Run, when the task is initialized outside the delegate can cause potential deadlocks.
You can avoid this problem by ensuring the task is initialized within the delegate or by using JoinableTask instead of Task.</source>
<target state="translated">當工作在委派的外部初始化時,對 JoinableTaskFactory.Run 內的 Task 上呼叫 await 可能會造成潛在的死結。
您可以讓工作在委派內初始化,或不使用 Task 而改用 JoinableTask 來避免此問題。</target>
<source>Calling await on a Task when the task is initialized in another context can cause potential deadlocks.
You can avoid this problem by ensuring the task is initialized within the same method or by using JoinableTask instead of Task.</source>
<target state="needs-review-translation">當工作在委派外部初始化時,在 JoinableTaskFactory.Run 內的 Task 上呼叫 await可能會造成潛在的鎖死情形。
您可以藉由確認工作在委派內初始化,或將 Task 改為使用 JoinableTask來避免此問題。</target>
<note from="MultilingualUpdate" annotates="source" priority="2">Please verify the translations accuracy as the source string was updated after it was translated.</note>
</trans-unit>
<trans-unit id="VSTHRD003_Title" translate="yes" xml:space="preserve">
<source>Avoid awaiting non-joinable tasks in join contexts</source>
<target state="translated">避免在加入內容中等候不可加入的工作</target>
<source>Avoid awaiting foreign Tasks</source>
<target state="needs-review-translation">避免在加入內容中等候不可加入的工作</target>
<note from="MultilingualUpdate" annotates="source" priority="2">Please verify the translations accuracy as the source string was updated after it was translated.</note>
</trans-unit>
<trans-unit id="VSTHRD011_MessageFormat" translate="yes" xml:space="preserve">
<source>Lazy&lt;Task&lt;T&gt;&gt;.Value can deadlock.

Просмотреть файл

@ -125,8 +125,8 @@ namespace Microsoft.VisualStudio.Threading.Analyzers {
}
/// <summary>
/// Looks up a localized string similar to Calling await on a Task inside a JoinableTaskFactory.Run, when the task is initialized outside the delegate can cause potential deadlocks.
///You can avoid this problem by ensuring the task is initialized within the delegate or by using JoinableTask instead of Task..
/// Looks up a localized string similar to Calling await on a Task when the task is initialized in another context can cause potential deadlocks.
///You can avoid this problem by ensuring the task is initialized within the same method or by using JoinableTask instead of Task..
/// </summary>
internal static string VSTHRD003_MessageFormat {
get {
@ -135,7 +135,7 @@ namespace Microsoft.VisualStudio.Threading.Analyzers {
}
/// <summary>
/// Looks up a localized string similar to Avoid awaiting non-joinable tasks in join contexts.
/// Looks up a localized string similar to Avoid awaiting foreign Tasks.
/// </summary>
internal static string VSTHRD003_Title {
get {

Просмотреть файл

@ -53,8 +53,8 @@
<value>InvokeAsync zum Auslösen asynchroner Ereignisse verwenden</value>
</data>
<data name="VSTHRD003_MessageFormat" xml:space="preserve">
<value>Das Aufrufen von "await" für eine Aufgabe innerhalb von "JoinableTaskFactory.Run" führt unter Umständen zu Deadlocks, wenn die Aufgabe außerhalb des Delegaten initialisiert wird.
Sie können dies vermeiden, indem Sie sicherstellen, dass die Aufgabe innerhalb des Delegaten initialisiert wird, oder "JoinableTask" anstelle der Aufgabe verwenden.</value>
<value>Das Aufrufen von "await" für einen Task innerhalb von JoinableTaskFactory.Run führt unter Umständen zu Deadlocks, wenn der Task außerhalb des Delegaten initialisiert wird.
Sie können dies vermeiden, indem Sie sicherstellen, dass der Task innerhalb des Delegaten initialisiert wird, oder JoinableTask anstelle von Task verwenden.</value>
</data>
<data name="VSTHRD003_Title" xml:space="preserve">
<value>Warten auf Tasks, für die kein Beitritt möglich ist, in Join-Kontexten vermeiden</value>

Просмотреть файл

@ -53,7 +53,7 @@
<value>Use InvokeAsync para desencadenar eventos asincrónicos</value>
</data>
<data name="VSTHRD003_MessageFormat" xml:space="preserve">
<value>La llamada a await en un elemento Task dentro de JoinableTaskFactory.Run, cuando la tarea se ha iniciado fuera del delegado, puede provocar posibles interbloqueos.
<value>La llamada a await en un elemento Task dentro de JoinableTaskFactory.Run, cuando la tarea se ha iniciado fuera del delegado, puede provocar potenciales interbloqueos.
Puede evitar este problema asegurándose de que la tarea se ha iniciado dentro del delegado o usando JoinableTask en lugar de Task.</value>
</data>
<data name="VSTHRD003_Title" xml:space="preserve">

Просмотреть файл

@ -53,7 +53,7 @@
<value>Utiliser InvokeAsync pour déclencher des événements async</value>
</data>
<data name="VSTHRD003_MessageFormat" xml:space="preserve">
<value>Le fait d'appeler await sur une tâche dans une méthode JoinableTaskFactory.Run quand la tâche est initialisée en dehors du délégué peut entraîner des blocages.
<value>Le fait dappeler await sur une tâche dans une méthode JoinableTaskFactory.Run quand la tâche est initialisée en dehors du délégué peut entraîner des blocages.
Vous pouvez éviter ce problème en vérifiant que la tâche est initialisée dans le délégué ou en utilisant JoinableTask à la place de Task.</value>
</data>
<data name="VSTHRD003_Title" xml:space="preserve">

Просмотреть файл

@ -57,7 +57,7 @@
Per evitare il problema, assicurarsi che Task venga inizializzato all'interno del delegato oppure usare JoinableTask invece di Task.</value>
</data>
<data name="VSTHRD003_Title" xml:space="preserve">
<value>Evita l'uso di await Task non joinable in contesti di join</value>
<value>Evita attesa di Task non joinable in contesti di join</value>
</data>
<data name="VSTHRD011_MessageFormat" xml:space="preserve">
<value>Lazy&lt;Task&lt;T&gt;&gt;.Value può causare deadlock.

Просмотреть файл

@ -53,7 +53,7 @@
<value>InvokeAsync を使用して非同期イベントを発生させる</value>
</data>
<data name="VSTHRD003_MessageFormat" xml:space="preserve">
<value>タスクがデリゲート外部で初期化されている場合、JoinableTaskFactory.Run 内の Task で await を呼び出すと、デッドロックが引き起こされる可能性があります。
<value>タスクがデリゲート外部で初期化されている場合、JoinableTaskFactory.Run 内の Task で Await を呼び出すと、デッドロックが引き起こされる可能性があります。
この問題は、タスクが確実にデリゲート内で初期化されるようにするか、Task の代わりに JoinableTask を使用することによって回避できます。</value>
</data>
<data name="VSTHRD003_Title" xml:space="preserve">

Просмотреть файл

@ -166,11 +166,11 @@
<value>Use InvokeAsync to raise async events</value>
</data>
<data name="VSTHRD003_MessageFormat" xml:space="preserve">
<value>Calling await on a Task inside a JoinableTaskFactory.Run, when the task is initialized outside the delegate can cause potential deadlocks.
You can avoid this problem by ensuring the task is initialized within the delegate or by using JoinableTask instead of Task.</value>
<value>Calling await on a Task when the task is initialized in another context can cause potential deadlocks.
You can avoid this problem by ensuring the task is initialized within the same method or by using JoinableTask instead of Task.</value>
</data>
<data name="VSTHRD003_Title" xml:space="preserve">
<value>Avoid awaiting non-joinable tasks in join contexts</value>
<value>Avoid awaiting foreign Tasks</value>
</data>
<data name="VSTHRD011b_MessageFormat" xml:space="preserve">
<value>Invoking or blocking on async code in a Lazy&lt;T&gt; value factory can deadlock.

Просмотреть файл

@ -53,7 +53,7 @@
<value>Использование InvokeAsync для вызова асинхронных событий</value>
</data>
<data name="VSTHRD003_MessageFormat" xml:space="preserve">
<value>При вызове await в объекте Task в JoinableTaskFactory.Run (и при условии, что задача инициализирована за пределами делегата) могут произойти взаимоблокировки.
<value>При вызове await в объекте Task в JoinableTaskFactory.Run (и при условии, что задача инициализирована за пределами делегата), могут произойти взаимоблокировки.
Вы можете избежать этой проблемы, инициализировав задачу в делегате или использовав JoinableTask вместо Task.</value>
</data>
<data name="VSTHRD003_Title" xml:space="preserve">

Просмотреть файл

@ -53,8 +53,8 @@
<value>Async olayları tetiklemek için InvokeAsync kullanın</value>
</data>
<data name="VSTHRD003_MessageFormat" xml:space="preserve">
<value>JoinableTaskFactory.Run içindeki bir Task için await çağırmak, görevin temsilci dışında başlatılması durumunda olası kilitlenmelere neden olabilir.
Görevin temsilci içinde başlatılmasını sağlayarak veya Task yerine JoinableTask kullanarak bu sorunu önleyebilirsiniz.</value>
<value>JoinableTaskFactory.Run içindeki bir Task için await çağırmak, görevin temsilci dışında başlatılması durumunda kilitlenmelere neden olabilir.
Görevin temsilcinin içinde başlatılmasını sağlayarak veya Task yerine JoinableTask kullanarak bu sorunu önleyebilirsiniz.</value>
</data>
<data name="VSTHRD003_Title" xml:space="preserve">
<value>Birleşik olmayan görevleri birleşik ortamlarda beklemeyin</value>

Просмотреть файл

@ -53,8 +53,8 @@
<value>使用 InvokeAsync 引發非同步事件</value>
</data>
<data name="VSTHRD003_MessageFormat" xml:space="preserve">
<value>當工作在委派的外部初始化時,對 JoinableTaskFactory.Run 內的 Task 上呼叫 await 可能會造成潛在的死結
您可以讓工作在委派內初始化,或不使用 Task 而改用 JoinableTask 來避免此問題。</value>
<value>當工作在委派外部初始化時,在 JoinableTaskFactory.Run 內的 Task 上呼叫 await可能會造成潛在的鎖死情形
您可以藉由確認工作在委派內初始化,或將 Task 改為使用 JoinableTask來避免此問題。</value>
</data>
<data name="VSTHRD003_Title" xml:space="preserve">
<value>避免在加入內容中等候不可加入的工作</value>

Просмотреть файл

@ -62,6 +62,25 @@ namespace Microsoft.VisualStudio.Threading.Analyzers
};
}
internal static Action<CodeBlockAnalysisContext> DebuggableWrapper(Action<CodeBlockAnalysisContext> handler)
{
return ctxt =>
{
try
{
handler(ctxt);
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception ex) when (LaunchDebuggerExceptionFilter())
{
throw new Exception($"Analyzer failure while processing syntax at {ctxt.CodeBlock.SyntaxTree.FilePath}({ctxt.CodeBlock.GetLocation()?.GetLineSpan().StartLinePosition.Line + 1},{ctxt.CodeBlock.GetLocation()?.GetLineSpan().StartLinePosition.Character + 1}): {ex.GetType()} {ex.Message}. Syntax: {ctxt.CodeBlock}", ex);
}
};
}
internal static ExpressionSyntax IsolateMethodName(InvocationExpressionSyntax invocation)
{
if (invocation == null)
@ -211,9 +230,12 @@ namespace Microsoft.VisualStudio.Threading.Analyzers
internal static bool IsAsyncCompatibleReturnType(this ITypeSymbol typeSymbol)
{
return typeSymbol?.Name == nameof(Task) && typeSymbol.BelongsToNamespace(Namespaces.SystemThreadingTasks);
// We could be more aggressive here and allow other types that implement the async method builder pattern.
return IsTask(typeSymbol);
}
internal static bool IsTask(ITypeSymbol typeSymbol) => typeSymbol?.Name == nameof(Task) && typeSymbol.BelongsToNamespace(Namespaces.SystemThreadingTasks);
/// <summary>
/// Gets a value indicating whether a method is async or is ready to be async by having an async-compatible return type.
/// </summary>
@ -349,6 +371,38 @@ namespace Microsoft.VisualStudio.Threading.Analyzers
return false;
}
internal static bool IsAssignedWithin(SyntaxNode container, SemanticModel semanticModel, ISymbol variable, CancellationToken cancellationToken)
{
if (semanticModel == null)
{
throw new ArgumentNullException(nameof(semanticModel));
}
if (variable == null)
{
throw new ArgumentNullException(nameof(variable));
}
if (container == null)
{
return false;
}
foreach (var node in container.DescendantNodesAndSelf(n => !(n is AnonymousFunctionExpressionSyntax)))
{
if (node is AssignmentExpressionSyntax assignment)
{
var assignedSymbol = semanticModel.GetSymbolInfo(assignment.Left, cancellationToken).Symbol;
if (variable.Equals(assignedSymbol))
{
return true;
}
}
}
return false;
}
internal static IEnumerable<ITypeSymbol> FindInterfacesImplemented(this ISymbol symbol)
{
if (symbol == null)
@ -830,7 +884,7 @@ namespace Microsoft.VisualStudio.Threading.Analyzers
{
// Is the FromResult method on the Task or Task<T> class?
var memberOwnerSymbol = semanticModel.GetSymbolInfo(originalSyntax, cancellationToken).Symbol;
if (memberOwnerSymbol?.ContainingType?.Name == nameof(Task) && memberOwnerSymbol.ContainingType.BelongsToNamespace(Namespaces.SystemThreadingTasks))
if (IsTask(memberOwnerSymbol?.ContainingType))
{
var simplified = awaitedInvocation.ArgumentList.Arguments.Single().Expression;
return simplified;

Просмотреть файл

@ -51,6 +51,11 @@ namespace Microsoft.VisualStudio.Threading.Analyzers
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true);
private static readonly IReadOnlyCollection<Type> DoNotPassTypesInSearchForAnonFuncInvocation = new[]
{
typeof(MethodDeclarationSyntax),
};
/// <inheritdoc />
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
{
@ -67,223 +72,142 @@ namespace Microsoft.VisualStudio.Threading.Analyzers
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze);
context.RegisterSyntaxNodeAction(Utils.DebuggableWrapper(this.AnalyzeAwaitExpression), SyntaxKind.AwaitExpression);
context.RegisterSyntaxNodeAction(Utils.DebuggableWrapper(this.AnalyzeReturnStatement), SyntaxKind.ReturnStatement);
context.RegisterSyntaxNodeAction(Utils.DebuggableWrapper(this.AnalyzeLambdaExpression), SyntaxKind.SimpleLambdaExpression);
context.RegisterSyntaxNodeAction(Utils.DebuggableWrapper(this.AnalyzeLambdaExpression), SyntaxKind.ParenthesizedLambdaExpression);
}
private void AnalyzeLambdaExpression(SyntaxNodeAnalysisContext context)
{
var anonFuncSyntax = (AnonymousFunctionExpressionSyntax)context.Node;
// Check whether it is called by Jtf.Run
InvocationExpressionSyntax invocationExpressionSyntax = this.FindInvocationOfDelegateOrLambdaExpression(anonFuncSyntax);
if (invocationExpressionSyntax == null || !this.IsInvocationExpressionACallToJtfRun(context, invocationExpressionSyntax))
var lambdaExpression = (LambdaExpressionSyntax)context.Node;
if (lambdaExpression.Body is ExpressionSyntax expression)
{
return;
var diagnostic = this.AnalyzeAwaitedOrReturnedExpression(expression, context.SemanticModel, context.CancellationToken);
if (diagnostic != null)
{
context.ReportDiagnostic(diagnostic);
}
}
}
var expressionsToSearch = Enumerable.Empty<ExpressionSyntax>();
switch (anonFuncSyntax.Body)
private void AnalyzeReturnStatement(SyntaxNodeAnalysisContext context)
{
var returnStatement = (ReturnStatementSyntax)context.Node;
var diagnostic = this.AnalyzeAwaitedOrReturnedExpression(returnStatement.Expression, context.SemanticModel, context.CancellationToken);
if (diagnostic != null)
{
case ExpressionSyntax expr:
expressionsToSearch = new ExpressionSyntax[] { expr };
break;
case BlockSyntax block:
expressionsToSearch = from ret in block.DescendantNodes().OfType<ReturnStatementSyntax>()
where ret.Expression != null
select ret.Expression;
break;
}
var identifiers = from ex in expressionsToSearch
from identifier in ex.DescendantNodesAndSelf(n => !(n is ArgumentListSyntax)).OfType<IdentifierNameSyntax>()
select new { expression = ex, identifier };
foreach (var identifier in identifiers)
{
this.ReportDiagnosticIfSymbolIsExternalTask(context, identifier.expression, identifier.identifier, anonFuncSyntax);
context.ReportDiagnostic(diagnostic);
}
}
private void AnalyzeAwaitExpression(SyntaxNodeAnalysisContext context)
{
AwaitExpressionSyntax awaitExpressionSyntax = (AwaitExpressionSyntax)context.Node;
IdentifierNameSyntax identifierNameSyntaxAwaitingOn = awaitExpressionSyntax.Expression as IdentifierNameSyntax;
if (identifierNameSyntaxAwaitingOn == null)
var diagnostic = this.AnalyzeAwaitedOrReturnedExpression(awaitExpressionSyntax.Expression, context.SemanticModel, context.CancellationToken);
if (diagnostic != null)
{
return;
context.ReportDiagnostic(diagnostic);
}
SyntaxNode currentNode = identifierNameSyntaxAwaitingOn;
// Step 1: Find the async delegate or lambda expression that matches the await
SyntaxNode delegateOrLambdaNode = this.FindAsyncDelegateOrLambdaExpressiomMatchingAwait(awaitExpressionSyntax);
if (delegateOrLambdaNode == null)
{
return;
}
// Step 2: Check whether it is called by Jtf.Run
InvocationExpressionSyntax invocationExpressionSyntax = this.FindInvocationOfDelegateOrLambdaExpression(delegateOrLambdaNode);
if (invocationExpressionSyntax == null || !this.IsInvocationExpressionACallToJtfRun(context, invocationExpressionSyntax))
{
return;
}
this.ReportDiagnosticIfSymbolIsExternalTask(context, awaitExpressionSyntax.Expression, identifierNameSyntaxAwaitingOn, delegateOrLambdaNode);
}
private void ReportDiagnosticIfSymbolIsExternalTask(SyntaxNodeAnalysisContext context, ExpressionSyntax expressionSyntax, IdentifierNameSyntax identifierNameToConsider, SyntaxNode delegateOrLambdaNode)
private Diagnostic AnalyzeAwaitedOrReturnedExpression(ExpressionSyntax expressionSyntax, SemanticModel semanticModel, CancellationToken cancellationToken)
{
// Step 3: Is the symbol we are waiting on a System.Threading.Tasks.Task
SymbolInfo symbolToConsider = context.SemanticModel.GetSymbolInfo(identifierNameToConsider);
if (expressionSyntax == null)
{
return null;
}
if (semanticModel == null)
{
throw new ArgumentNullException(nameof(semanticModel));
}
SymbolInfo symbolToConsider = semanticModel.GetSymbolInfo(expressionSyntax, cancellationToken);
ITypeSymbol symbolType;
bool dataflowAnalysisCompatibleVariable = false;
switch (symbolToConsider.Symbol)
{
case ILocalSymbol localSymbol:
symbolType = localSymbol.Type;
dataflowAnalysisCompatibleVariable = true;
break;
case IParameterSymbol parameterSymbol:
symbolType = parameterSymbol.Type;
dataflowAnalysisCompatibleVariable = true;
break;
case IFieldSymbol fieldSymbol:
symbolType = fieldSymbol.Type;
break;
case IMethodSymbol methodSymbol:
if (Utils.IsTask(methodSymbol.ReturnType) && expressionSyntax is InvocationExpressionSyntax invocationExpressionSyntax)
{
// Consider all arguments
var expressionsToConsider = invocationExpressionSyntax.ArgumentList.Arguments.Select(a => a.Expression);
// Consider the implicit first argument when this method is invoked as an extension method.
if (methodSymbol.IsExtensionMethod && invocationExpressionSyntax.Expression is MemberAccessExpressionSyntax invokedMember)
{
if (!methodSymbol.ContainingType.Equals(semanticModel.GetSymbolInfo(invokedMember.Expression, cancellationToken).Symbol))
{
expressionsToConsider = new ExpressionSyntax[] { invokedMember.Expression }.Concat(expressionsToConsider);
}
}
return expressionsToConsider.Select(e => this.AnalyzeAwaitedOrReturnedExpression(e, semanticModel, cancellationToken)).FirstOrDefault(r => r != null);
}
return null;
default:
return;
return null;
}
if (symbolType == null || symbolType.Name != nameof(Task) || !symbolType.BelongsToNamespace(Namespaces.SystemThreadingTasks))
if (symbolType?.Name != nameof(Task) || !symbolType.BelongsToNamespace(Namespaces.SystemThreadingTasks))
{
return;
return null;
}
// Step 4: Report warning if the task was not initialized within the current delegate or lambda expression
CSharpSyntaxNode delegateBlockOrBlock = this.GetBlockOrExpressionBodyOfDelegateOrLambdaExpression(delegateOrLambdaNode);
if (delegateBlockOrBlock is BlockSyntax delegateBlock)
// Report warning if the task was not initialized within the current delegate or lambda expression
var containingFunc = Utils.GetContainingFunction(expressionSyntax);
if (containingFunc.BlockOrExpression is BlockSyntax delegateBlock)
{
// Run data flow analysis to understand where the task was defined
DataFlowAnalysis dataFlowAnalysis;
// When possible (await is direct child of the block), execute data flow analysis by passing first and last statement to capture only what happens before the await
// Check if the await is direct child of the code block (first parent is ExpressionStantement, second parent is the block itself)
if (expressionSyntax.Parent.Parent.Parent.Equals(delegateBlock))
if (dataflowAnalysisCompatibleVariable)
{
dataFlowAnalysis = context.SemanticModel.AnalyzeDataFlow(delegateBlock.ChildNodes().First(), expressionSyntax.Parent.Parent);
// Run data flow analysis to understand where the task was defined
DataFlowAnalysis dataFlowAnalysis;
// When possible (await is direct child of the block and not a field), execute data flow analysis by passing first and last statement to capture only what happens before the await
// Check if the await is direct child of the code block (first parent is ExpressionStantement, second parent is the block itself)
if (delegateBlock.Equals(expressionSyntax.Parent.Parent?.Parent))
{
dataFlowAnalysis = semanticModel.AnalyzeDataFlow(delegateBlock.ChildNodes().First(), expressionSyntax.Parent.Parent);
}
else
{
// Otherwise analyze the data flow for the entire block. One caveat: it doesn't distinguish if the initalization happens after the await.
dataFlowAnalysis = semanticModel.AnalyzeDataFlow(delegateBlock);
}
if (!dataFlowAnalysis.WrittenInside.Contains(symbolToConsider.Symbol))
{
return Diagnostic.Create(Descriptor, expressionSyntax.GetLocation());
}
}
else
{
// Otherwise analyze the data flow for the entire block. One caveat: it doesn't distinguish if the initalization happens after the await.
dataFlowAnalysis = context.SemanticModel.AnalyzeDataFlow(delegateBlock);
}
if (!dataFlowAnalysis.WrittenInside.Contains(symbolToConsider.Symbol))
{
context.ReportDiagnostic(Diagnostic.Create(Descriptor, expressionSyntax.GetLocation()));
// Do the best we can searching for assignment statements.
if (!Utils.IsAssignedWithin(containingFunc.BlockOrExpression, semanticModel, symbolToConsider.Symbol, cancellationToken))
{
return Diagnostic.Create(Descriptor, expressionSyntax.GetLocation());
}
}
}
else
{
// It's not a block, it's just a lambda expression, so the variable must be external.
context.ReportDiagnostic(Diagnostic.Create(Descriptor, expressionSyntax.GetLocation()));
}
}
/// <summary>
/// Finds the async delegate or lambda expression that matches the await by walking up the syntax tree until we encounter an async delegate or lambda expression.
/// </summary>
/// <param name="awaitExpressionSyntax">The await expression syntax.</param>
/// <returns>Node representing the delegate or lambda expression if found. Null if not found.</returns>
private SyntaxNode FindAsyncDelegateOrLambdaExpressiomMatchingAwait(AwaitExpressionSyntax awaitExpressionSyntax)
{
SyntaxNode currentNode = awaitExpressionSyntax;
while (currentNode != null && !(currentNode is MethodDeclarationSyntax))
{
AnonymousMethodExpressionSyntax anonymousMethod = currentNode as AnonymousMethodExpressionSyntax;
if (anonymousMethod != null && anonymousMethod.AsyncKeyword != null)
{
return currentNode;
}
if ((currentNode as ParenthesizedLambdaExpressionSyntax)?.AsyncKeyword != null)
{
return currentNode;
}
// Advance to the next parent
currentNode = currentNode.Parent;
return Diagnostic.Create(Descriptor, expressionSyntax.GetLocation());
}
return null;
}
/// <summary>
/// Helper method to get the code Block of a delegate or lambda expression.
/// </summary>
/// <param name="delegateOrLambdaExpression">The delegate or lambda expression.</param>
/// <returns>The code block.</returns>
private CSharpSyntaxNode GetBlockOrExpressionBodyOfDelegateOrLambdaExpression(SyntaxNode delegateOrLambdaExpression)
{
if (delegateOrLambdaExpression is AnonymousMethodExpressionSyntax anonymousMethod)
{
return anonymousMethod.Block;
}
if (delegateOrLambdaExpression is ParenthesizedLambdaExpressionSyntax lambdaExpression)
{
return lambdaExpression.Body;
}
throw new ArgumentException("Must be of type AnonymousMethodExpressionSyntax or ParenthesizedLambdaExpressionSyntax", nameof(delegateOrLambdaExpression));
}
/// <summary>
/// Walks up the syntax tree to find out where the specified delegate or lambda expression is being invoked.
/// </summary>
/// <param name="delegateOrLambdaExpression">Node representing a delegate or lambda expression.</param>
/// <returns>The invocation expression. Null if not found.</returns>
private InvocationExpressionSyntax FindInvocationOfDelegateOrLambdaExpression(SyntaxNode delegateOrLambdaExpression)
{
SyntaxNode currentNode = delegateOrLambdaExpression;
while (currentNode != null && !(currentNode is MethodDeclarationSyntax))
{
if (currentNode is InvocationExpressionSyntax invocationExpressionSyntax)
{
return invocationExpressionSyntax;
}
// Advance to the next parent
currentNode = currentNode.Parent;
}
return null;
}
/// <summary>
/// Checks whether the specified invocation is a call to JoinableTaskFactory.Run or RunAsync
/// </summary>
/// <param name="context">The analysis context.</param>
/// <param name="invocationExpressionSyntax">The invocation to check for.</param>
/// <returns>True if the specified invocation is a call to JoinableTaskFactory.Run or RunAsyn</returns>
private bool IsInvocationExpressionACallToJtfRun(SyntaxNodeAnalysisContext context, InvocationExpressionSyntax invocationExpressionSyntax)
{
if (invocationExpressionSyntax.Expression is MemberAccessExpressionSyntax memberAccessExpressionSyntax)
{
// Check if we encountered a call to Run and had already encountered a delegate (so Run is a parent of the delegate)
string methodName = memberAccessExpressionSyntax.Name.Identifier.Text;
if (methodName == Types.JoinableTaskFactory.Run || methodName == Types.JoinableTaskFactory.RunAsync)
{
// Check whether the Run method belongs to JTF
IMethodSymbol methodSymbol = context.SemanticModel.GetSymbolInfo(memberAccessExpressionSyntax).Symbol as IMethodSymbol;
if (methodSymbol?.ContainingType != null &&
methodSymbol.ContainingType.Name == Types.JoinableTaskFactory.TypeName &&
methodSymbol.ContainingType.BelongsToNamespace(Types.JoinableTaskFactory.Namespace))
{
return true;
}
}
}
return false;
}
}
}