|
|
|
|
@@ -3,7 +3,9 @@
|
|
|
|
|
* Provides helper classes and methods related to LINQ.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import csharp
|
|
|
|
|
private import csharp
|
|
|
|
|
private import semmle.code.csharp.frameworks.system.collections.Generic as GenericCollections
|
|
|
|
|
private import semmle.code.csharp.frameworks.system.Collections as Collections
|
|
|
|
|
|
|
|
|
|
//#################### PREDICATES ####################
|
|
|
|
|
private Stmt firstStmt(ForeachStmt fes) {
|
|
|
|
|
@@ -29,13 +31,40 @@ predicate isIEnumerableType(ValueOrRefType t) {
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* A class of foreach statements where the iterable expression
|
|
|
|
|
* supports the use of the LINQ extension methods on `IEnumerable<T>`.
|
|
|
|
|
*/
|
|
|
|
|
class ForeachStmtGenericEnumerable extends ForeachStmt {
|
|
|
|
|
ForeachStmtGenericEnumerable() {
|
|
|
|
|
exists(ValueOrRefType t | t = this.getIterableExpr().getType() |
|
|
|
|
|
t.getABaseType*().getUnboundDeclaration() instanceof
|
|
|
|
|
GenericCollections::SystemCollectionsGenericIEnumerableTInterface or
|
|
|
|
|
t.(ArrayType).getRank() = 1
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* A class of foreach statements where the iterable expression
|
|
|
|
|
* supports the use of the LINQ extension methods on `IEnumerable`.
|
|
|
|
|
*/
|
|
|
|
|
class ForeachStmtEnumerable extends ForeachStmt {
|
|
|
|
|
ForeachStmtEnumerable() {
|
|
|
|
|
exists(ValueOrRefType t | t = this.getIterableExpr().getType() |
|
|
|
|
|
t.getABaseType*() instanceof Collections::SystemCollectionsIEnumerableInterface or
|
|
|
|
|
t.(ArrayType).getRank() = 1
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Holds if `foreach` statement `fes` could be converted to a `.All()` call.
|
|
|
|
|
* That is, the `ForeachStmt` contains a single `if` with a condition that
|
|
|
|
|
* accesses the loop variable and with a body that assigns `false` to a variable
|
|
|
|
|
* and `break`s out of the `foreach`.
|
|
|
|
|
*/
|
|
|
|
|
predicate missedAllOpportunity(ForeachStmt fes) {
|
|
|
|
|
predicate missedAllOpportunity(ForeachStmtGenericEnumerable fes) {
|
|
|
|
|
exists(IfStmt is |
|
|
|
|
|
// The loop contains an if statement with no else case, and nothing else.
|
|
|
|
|
is = firstStmt(fes) and
|
|
|
|
|
@@ -54,12 +83,12 @@ predicate missedAllOpportunity(ForeachStmt fes) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Holds if `foreach` statement `fes` could be converted to a `.Cast()` call.
|
|
|
|
|
* Holds if the `foreach` statement `fes` can be converted to a `.Cast()` call.
|
|
|
|
|
* That is, the loop variable is accessed only in the first statement of the
|
|
|
|
|
* block, and the access is a cast. The first statement needs to be a
|
|
|
|
|
* `LocalVariableDeclStmt`.
|
|
|
|
|
* block, the access is a cast, and the first statement is a
|
|
|
|
|
* local variable declaration statement `s`.
|
|
|
|
|
*/
|
|
|
|
|
predicate missedCastOpportunity(ForeachStmt fes, LocalVariableDeclStmt s) {
|
|
|
|
|
predicate missedCastOpportunity(ForeachStmtEnumerable fes, LocalVariableDeclStmt s) {
|
|
|
|
|
s = firstStmt(fes) and
|
|
|
|
|
forex(VariableAccess va | va = fes.getVariable().getAnAccess() |
|
|
|
|
|
va = s.getAVariableDeclExpr().getAChildExpr*()
|
|
|
|
|
@@ -71,12 +100,12 @@ predicate missedCastOpportunity(ForeachStmt fes, LocalVariableDeclStmt s) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Holds if `foreach` statement `fes` could be converted to an `.OfType()` call.
|
|
|
|
|
* Holds if `foreach` statement `fes` can be converted to an `.OfType()` call.
|
|
|
|
|
* That is, the loop variable is accessed only in the first statement of the
|
|
|
|
|
* block, and the access is a cast with the `as` operator. The first statement
|
|
|
|
|
* needs to be a `LocalVariableDeclStmt`.
|
|
|
|
|
* block, the access is a cast with the `as` operator, and the first statement
|
|
|
|
|
* is a local variable declaration statement `s`.
|
|
|
|
|
*/
|
|
|
|
|
predicate missedOfTypeOpportunity(ForeachStmt fes, LocalVariableDeclStmt s) {
|
|
|
|
|
predicate missedOfTypeOpportunity(ForeachStmtEnumerable fes, LocalVariableDeclStmt s) {
|
|
|
|
|
s = firstStmt(fes) and
|
|
|
|
|
forex(VariableAccess va | va = fes.getVariable().getAnAccess() |
|
|
|
|
|
va = s.getAVariableDeclExpr().getAChildExpr*()
|
|
|
|
|
@@ -88,12 +117,12 @@ predicate missedOfTypeOpportunity(ForeachStmt fes, LocalVariableDeclStmt s) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Holds if `foreach` statement `fes` could be converted to a `.Select()` call.
|
|
|
|
|
* Holds if `foreach` statement `fes` can be converted to a `.Select()` call.
|
|
|
|
|
* That is, the loop variable is accessed only in the first statement of the
|
|
|
|
|
* block, and the access is not a cast. The first statement needs to be a
|
|
|
|
|
* `LocalVariableDeclStmt`.
|
|
|
|
|
* block, the access is not a cast, and the first statement is a
|
|
|
|
|
* local variable declaration statement `s`.
|
|
|
|
|
*/
|
|
|
|
|
predicate missedSelectOpportunity(ForeachStmt fes, LocalVariableDeclStmt s) {
|
|
|
|
|
predicate missedSelectOpportunity(ForeachStmtGenericEnumerable fes, LocalVariableDeclStmt s) {
|
|
|
|
|
s = firstStmt(fes) and
|
|
|
|
|
forex(VariableAccess va | va = fes.getVariable().getAnAccess() |
|
|
|
|
|
va = s.getAVariableDeclExpr().getAChildExpr*()
|
|
|
|
|
@@ -107,7 +136,7 @@ predicate missedSelectOpportunity(ForeachStmt fes, LocalVariableDeclStmt s) {
|
|
|
|
|
* variable, and the body of the `if` is either a `continue` or there's nothing
|
|
|
|
|
* else in the loop than the `if`.
|
|
|
|
|
*/
|
|
|
|
|
predicate missedWhereOpportunity(ForeachStmt fes, IfStmt is) {
|
|
|
|
|
predicate missedWhereOpportunity(ForeachStmtGenericEnumerable fes, IfStmt is) {
|
|
|
|
|
// The very first thing the foreach loop does is test its iteration variable.
|
|
|
|
|
is = firstStmt(fes) and
|
|
|
|
|
exists(VariableAccess va |
|
|
|
|
|
|