6
6
using Microsoft . CodeAnalysis . CSharp . Syntax ;
7
7
using Microsoft . CodeAnalysis . Diagnostics ;
8
8
using Microsoft . CodeAnalysis . Operations ;
9
+ using System . Collections . Generic ;
10
+ using System ;
9
11
10
12
namespace AsyncFixer . UnnecessaryAsync
11
13
{
@@ -63,7 +65,7 @@ private void AnalyzeMethodDeclaration(SyntaxNodeAnalysisContext context)
63
65
// Retrieve all await expressions excluding the ones under lambda functions.
64
66
var awaitExpressions = node . DescendantNodes ( ) . OfType < AwaitExpressionSyntax > ( ) . Where ( a => a . FirstAncestorOrSelfUnderGivenNode < LambdaExpressionSyntax > ( node ) == null ) . ToList ( ) ;
65
67
66
- if ( node . Body == null &&
68
+ if ( node . Body == null &&
67
69
node . ExpressionBody ? . Expression . Kind ( ) == SyntaxKind . AwaitExpression )
68
70
{
69
71
// Expression-bodied members
@@ -96,6 +98,13 @@ private void AnalyzeMethodDeclaration(SyntaxNodeAnalysisContext context)
96
98
var controlFlow = context . SemanticModel . AnalyzeControlFlow ( node . Body ) ;
97
99
var returnStatements = controlFlow ? . ReturnStatements ?? ImmutableArray < SyntaxNode > . Empty ;
98
100
101
+ // Retrieve the disposable object identifiers from the using statements.
102
+ // For instance, for the following statement, we'd like to return 'source'.
103
+ // using FileStream source = File.Open("data", FileMode.Open);
104
+ var disposableObjectNames = node . Body . DescendantNodes ( ) . OfType < LocalDeclarationStatementSyntax > ( )
105
+ . Where ( a => a . UsingKeyword . Kind ( ) != SyntaxKind . None )
106
+ . SelectMany ( a => a . DescendantNodes ( ) . OfType < VariableDeclaratorSyntax > ( ) . Select ( b => b . Identifier . ValueText ) ) . ToList ( ) ;
107
+
99
108
var numAwait = 0 ;
100
109
if ( returnStatements . Any ( ) )
101
110
{
@@ -107,7 +116,7 @@ private void AnalyzeMethodDeclaration(SyntaxNodeAnalysisContext context)
107
116
return ;
108
117
}
109
118
110
- if ( HasUsingOrTryParent ( returnStatement , node ) )
119
+ if ( ! IsSafeToRemoveAsyncAwait ( returnStatement ) )
111
120
{
112
121
return ;
113
122
}
@@ -125,13 +134,18 @@ private void AnalyzeMethodDeclaration(SyntaxNodeAnalysisContext context)
125
134
else
126
135
{
127
136
// if awaitExpression is the last statement's expression
137
+ // using (var stream = new MemoryStream())
138
+ // {
139
+ // await Task.FromResult(3); ==> this is NOT the last statement because of the using block.
140
+ // }
141
+
128
142
var lastStatement = node . Body . Statements . LastOrDefault ( ) ;
129
143
if ( ( lastStatement as ExpressionStatementSyntax ) ? . Expression ? . Kind ( ) != SyntaxKind . AwaitExpression )
130
144
{
131
145
return ;
132
146
}
133
147
134
- if ( HasUsingOrTryParent ( lastStatement , node ) )
148
+ if ( ! IsSafeToRemoveAsyncAwait ( lastStatement ) )
135
149
{
136
150
return ;
137
151
}
@@ -144,26 +158,83 @@ private void AnalyzeMethodDeclaration(SyntaxNodeAnalysisContext context)
144
158
return ;
145
159
}
146
160
147
- // Make sure that we do not give a warning about the await statement involving a disposable object.
161
+ context . ReportDiagnostic ( diagnostic ) ;
148
162
149
- // Retrieve the disposable object identifiers from the using statements.
150
- // For instance, for the following statement, we'd like to return 'source'.
151
- // using FileStream source = File.Open("data", FileMode.Open);
152
- var disposableObjectNames = node . Body . DescendantNodes ( ) . OfType < LocalDeclarationStatementSyntax > ( )
153
- . Where ( a => a . UsingKeyword . Kind ( ) != SyntaxKind . None )
154
- . SelectMany ( a => a . DescendantNodes ( ) . OfType < VariableDeclaratorSyntax > ( ) . Select ( b => b . Identifier . ValueText ) ) ;
155
- if ( disposableObjectNames . Any ( ) )
163
+ bool IsSafeToRemoveAsyncAwait ( StatementSyntax statement )
156
164
{
157
- // There are disposable objects.
158
- // Let's check whether at least one await expression uses one of those disposable objects.
159
- if ( awaitExpressions . SelectMany ( a => a . DescendantNodes ( ) . OfType < IdentifierNameSyntax > ( ) )
160
- . Any ( a => disposableObjectNames . Contains ( a . Identifier . ValueText ) ) )
165
+ if ( HasUsingOrTryParent ( statement , node ) )
161
166
{
162
- return ;
167
+ // If it is under 'using' or 'try' block, it is not safe to remove async/await.
168
+ return false ;
169
+ }
170
+
171
+ // Make sure that we do not give a warning about the await statement involving an object which is created by an using statement.
172
+ // We use dataflow analysis to accurately detect a case like below:
173
+ // async Task foo()
174
+ // {
175
+ // using var stream = new MemoryStream();
176
+ // int streamOperation()
177
+ // {
178
+ // return stream.Read(null);
179
+ // }
180
+ //
181
+ // var t = Task.Run(() => streamOperation())
182
+ // await t;
183
+ // }
184
+ // In the example above, we need to find out whether 'stream' is accessed in the last statement.
185
+
186
+ var names = GetAccessedVariableNamesWithPointsToAnalysis ( context . SemanticModel , node , statement ) . ToList ( ) ;
187
+
188
+ if ( names . Any ( a => disposableObjectNames . Contains ( a ) ) )
189
+ {
190
+ return false ;
163
191
}
192
+
193
+ return true ;
164
194
}
195
+ }
165
196
166
- context . ReportDiagnostic ( diagnostic ) ;
197
+ /// <summary>
198
+ /// Return the names of all variables that are read-accessed in the given statement.
199
+ /// </summary>
200
+ /// <remarks>
201
+ /// The method iteratively goes through the definitions to find implicitly-accessed variables.
202
+ /// </remarks>
203
+ private IEnumerable < string > GetAccessedVariableNamesWithPointsToAnalysis ( SemanticModel semanticModel , SyntaxNode root , SyntaxNode node , int depth = 0 )
204
+ {
205
+ if ( depth == 5 || node == null || root == null )
206
+ {
207
+ // Put a limit for the call stack frame
208
+ yield break ;
209
+ }
210
+
211
+ var dataFlowResult = semanticModel . AnalyzeDataFlow ( node ) ;
212
+ if ( dataFlowResult ? . Succeeded == true )
213
+ {
214
+ foreach ( ISymbol symbol in dataFlowResult . ReadInside )
215
+ {
216
+ yield return symbol . Name ;
217
+
218
+ if ( symbol . DeclaringSyntaxReferences == null )
219
+ {
220
+ continue ;
221
+ }
222
+
223
+ foreach ( var syntaxRef in symbol . DeclaringSyntaxReferences )
224
+ {
225
+ var expressions = root . FindNode ( syntaxRef . Span , getInnermostNodeForTie : true ) . DescendantNodes ( ( n ) => ! ( n is ExpressionSyntax ) ) . OfType < ExpressionSyntax > ( ) ;
226
+
227
+ foreach ( var expr in expressions )
228
+ {
229
+ var names = GetAccessedVariableNamesWithPointsToAnalysis ( semanticModel , root , expr , depth + 1 ) ;
230
+ foreach ( var name in names )
231
+ {
232
+ yield return name ;
233
+ }
234
+ }
235
+ }
236
+ }
237
+ }
167
238
}
168
239
}
169
240
}
0 commit comments