@@ -116,28 +116,79 @@ class ForEachStmtEnumerable extends ForEachStmt {
116116 }
117117}
118118
119+ bindingset [ e]
120+ private predicate acceptableForLinqCapture ( Expr e ) {
121+ not exists ( ParameterAccess pa , Parameter p |
122+ p = pa .getTarget ( ) and
123+ pa = e .getAChildExpr * ( )
124+ |
125+ p .isOutOrRef ( ) or p .isIn ( ) or p .isReadonlyRef ( )
126+ )
127+ }
128+
129+ private signature predicate linqCandidateSig ( Stmt s , Expr e ) ;
130+
131+ private module LinqFilterOpportunity< linqCandidateSig / 2 linqCandidate> {
132+ predicate missed ( ForEachStmtGenericEnumerable fes , Stmt s ) {
133+ s = firstStmt ( fes ) and
134+ // The linq candidate expression accesses the loop variable, and the
135+ // candidate doesn't access an in, out, or ref parameter.
136+ exists ( Expr candidate | linqCandidate ( s , candidate ) |
137+ fes .getVariable ( ) .getAnAccess ( ) = candidate .getAChildExpr * ( ) and
138+ acceptableForLinqCapture ( candidate )
139+ )
140+ }
141+ }
142+
143+ private module LinqMapOpportunity< linqCandidateSig / 2 linqCandidate> {
144+ predicate missed ( ForEachStmt fes , Stmt s ) {
145+ s = firstStmt ( fes ) and
146+ // The linq candidate (and only the candidate) expression accesses the loop variable and the
147+ // candidate doesn't access an in, out, or ref parameter.
148+ exists ( Expr candidate | linqCandidate ( s , candidate ) |
149+ forex ( VariableAccess va | va = fes .getVariable ( ) .getAnAccess ( ) |
150+ va = candidate .getAChildExpr * ( )
151+ ) and
152+ acceptableForLinqCapture ( candidate )
153+ )
154+ }
155+ }
156+
157+ private predicate linqAllCandidate ( Stmt s , Expr e ) {
158+ s =
159+ any ( IfStmt is |
160+ e = is .getCondition ( ) and
161+ not exists ( is .getElse ( ) ) and // The then case of the if assigns false to something and breaks out of the loop.
162+ exists ( Assignment a , BoolLiteral bl |
163+ a = is .getThen ( ) .getAChild * ( ) and
164+ bl = a .getRightOperand ( ) and
165+ bl .toString ( ) = "false"
166+ ) and
167+ is .getThen ( ) .getAChild * ( ) instanceof BreakStmt
168+ )
169+ }
170+
119171/**
120172 * Holds if `foreach` statement `fes` could be converted to a `.All()` call.
121173 * That is, the `ForEachStmt` contains a single `if` with a condition that
122174 * accesses the loop variable and with a body that assigns `false` to a variable
123175 * and `break`s out of the `foreach`.
124176 */
125177predicate missedAllOpportunity ( ForEachStmtGenericEnumerable fes ) {
126- exists ( IfStmt is |
127- // The loop contains an if statement with no else case, and nothing else.
128- is = firstStmt ( fes ) and
129- numStmts ( fes ) = 1 and
130- not exists ( is .getElse ( ) ) and
131- // The if statement accesses the loop variable.
132- is .getCondition ( ) .getAChildExpr * ( ) = fes .getVariable ( ) .getAnAccess ( ) and
133- // The then case of the if assigns false to something and breaks out of the loop.
134- exists ( Assignment a , BoolLiteral bl |
135- a = is .getThen ( ) .getAChild * ( ) and
136- bl = a .getRightOperand ( ) and
137- bl .toString ( ) = "false"
138- ) and
139- is .getThen ( ) .getAChild * ( ) instanceof BreakStmt
140- )
178+ // The loop contains an if statement with no else case, and nothing else.
179+ LinqFilterOpportunity< linqAllCandidate / 2 > :: missed ( fes , _) and
180+ numStmts ( fes ) = 1
181+ }
182+
183+ private predicate linqCastCandidate ( Stmt s , Expr e ) {
184+ s =
185+ any ( LocalVariableDeclStmt lvds |
186+ exists ( CastExpr ce |
187+ ce = lvds .getAVariableDeclExpr ( ) .getInitializer ( ) and
188+ e = ce .getExpr ( ) and
189+ e instanceof LocalVariableAccess
190+ )
191+ )
141192}
142193
143194/**
@@ -147,14 +198,18 @@ predicate missedAllOpportunity(ForEachStmtGenericEnumerable fes) {
147198 * local variable declaration statement `s`.
148199 */
149200predicate missedCastOpportunity ( ForEachStmtEnumerable fes , LocalVariableDeclStmt s ) {
150- s = firstStmt ( fes ) and
151- forex ( VariableAccess va | va = fes .getVariable ( ) .getAnAccess ( ) |
152- va = s .getAVariableDeclExpr ( ) .getAChildExpr * ( )
153- ) and
154- exists ( CastExpr ce |
155- ce = s .getAVariableDeclExpr ( ) .getInitializer ( ) and
156- ce .getExpr ( ) = fes .getVariable ( ) .getAnAccess ( )
157- )
201+ LinqMapOpportunity< linqCastCandidate / 2 > :: missed ( fes , s )
202+ }
203+
204+ private predicate linqOfTypeCandidate ( Stmt s , Expr e ) {
205+ s =
206+ any ( LocalVariableDeclStmt lvds |
207+ exists ( AsExpr ae |
208+ ae = lvds .getAVariableDeclExpr ( ) .getInitializer ( ) and
209+ e = ae .getExpr ( ) and
210+ e instanceof LocalVariableAccess
211+ )
212+ )
158213}
159214
160215/**
@@ -164,14 +219,16 @@ predicate missedCastOpportunity(ForEachStmtEnumerable fes, LocalVariableDeclStmt
164219 * is a local variable declaration statement `s`.
165220 */
166221predicate missedOfTypeOpportunity ( ForEachStmtEnumerable fes , LocalVariableDeclStmt s ) {
167- s = firstStmt ( fes ) and
168- forex ( VariableAccess va | va = fes .getVariable ( ) .getAnAccess ( ) |
169- va = s .getAVariableDeclExpr ( ) .getAChildExpr * ( )
170- ) and
171- exists ( AsExpr ae |
172- ae = s .getAVariableDeclExpr ( ) .getInitializer ( ) and
173- ae .getExpr ( ) = fes .getVariable ( ) .getAnAccess ( )
174- )
222+ LinqMapOpportunity< linqOfTypeCandidate / 2 > :: missed ( fes , s )
223+ }
224+
225+ private predicate linqSelectCandidate ( Stmt s , Expr e ) {
226+ s =
227+ any ( LocalVariableDeclStmt lvds |
228+ e = lvds .getAVariableDeclExpr ( ) .getInitializer ( ) and
229+ not e instanceof Cast and
230+ not e .getAChildExpr * ( ) instanceof AwaitExpr
231+ )
175232}
176233
177234/**
@@ -182,12 +239,24 @@ predicate missedOfTypeOpportunity(ForEachStmtEnumerable fes, LocalVariableDeclSt
182239 * contain an `await` expression (since `Select` does not support async lambdas).
183240 */
184241predicate missedSelectOpportunity ( ForEachStmtGenericEnumerable fes , LocalVariableDeclStmt s ) {
185- s = firstStmt ( fes ) and
186- forex ( VariableAccess va | va = fes .getVariable ( ) .getAnAccess ( ) |
187- va = s .getAVariableDeclExpr ( ) .getAChildExpr * ( )
188- ) and
189- not s .getAVariableDeclExpr ( ) .getInitializer ( ) instanceof Cast and
190- not s .getAVariableDeclExpr ( ) .getInitializer ( ) .getAChildExpr * ( ) instanceof AwaitExpr
242+ LinqMapOpportunity< linqSelectCandidate / 2 > :: missed ( fes , s )
243+ }
244+
245+ private predicate linqWhereCandidateCase1 ( Stmt s , Expr e ) {
246+ s =
247+ any ( IfStmt is |
248+ e = is .getCondition ( ) and
249+ is .getThen ( ) instanceof ContinueStmt
250+ )
251+ }
252+
253+ private predicate linqWhereCandidateCase2 ( Stmt s , Expr e ) {
254+ s =
255+ any ( IfStmt is |
256+ e = is .getCondition ( ) and
257+ not exists ( is .getElse ( ) ) and
258+ not terminatesCallable ( is .getThen ( ) )
259+ )
191260}
192261
193262/**
@@ -197,20 +266,21 @@ predicate missedSelectOpportunity(ForEachStmtGenericEnumerable fes, LocalVariabl
197266 * else in the loop than the `if`.
198267 */
199268predicate missedWhereOpportunity ( ForEachStmtGenericEnumerable fes , IfStmt is ) {
200- // The very first thing the foreach loop does is test its iteration variable.
201- is = firstStmt ( fes ) and
202- exists ( VariableAccess va |
203- va .getTarget ( ) = fes .getVariable ( ) and
204- va = is .getCondition ( ) .getAChildExpr * ( )
205- ) and
206- // It then either (a) continues, or (b) performs the entire body of the loop within the condition.
207- (
208- is .getThen ( ) instanceof ContinueStmt
209- or
210- not exists ( is .getElse ( ) ) and
211- numStmts ( fes ) = 1 and
212- not terminatesCallable ( is .getThen ( ) )
213- )
269+ // The body of the `if` is a continue.
270+ LinqFilterOpportunity< linqWhereCandidateCase1 / 2 > :: missed ( fes , is )
271+ or
272+ // There's nothing else in the loop than the `if`.
273+ LinqFilterOpportunity< linqWhereCandidateCase2 / 2 > :: missed ( fes , is ) and
274+ numStmts ( fes ) = 1
275+ }
276+
277+ private predicate linqFirstOrDefaultCandidate ( Stmt s , Expr e ) {
278+ s =
279+ any ( IfStmt is |
280+ e = is .getCondition ( ) and
281+ not exists ( is .getElse ( ) ) and
282+ not e .getAChildExpr * ( ) instanceof AwaitExpr
283+ )
214284}
215285
216286/**
@@ -220,15 +290,8 @@ predicate missedWhereOpportunity(ForEachStmtGenericEnumerable fes, IfStmt is) {
220290 */
221291predicate missedFirstOrDefaultOpportunity ( ForEachStmtGenericEnumerable fes , IfStmt is ) {
222292 // The loop only checks whether the current element is the first match.
223- is = firstStmt ( fes ) and
224- not exists ( is .getElse ( ) ) and
293+ LinqFilterOpportunity< linqFirstOrDefaultCandidate / 2 > :: missed ( fes , is ) and
225294 numStmts ( fes ) = 1 and
226- // Condition relies on loop variable.
227- exists ( VariableAccess va |
228- va .getTarget ( ) = fes .getVariable ( ) and
229- va = is .getCondition ( ) .getAChildExpr * ( )
230- ) and
231- not is .getCondition ( ) .getAChildExpr * ( ) instanceof AwaitExpr and
232295 not fes .isAsync ( ) and
233296 not fes .getVariable ( ) .isCaptured ( ) and
234297 returnsLoopVariable ( fes , is .getThen ( ) ) and
0 commit comments