Skip to content

Commit 77af7ed

Browse files
authored
Merge pull request github#4628 from tamasvajk/feature/csharp9-foreach
C#: Extract underlying methods of foreach statements
2 parents 4fa33b1 + 0aded15 commit 77af7ed

File tree

20 files changed

+11705
-6819
lines changed

20 files changed

+11705
-6819
lines changed
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: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,15 @@ namespace Semmle.Extraction.CSharp.Entities.Statements
88
{
99
internal class ForEach : Statement<ForEachStatementSyntax>
1010
{
11+
internal enum ForeachSymbolType
12+
{
13+
GetEnumeratorMethod = 1,
14+
CurrentProperty,
15+
MoveNextMethod,
16+
DisposeMethod,
17+
ElementType
18+
}
19+
1120
private ForEach(Context cx, ForEachStatementSyntax stmt, IStatementParentEntity parent, int child)
1221
: base(cx, stmt, StmtKind.FOREACH, parent, child) { }
1322

@@ -18,18 +27,59 @@ public static ForEach Create(Context cx, ForEachStatementSyntax node, IStatement
1827
return ret;
1928
}
2029

21-
protected override void PopulateStatement(TextWriter _)
30+
protected override void PopulateStatement(TextWriter trapFile)
2231
{
2332
Expression.Create(cx, Stmt.Expression, this, 1);
2433

25-
var typeSymbol = cx.GetModel(Stmt).GetDeclaredSymbol(Stmt);
34+
var semanticModel = cx.GetModel(Stmt);
35+
var typeSymbol = semanticModel.GetDeclaredSymbol(Stmt);
2636
var type = typeSymbol.GetAnnotatedType();
2737

2838
var location = cx.Create(Stmt.Identifier.GetLocation());
2939

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

3242
Statement.Create(cx, Stmt.Statement, this, 2);
43+
44+
var info = semanticModel.GetForEachStatementInfo(Stmt);
45+
46+
if (info.Equals(default))
47+
{
48+
cx.ExtractionError("Could not get foreach statement info", null, Location.Create(cx, this.ReportingLocation), severity: Util.Logging.Severity.Info);
49+
return;
50+
}
51+
52+
trapFile.foreach_stmt_info(this, info.IsAsynchronous);
53+
54+
if (info.GetEnumeratorMethod != null)
55+
{
56+
var m = Method.Create(cx, info.GetEnumeratorMethod);
57+
trapFile.foreach_stmt_desugar(this, m, ForeachSymbolType.GetEnumeratorMethod);
58+
}
59+
60+
if (info.MoveNextMethod != null)
61+
{
62+
var m = Method.Create(cx, info.MoveNextMethod);
63+
trapFile.foreach_stmt_desugar(this, m, ForeachSymbolType.MoveNextMethod);
64+
}
65+
66+
if (info.DisposeMethod != null)
67+
{
68+
var m = Method.Create(cx, info.DisposeMethod);
69+
trapFile.foreach_stmt_desugar(this, m, ForeachSymbolType.DisposeMethod);
70+
}
71+
72+
if (info.CurrentProperty != null)
73+
{
74+
var p = Property.Create(cx, info.CurrentProperty);
75+
trapFile.foreach_stmt_desugar(this, p, ForeachSymbolType.CurrentProperty);
76+
}
77+
78+
if (info.ElementType != null)
79+
{
80+
var t = Type.Create(cx, info.ElementType);
81+
trapFile.foreach_stmt_desugar(this, t, ForeachSymbolType.ElementType);
82+
}
3383
}
3484
}
3585

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,17 @@ 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, bool isAsync)
57+
{
58+
trapFile.WriteTuple("foreach_stmt_info", @foreach, isAsync ? 2 : 1);
59+
}
60+
61+
internal static void foreach_stmt_desugar(this TextWriter trapFile, Entities.Statements.ForEach @foreach, IEntity entity,
62+
Entities.Statements.ForEach.ForeachSymbolType type)
63+
{
64+
trapFile.WriteTuple("foreach_stmt_desugar", @foreach, entity, (int)type);
65+
}
66+
5667
internal static void commentblock(this TextWriter trapFile, CommentBlock k)
5768
{
5869
trapFile.WriteTuple("commentblock", k);

csharp/extractor/Semmle.Extraction/Entities/File.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public override void Populate(TextWriter trapFile)
6262
}
6363
catch (Exception exc)
6464
{
65-
Context.ExtractionError($"Couldn't read file: {originalPath}", null, null, exc.StackTrace);
65+
Context.ExtractionError($"Couldn't read file: {originalPath}. {exc.Message}", null, null, exc.StackTrace);
6666
}
6767
}
6868

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_desugar(_, m, 1)
4648
}
4749

