Skip to content

Commit 40c8881

Browse files
authored
Merge pull request #7472 from erik-krogh/redundant-aggregate
QL-for-QL: Add a could-be-cast query
2 parents 58b1a6f + 30d896b commit 40c8881

File tree

10 files changed

+132
-45
lines changed

10 files changed

+132
-45
lines changed

ql/ql/src/codeql-suites/ql-code-scanning.qls

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
problem.severity:
1313
- error
1414
- warning
15+
- recommendation
1516
- exclude:
1617
deprecated: //
1718
- exclude:

ql/ql/src/codeql_ql/ast/Ast.qll

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ class AstNode extends TAstNode {
6363
pred = directMember("getQLDoc") and result = this.getQLDoc()
6464
}
6565

66+
/** Gets any child of this node. */
67+
AstNode getAChild() { result = this.getAChild(_) }
68+
6669
/**
6770
* Gets the primary QL class for the ast node.
6871
*/
@@ -1238,14 +1241,7 @@ class Boolean extends Literal {
12381241

12391242
/** A comparison symbol, such as `"<"` or `"="`. */
12401243
class ComparisonSymbol extends string {
1241-
ComparisonSymbol() {
1242-
this = "=" or
1243-
this = "!=" or
1244-
this = "<" or
1245-
this = ">" or
1246-
this = "<=" or
1247-
this = ">="
1248-
}
1244+
ComparisonSymbol() { this = ["=", "!=", "<", ">", "<=", ">="] }
12491245
}
12501246

12511247
/** A comparison formula, such as `x < 3` or `y = true`. */
@@ -1287,10 +1283,7 @@ class Quantifier extends TQuantifier, Formula {
12871283
}
12881284

12891285
/** Gets the ith variable declaration of this quantifier. */
1290-
VarDecl getArgument(int i) {
1291-
i >= 1 and
1292-
toQL(result) = quant.getChild(i - 1)
1293-
}
1286+
VarDecl getArgument(int i) { toQL(result) = quant.getChild(i + 1) }
12941287

12951288
/** Gets an argument of this quantifier. */
12961289
VarDecl getAnArgument() { result = this.getArgument(_) }
@@ -1661,6 +1654,15 @@ class FullAggregate extends TFullAggregate, Aggregate {
16611654
}
16621655
}
16631656

