Skip to content

Commit 7c506f4

Browse files
committed
C#: Extract underlying methods of foreach statements
1 parent 7859c52 commit 7c506f4

14 files changed

Lines changed: 235 additions & 41 deletions

File tree

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
lgtm,codescanning
2+
* The underlying methods of `foreach` statements are now explicitly extracted and
3+
they are made available on the `ForeachStmt` class.

csharp/extractor/Semmle.Extraction.CSharp/Entities/Statements/ForEach.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,28 @@ public static ForEach Create(Context cx, ForEachStatementSyntax node, IStatement
1818
return ret;
1919
}
2020

21-
protected override void PopulateStatement(TextWriter _)
21+
protected override void PopulateStatement(TextWriter trapFile)
2222
{
2323
Expression.Create(cx, Stmt.Expression, this, 1);
2424

25-
var typeSymbol = cx.GetModel(Stmt).GetDeclaredSymbol(Stmt);
25+
var semanticModel = cx.GetModel(Stmt);
26+
var typeSymbol = semanticModel.GetDeclaredSymbol(Stmt);
2627
var type = typeSymbol.GetAnnotatedType();
2728

2829
var location = cx.Create(Stmt.Identifier.GetLocation());
2930

3031
Expressions.VariableDeclaration.Create(cx, typeSymbol, type, Stmt.Type, location, Stmt.Type.IsVar, this, 0);
3132

3233
Statement.Create(cx, Stmt.Statement, this, 2);
34+
35+
var info = semanticModel.GetForEachStatementInfo(Stmt);
36+
var getEnumerator = Method.Create(cx, info.GetEnumeratorMethod);
37+
var currentProp = Property.Create(cx, info.CurrentProperty);
38+
var moveNext = Method.Create(cx, info.MoveNextMethod);
39+
var dispose = Method.Create(cx, info.DisposeMethod);
40+
var elementType = Type.Create(cx, info.ElementType);
41+
42+
trapFile.foreach_stmt_info(this, elementType, getEnumerator, moveNext, dispose, currentProp, info.IsAsynchronous);
3343
}
3444
}
3545

csharp/extractor/Semmle.Extraction.CSharp/Tuples.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@ internal static void catch_type(this TextWriter trapFile, Entities.Statements.Ca
5353
trapFile.WriteTuple("catch_type", @catch, type, explicityCaught ? 1 : 2);
5454
}
5555