4850
predicate isRecursivelyLiveExpression(Expr e) {

csharp/ql/src/Likely Bugs/NestedLoopsSameVariable.ql

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,13 @@ class NestedForConditions extends SC::StructuralComparisonConfiguration {
2727
}
2828
}
2929

30+
private predicate hasChild(Stmt outer, Element child) {
31+
outer = child.getParent() and
32+
(outer instanceof ForStmt or outer = any(ForStmt f).getBody())
33+
or
34+
hasChild(outer, child.getParent())
35+
}
36+
3037
/** A nested `for` statement that shares the same iteration variable as an outer `for` statement. */
3138
class NestedForLoopSameVariable extends ForStmt {
3239
ForStmt outer;
@@ -35,7 +42,7 @@ class NestedForLoopSameVariable extends ForStmt {
3542
MutatorOperation outerUpdate;
3643

3744
NestedForLoopSameVariable() {
38-
outer = this.getParent+() and
45+
hasChild(outer, this) and
3946
innerUpdate = this.getAnUpdate() and
4047
outerUpdate = outer.getAnUpdate() and
4148
innerUpdate.getOperand() = iteration.getAnAccess() and
@@ -88,7 +95,7 @@ class NestedForLoopSameVariable extends ForStmt {
8895

8996
/** Finds elements inside the outer loop that are no longer guarded by the loop invariant. */
9097
private ControlFlow::Node getAnUnguardedNode() {
91-
result.getElement().getParent+() = getOuterForStmt().getBody() and
98+
hasChild(getOuterForStmt().getBody(), result.getElement()) and
9299
(
93100
result =
94101
this.getCondition().(ControlFlowElement).getAControlFlowExitNode().getAFalseSuccessor()

csharp/ql/src/Likely Bugs/UncheckedCastInEquals.ql

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,18 @@
1212
import csharp
1313
import semmle.code.csharp.frameworks.System
1414

15+
private predicate equalsMethodChild(EqualsMethod equals, Element child) {
16+
child = equals
17+
or
18+
equalsMethodChild(equals, child.getParent())
19+
}
20+
1521
predicate nodeBeforeParameterAccess(ControlFlow::Node node) {
1622
exists(EqualsMethod equals | equals.getBody() = node.getElement())
1723
or
1824
exists(EqualsMethod equals, Parameter param, ControlFlow::Node mid |
1925
equals.getParameter(0) = param and
20-
equals.getAChild*() = mid.getElement() and
26+
equalsMethodChild(equals, mid.getElement()) and
2127
nodeBeforeParameterAccess(mid) and
2228
not param.getAnAccess() = mid.getElement() and
2329
mid.getASuccessor() = node

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_desugar(this, result, 1) }
587+
588+
/** Gets the called `MoveNext` or `MoveNextAsync` method. */
589+
Method getMoveNext() { foreach_stmt_desugar(this, result, 3) }
590+
591+
/** Gets the called `Dispose` or `DisposeAsync` method, if any. */
592+
Method getDispose() { foreach_stmt_desugar(this, result, 4) }
593+
594+
/** Gets the called `Current` property. */
595+
Property getCurrent() { foreach_stmt_desugar(this, result, 2) }
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_desugar(this, result, 5) }
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: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -982,6 +982,18 @@ 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 kind: int ref /* non-async = 1, async = 2 */);
988+
989+
@foreach_symbol = @method | @property | @type_or_ref;
990+
991+
#keyset[id, kind]
992+
foreach_stmt_desugar(
993+
int id: @foreach_stmt ref,
994+
int symbol: @foreach_symbol ref,
995+
int kind: int ref /* GetEnumeratorMethod = 1, CurrentProperty = 2, MoveNextMethod = 3, DisposeMethod = 4, ElementType = 5 */);
996+
985997
/** EXPRESSIONS **/
986998

987999
expressions(

0 commit comments

Comments
 (0)