1657+
/**
1658+
* A "any" expression, such as `any(int i | i > 0).toString()`.
1659+
*/
1660+
class Any extends FullAggregate {
1661+
Any() { this.getKind() = "any" }
1662+
1663+
override string getAPrimaryQlClass() { result = "Any" }
1664+
}
1665+
16641666
/**
16651667
* A "rank" expression, such as `rank[4](int i | i = [5 .. 15] | i)`.
16661668
*/
@@ -1855,7 +1857,7 @@ class ExprAnnotation extends TExprAnnotation, Expr {
18551857

18561858
/** A function symbol, such as `+` or `*`. */
18571859
class FunctionSymbol extends string {
1858-
FunctionSymbol() { this = "+" or this = "-" or this = "*" or this = "/" or this = "%" }
1860+
FunctionSymbol() { this = ["+", "-", "*", "/", "%"] }
18591861
}
18601862

18611863
/**
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import ql
2+
3+
/**
4+
* Holds if `aggr` is of one of the following forms:
5+
* `exists(var | range)` or `any(var | range)` or `any(var | | range)` or `any(type x | .. | x)`
6+
*/
7+
private predicate castAggregate(AstNode aggr, Formula range, VarDecl var, string kind) {
8+
kind = "exists" and
9+
exists(Exists ex | aggr = ex |
10+
ex.getRange() = range and
11+
not exists(ex.getFormula()) and
12+
count(ex.getArgument(_)) = 1 and
13+
ex.getArgument(0) = var
14+
)
15+
or
16+
kind = "any" and
17+
exists(Any anyy | aggr = anyy |
18+
not exists(anyy.getRange()) and
19+
anyy.getExpr(0) = range and
20+
count(anyy.getExpr(_)) = 1 and
21+
count(anyy.getArgument(_)) = 1 and
22+
anyy.getArgument(0) = var
23+
)
24+
or
25+
kind = "any" and
26+
exists(Any anyy | aggr = anyy |
27+
range = anyy.getRange() and
28+
count(anyy.getArgument(_)) = 1 and
29+
anyy.getArgument(0) = var and
30+
not exists(anyy.getExpr(0))
31+
)
32+
or
33+
kind = "any" and
34+
exists(Any anyy | aggr = anyy |
35+
range = anyy.getRange() and
36+
count(anyy.getExpr(_)) = 1 and
37+
count(anyy.getArgument(_)) = 1 and
38+
anyy.getExpr(_).(VarAccess).getDeclaration() = var and
39+
anyy.getArgument(0) = var
40+
)
41+
}
42+
43+
/** Holds if `aggr` is an aggregate that could be replaced with an instanceof expression or an inline cast. */
44+
predicate aggregateCouldBeCast(
45+
AstNode aggr, ComparisonFormula comp, string kind, VarDecl var, AstNode operand
46+
) {
47+
castAggregate(aggr, comp, var, kind) and
48+
comp.getOperator() = "=" and
49+
count(VarAccess access | access.getDeclaration() = var and access.getParent() != aggr) = 1 and
50+
comp.getAnOperand().(VarAccess).getDeclaration() = var and
51+
operand = comp.getAnOperand() and
52+
not operand.(VarAccess).getDeclaration() = var
53+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/**
2+
* @name Expression can be replaced with a cast
3+
* @description An exists/any that is only used to cast a value to a type
4+
* can be replaced with a cast.
5+
* @kind problem
6+
* @problem.severity warning
7+
* @id ql/could-be-cast
8+
* @precision very-high
9+
*/
10+
11+
import ql
12+
import codeql_ql.style.CouldBeCastQuery
13+
14+
from AstNode aggr, VarDecl var, string msg, Expr operand
15+
where
16+
exists(string kind | aggregateCouldBeCast(aggr, _, kind, var, operand) |
17+
kind = "exists" and
18+
if operand.getType().getASuperType*() = var.getType()
19+
then msg = "The assignment in the exists(..) is redundant."
20+
else msg = "The assignment to $@ in the exists(..) can replaced with an instanceof expression."
21+
or
22+
kind = "any" and
23+
if operand.getType().getASuperType*() = var.getType()
24+
then msg = "The assignment in the any(..) is redundant."
25+
else msg = "The assignment to $@ in this any(..) can be replaced with an inline cast."
26+
)
27+
select aggr, msg, var, var.getName()

ql/ql/src/queries/style/RedundantInlineCast.ql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* @name Redundant inline cast
33
* @description Redundant inline casts
44
* @kind problem
5-
* @problem.severity error
5+
* @problem.severity warning
66
* @id ql/redundant-inline-cast
77
* @tags maintainability
88
* @precision high

ql/ql/src/queries/style/docs/MissingQLDoc.ql

Lines changed: 0 additions & 25 deletions
This file was deleted.

ql/ql/test/printAst/printAst.expected

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,8 @@ nodes
121121
| Foo.qll:22:3:22:3 | f | semmle.order | 60 |
122122
| Foo.qll:22:3:22:16 | ComparisonFormula | semmle.label | [ComparisonFormula] ComparisonFormula |
123123
| Foo.qll:22:3:22:16 | ComparisonFormula | semmle.order | 60 |
124-
| Foo.qll:22:7:22:16 | FullAggregate[any] | semmle.label | [FullAggregate[any]] FullAggregate[any] |
125-
| Foo.qll:22:7:22:16 | FullAggregate[any] | semmle.order | 62 |
124+
| Foo.qll:22:7:22:16 | Any | semmle.label | [Any] Any |
125+
| Foo.qll:22:7:22:16 | Any | semmle.order | 62 |
126126
| Foo.qll:22:11:22:13 | TypeExpr | semmle.label | [TypeExpr] TypeExpr |
127127
| Foo.qll:22:11:22:13 | TypeExpr | semmle.order | 63 |
128128
| Foo.qll:22:11:22:15 | f | semmle.label | [VarDecl] f |
@@ -363,10 +363,10 @@ edges
363363
| Foo.qll:20:3:20:13 | ComparisonFormula | Foo.qll:20:13:20:13 | f | semmle.order | 59 |
364364
| Foo.qll:22:3:22:16 | ComparisonFormula | Foo.qll:22:3:22:3 | f | semmle.label | getLeftOperand() |
365365
| Foo.qll:22:3:22:16 | ComparisonFormula | Foo.qll:22:3:22:3 | f | semmle.order | 60 |
366-
| Foo.qll:22:3:22:16 | ComparisonFormula | Foo.qll:22:7:22:16 | FullAggregate[any] | semmle.label | getRightOperand() |
367-
| Foo.qll:22:3:22:16 | ComparisonFormula | Foo.qll:22:7:22:16 | FullAggregate[any] | semmle.order | 62 |
368-
| Foo.qll:22:7:22:16 | FullAggregate[any] | Foo.qll:22:11:22:15 | f | semmle.label | getArgument(_) |
369-
| Foo.qll:22:7:22:16 | FullAggregate[any] | Foo.qll:22:11:22:15 | f | semmle.order | 63 |
366+
| Foo.qll:22:3:22:16 | ComparisonFormula | Foo.qll:22:7:22:16 | Any | semmle.label | getRightOperand() |
367+
| Foo.qll:22:3:22:16 | ComparisonFormula | Foo.qll:22:7:22:16 | Any | semmle.order | 62 |
368+
| Foo.qll:22:7:22:16 | Any | Foo.qll:22:11:22:15 | f | semmle.label | getArgument(_) |
369+
| Foo.qll:22:7:22:16 | Any | Foo.qll:22:11:22:15 | f | semmle.order | 63 |
370370
| Foo.qll:22:11:22:15 | f | Foo.qll:22:11:22:13 | TypeExpr | semmle.label | getTypeExpr() |
371371
| Foo.qll:22:11:22:15 | f | Foo.qll:22:11:22:13 | TypeExpr | semmle.order | 63 |
372372
| Foo.qll:24:3:24:23 | ComparisonFormula | Foo.qll:24:3:24:3 | Integer | semmle.label | getLeftOperand() |
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
| Foo.qll:3:3:3:24 | Exists | The assignment to $@ in the exists(..) can replaced with an instanceof expression. | Foo.qll:3:10:3:15 | j | j |
2+
| Foo.qll:7:3:7:21 | Any | The assignment to $@ in this any(..) can be replaced with an inline cast. | Foo.qll:7:7:7:12 | j | j |
3+
| Foo.qll:9:3:9:25 | Any | The assignment to $@ in this any(..) can be replaced with an inline cast. | Foo.qll:9:7:9:12 | j | j |
4+
| Foo.qll:15:3:15:20 | Any | The assignment in the any(..) is redundant. | Foo.qll:15:7:15:11 | j | j |
5+
| Foo.qll:17:3:17:23 | Exists | The assignment in the exists(..) is redundant. | Foo.qll:17:10:17:14 | j | j |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
queries/style/CouldBeCast.ql
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
bindingset[i]
2+
predicate foo(int i) {
3+
exists(Even j | j = i) // NOT OK
4+
or
5+
exists(Even j | j = i | j % 4 = 0) // OK
6+
or
7+
any(Even j | j = i) = 2 // NOT OK
8+
or
9+
any(Even j | j = i | j) = 2 // NOT OK
10+
or
11+
any(Even j | j = i | j * 2) = 4 // OK
12+
or
13+
any(Even j | j = i and j % 4 = 0 | j) = 4 // OK
14+
or
15+
any(int j | j = i) = 2 // NOT OK
16+
or
17+
exists(int j | j = i) // NOT OK
18+
}
19+
20+
class Even extends int {
21+
bindingset[this]
22+
Even() { this % 2 = 0 }
23+
}

0 commit comments

Comments
 (0)