56+
internal static void foreach_stmt_info(this TextWriter trapFile, Entities.Statements.ForEach @foreach, Type element, Method getEnumerator,
57+
Method moveNext, Method dispose, Property current, bool isAsync)
58+
{
59+
trapFile.WriteTuple("foreach_stmt_info", @foreach, element, getEnumerator, moveNext, dispose, current, isAsync ? 2 : 1);
60+
}
61+
5662
internal static void commentblock(this TextWriter trapFile, CommentBlock k)
5763
{
5864
trapFile.WriteTuple("commentblock", k);

csharp/ql/src/Dead Code/DeadCode.qll

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ Expr getAMethodAccess(Method m) {
4343
predicate potentiallyAccessedByForEach(Method m) {
4444
m.hasName("GetEnumerator") and
4545
m.getDeclaringType().getABaseType+().hasQualifiedName("System.Collections.IEnumerable")
46+
or
47+
foreach_stmt_info(_, _, m, _, _, _, _)
4648
}
4749

4850
predicate isRecursivelyLiveExpression(Expr e) {

csharp/ql/src/experimental/ir/implementation/raw/internal/desugar/Foreach.qll

Lines changed: 5 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -171,11 +171,7 @@ private class TranslatedForeachMoveNext extends TranslatedCompilerGeneratedCall,
171171

172172
override Callable getInstructionFunction(InstructionTag tag) {
173173
tag = CallTargetTag() and
174-
exists(Callable internal |
175-
internal.getName() = "MoveNext" and
176-
internal.getReturnType() instanceof BoolType and
177-
result = internal
178-
)
174+
result = generatedBy.getMoveNext()
179175
}
180176

181177
override Type getCallResultType() { result instanceof BoolType }
@@ -205,28 +201,9 @@ private class TranslatedForeachGetEnumerator extends TranslatedCompilerGenerated
205201
result = getInstructionFunction(CallTargetTag()).getReturnType()
206202
}
207203

208-
private Callable getEnumeratorCallable() {
209-
if exists(generatedBy.getIterableExpr().getType().(ValueOrRefType).getAMember("GetEnumerator"))
210-
then
211-
result = generatedBy.getIterableExpr().getType().(ValueOrRefType).getAMember("GetEnumerator")
212-
else
213-
exists(Interface inter |
214-
inter =
215-
generatedBy
216-
.getIterableExpr()
217-
.getType()
218-
.(ValueOrRefType)
219-
// There could be some abstract base types until we reach `IEnumerable` (eg. `Array`)
220-
.getABaseType*()
221-
.getABaseInterface() and
222-
inter.getName() = "IEnumerable" and
223-
result = inter.getAMember("GetEnumerator")
224-
)
225-
}
226-
227204
override Callable getInstructionFunction(InstructionTag tag) {
228205
tag = CallTargetTag() and
229-
result = getEnumeratorCallable()
206+
result = generatedBy.getGetEnumerator()
230207
}
231208

232209
override TranslatedExpr getArgument(int id) { none() }
@@ -247,7 +224,7 @@ private class TranslatedForeachCurrent extends TranslatedCompilerGeneratedCall,
247224

248225
TranslatedForeachCurrent() { this = TTranslatedCompilerGeneratedElement(generatedBy, 5) }
249226

250-
override Type getCallResultType() { result = generatedBy.getAVariable().getType() }
227+
override Type getCallResultType() { result = generatedBy.getElementType() }
251228

252229
override TranslatedExpr getArgument(int id) { none() }
253230

@@ -262,10 +239,7 @@ private class TranslatedForeachCurrent extends TranslatedCompilerGeneratedCall,
262239

263240
override Callable getInstructionFunction(InstructionTag tag) {
264241
tag = CallTargetTag() and
265-
exists(Property prop |
266-
prop.getName() = "Current" and
267-
result = prop.getGetter()
268-
)
242+
result = generatedBy.getCurrent().getGetter()
269243
}
270244
}
271245

@@ -280,10 +254,7 @@ private class TranslatedForeachDispose extends TranslatedCompilerGeneratedCall,
280254

281255
override Callable getInstructionFunction(InstructionTag tag) {
282256
tag = CallTargetTag() and
283-
exists(Callable dispose |
284-
dispose.getName() = "Dispose" and
285-
result = dispose
286-
)
257+
result = generatedBy.getDispose()
287258
}
288259

289260
final override Type getCallResultType() { result instanceof VoidType }

csharp/ql/src/semmle/code/csharp/Stmt.qll

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -582,6 +582,27 @@ class ForeachStmt extends LoopStmt, @foreach_stmt {
582582
*/
583583
Expr getIterableExpr() { result = this.getChild(1) }
584584

585+
/** Gets the called `GetEnumerator` method. */
586+
Method getGetEnumerator() { foreach_stmt_info(this, _, result, _, _, _, _) }
587+
588+
/** Gets the called `MoveNext` or `MoveNextAsync` method. */
589+
Method getMoveNext() { foreach_stmt_info(this, _, _, result, _, _, _) }
590+
591+
/** Gets the called `Dispose` or `DisposeAsync` method. */
592+
Method getDispose() { foreach_stmt_info(this, _, _, _, result, _, _) }
593+
594+
/** Gets the called `Current` property. */
595+
Property getCurrent() { foreach_stmt_info(this, _, _, _, _, result, _) }
596+
597+
/**
598+
* Gets the intermediate type to which the `Current` property is converted before
599+
* being converted to the iteration variable type.
600+
*/
601+
Type getElementType() { foreach_stmt_info(this, result, _, _, _, _, _) }
602+
603+
/** Holds if this `foreach` statement is asynchronous. */
604+
predicate isAsync() { foreach_stmt_info(this, _, _, _, _, _, 2) }
605+
585606
override string toString() { result = "foreach (... ... in ...) ..." }
586607

587608
override string getAPrimaryQlClass() { result = "ForeachStmt" }

csharp/ql/src/semmlecode.csharp.dbscheme

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -982,6 +982,15 @@ catch_type(
982982
int type_id: @type_or_ref ref,
983983
int kind: int ref /* explicit = 1, implicit = 2 */);
984984

985+
foreach_stmt_info(
986+
unique int id: @foreach_stmt ref,
987+
int element_type_id: @type_or_ref ref,
988+
int getenumerator_id: @method ref,
989+
int movenext_id: @method ref,
990+
int dispose_id: @method ref,
991+
int current_id: @property ref,
992+
int kind: int ref /* non-async = 1, async = 2 */);
993+
985994
/** EXPRESSIONS **/
986995

987996
expressions(
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Threading.Tasks;
5+
6+
static class Extensions
7+
{
8+
public static IEnumerator<T> GetEnumerator<T>(this IEnumerator<T> enumerator) => enumerator;
9+
public static IAsyncEnumerator<T> GetAsyncEnumerator<T>(this IAsyncEnumerator<T> enumerator) => enumerator;
10+
public static IEnumerator<int> GetEnumerator(this int count)
11+
{
12+
for (int i = 0; i < count; i++)
13+
{
14+
yield return i;
15+
}
16+
}
17+
}
18+
19+
class Program
20+
{
21+
async Task Main()
22+
{
23+
IEnumerator<int> enumerator1 = Enumerable.Range(0, 10).GetEnumerator();
24+
foreach (var item in enumerator1)
25+
{
26+
}
27+
28+
IAsyncEnumerator<int> enumerator2 = GetAsyncEnumerator();
29+
await foreach (var item in enumerator2)
30+
{
31+
}
32+
33+
foreach (var item in 42)
34+
{
35+
}
36+
37+
foreach (var i in new int[] { 1, 2, 3 }) // not extension
38+
{
39+
}
40+
}
41+
42+
static async IAsyncEnumerator<int> GetAsyncEnumerator()
43+
{
44+
yield return 0;
45+
await Task.Delay(1);
46+
yield return 1;
47+
}
48+
}
49+
50+
// semmle-extractor-options: /r:System.Linq.dll

csharp/ql/test/library-tests/csharp9/PrintAst.expected

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,109 @@ Discard.cs:
166166
# 10| 4: [BlockStmt] {...}
167167
# 10| 0: [ReturnStmt] return ...;
168168
# 10| 0: [IntLiteral] 0
169+
ForeachExtension.cs:
170+
# 6| [Class] Extensions
171+
# 8| 4: [ExtensionMethod] GetEnumerator
172+
# 8| -1: [TypeMention] IEnumerator<T>
173+
# 8| 1: [TypeMention] T
174+
#-----| 1: (Type parameters)
175+
# 8| 0: [TypeParameter] T
176+
#-----| 2: (Parameters)
177+
# 8| 0: [Parameter] enumerator
178+
# 8| -1: [TypeMention] IEnumerator<T>
179+
# 8| 1: [TypeMention] T
180+
# 8| 4: [ParameterAccess] access to parameter enumerator
181+
# 9| 6: [ExtensionMethod] GetAsyncEnumerator
182+
# 9| -1: [TypeMention] IAsyncEnumerator<T>
183+
# 9| 1: [TypeMention] T
184+
#-----| 1: (Type parameters)
185+
# 9| 0: [TypeParameter] T
186+
#-----| 2: (Parameters)
187+
# 9| 0: [Parameter] enumerator
188+
# 9| -1: [TypeMention] IAsyncEnumerator<T>
189+
# 9| 1: [TypeMention] T
190+
# 9| 4: [ParameterAccess] access to parameter enumerator
191+
# 10| 8: [ExtensionMethod] GetEnumerator
192+
# 10| -1: [TypeMention] IEnumerator<Int32>
193+
# 10| 1: [TypeMention] int
194+
#-----| 2: (Parameters)
195+
# 10| 0: [Parameter] count
196+
# 10| -1: [TypeMention] int
197+
# 11| 4: [BlockStmt] {...}
198+
# 12| 0: [ForStmt] for (...;...;...) ...
199+
# 12| -1: [LocalVariableDeclAndInitExpr] Int32 i = ...
200+
# 12| -1: [TypeMention] int
201+
# 12| 0: [LocalVariableAccess] access to local variable i
202+
# 12| 1: [IntLiteral] 0
203+
# 12| 0: [LTExpr] ... < ...
204+
# 12| 0: [LocalVariableAccess] access to local variable i
205+
# 12| 1: [ParameterAccess] access to parameter count
206+
# 12| 1: [PostIncrExpr] ...++
207+
# 12| 0: [LocalVariableAccess] access to local variable i
208+
# 13| 2: [BlockStmt] {...}
209+
# 14| 0: [YieldReturnStmt] yield return ...;
210+
# 14| 0: [LocalVariableAccess] access to local variable i
211+
# 19| [Class] Program
212+
# 21| 5: [Method] Main
213+
# 21| -1: [TypeMention] Task
214+
# 22| 4: [BlockStmt] {...}
215+
# 23| 0: [LocalVariableDeclStmt] ... ...;
216+
# 23| 0: [LocalVariableDeclAndInitExpr] IEnumerator<Int32> enumerator1 = ...
217+
# 23| -1: [TypeMention] IEnumerator<Int32>
218+
# 23| 1: [TypeMention] int
219+
# 23| 0: [LocalVariableAccess] access to local variable enumerator1
220+
# 23| 1: [MethodCall] call to method GetEnumerator
221+
# 23| -1: [MethodCall] call to method Range
222+
# 23| -1: [TypeAccess] access to type Enumerable
223+
# 23| 0: [TypeMention] Enumerable
224+
# 23| 0: [IntLiteral] 0
225+
# 23| 1: [IntLiteral] 10
226+
# 24| 1: [ForeachStmt] foreach (... ... in ...) ...
227+
# 24| 0: [LocalVariableDeclExpr] Int32 item
228+
# 24| 0: [TypeMention] int
229+
# 24| 1: [LocalVariableAccess] access to local variable enumerator1
230+
# 25| 2: [BlockStmt] {...}
231+
# 28| 2: [LocalVariableDeclStmt] ... ...;
232+
# 28| 0: [LocalVariableDeclAndInitExpr] IAsyncEnumerator<Int32> enumerator2 = ...
233+
# 28| -1: [TypeMention] IAsyncEnumerator<Int32>
234+
# 28| 1: [TypeMention] int
235+
# 28| 0: [LocalVariableAccess] access to local variable enumerator2
236+
# 28| 1: [MethodCall] call to method GetAsyncEnumerator
237+
# 29| 3: [ForeachStmt] foreach (... ... in ...) ...
238+
# 29| 0: [LocalVariableDeclExpr] Int32 item
239+
# 29| 0: [TypeMention] int
240+
# 29| 1: [LocalVariableAccess] access to local variable enumerator2
241+
# 30| 2: [BlockStmt] {...}
242+
# 33| 4: [ForeachStmt] foreach (... ... in ...) ...
243+
# 33| 0: [LocalVariableDeclExpr] Int32 item
244+
# 33| 0: [TypeMention] int
245+
# 33| 1: [IntLiteral] 42
246+
# 34| 2: [BlockStmt] {...}
247+
# 37| 5: [ForeachStmt] foreach (... ... in ...) ...
248+
# 37| 0: [LocalVariableDeclExpr] Int32 i
249+
# 37| 0: [TypeMention] int
250+
# 37| 1: [ArrayCreation] array creation of type Int32[]
251+
# 37| -2: [TypeMention] Int32[]
252+
# 37| 1: [TypeMention] int
253+
# 37| -1: [ArrayInitializer] { ..., ... }
254+
# 37| 0: [IntLiteral] 1
255+
# 37| 1: [IntLiteral] 2
256+
# 37| 2: [IntLiteral] 3
257+
# 38| 2: [BlockStmt] {...}
258+
# 42| 6: [Method] GetAsyncEnumerator
259+
# 42| -1: [TypeMention] IAsyncEnumerator<Int32>
260+
# 42| 1: [TypeMention] int
261+
# 43| 4: [BlockStmt] {...}
262+
# 44| 0: [YieldReturnStmt] yield return ...;
263+
# 44| 0: [IntLiteral] 0
264+
# 45| 1: [ExprStmt] ...;
265+
# 45| 0: [AwaitExpr] await ...
266+
# 45| 0: [MethodCall] call to method Delay
267+
# 45| -1: [TypeAccess] access to type Task
268+
# 45| 0: [TypeMention] Task
269+
# 45| 0: [IntLiteral] 1
270+
# 46| 2: [YieldReturnStmt] yield return ...;
271+
# 46| 0: [IntLiteral] 1
169272
FunctionPointer.cs:
170273
# 5| [Class] FnPointer
171274
# 7| 5: [Class] Program
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
| ForeachExtension.cs:24:9:26:9 | foreach (... ... in ...) ... | Int32 | sync | Extensions | ForeachExtension.cs:8:34:8:49 | System.Collections.Generic.IEnumerator<Int32> | - | System.Collections.IEnumerator | - |
2+
| ForeachExtension.cs:29:9:31:9 | foreach (... ... in ...) ... | Int32 | async | Extensions | ForeachExtension.cs:9:39:9:59 | System.Collections.Generic.IAsyncEnumerator<Int32> | - | System.Collections.Generic.IAsyncEnumerator<Int32> | - |
3+
| ForeachExtension.cs:33:9:35:9 | foreach (... ... in ...) ... | Int32 | sync | Extensions | ForeachExtension.cs:10:36:10:48 | System.Collections.Generic.IEnumerator<Int32> | - | System.Collections.IEnumerator | - |
4+
| ForeachExtension.cs:37:9:39:9 | foreach (... ... in ...) ... | Int32 | sync | System.Collections.IEnumerable | - | System.Collections.IEnumerator | - | System.Collections.IEnumerator | - |

0 commit comments

Comments
 (0)