Skip to content

Commit 5d54482

Browse files
authored
Merge pull request github#11770 from atorralba/atorralba/ql/omittable-exists
QL: Add OmittableExists query
2 parents 466f246 + 6914e9a commit 5d54482

File tree

6 files changed

+132
-39
lines changed

6 files changed

+132
-39
lines changed
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import ql
2+
3+
signature predicate checkAstNodeSig(AstNode p);
4+
5+
module ConjunctionParent<checkAstNodeSig/1 checkAstNode> {
6+
/**
7+
* Gets the top-most parent of `p` that is not a disjunction.
8+
*/
9+
AstNode getConjunctionParent(AstNode p) {
10+
result =
11+
min(int level, AstNode parent |
12+
parent = getConjunctionParentRec(p) and level = level(parent)
13+
|
14+
parent order by level
15+
)
16+
}
17+
18+
/**
19+
* Gets a (transitive) parent of `p`, where the parent is not a negative edge, and `checkAstNode(p)` holds.
20+
*/
21+
private AstNode getConjunctionParentRec(AstNode p) {
22+
checkAstNode(p) and
23+
result = p
24+
or
25+
result = getConjunctionParentRec(p).getParent() and
26+
not result instanceof Disjunction and
27+
not result instanceof IfFormula and
28+
not result instanceof Implication and
29+
not result instanceof Negation and
30+
not result instanceof Predicate and
31+
not result instanceof ExprAggregate and
32+
not result instanceof FullAggregate and
33+
not result instanceof Forex and
34+
not result instanceof Forall
35+
}
36+
37+
/**
38+
* Gets which level in the AST `p` is at.
39+
* E.g. the top-level is 0, the next level is 1, etc.
40+
*/
41+
private int level(AstNode p) {
42+
p instanceof TopLevel and result = 0
43+
or
44+
result = level(p.getParent()) + 1
45+
}
46+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/**
2+
* @name Omittable 'exists' variable
3+
* @description Writing 'exists(x | pred(x))' is bad practice and can be omitted.
4+
* @kind problem
5+
* @problem.severity warning
6+
* @precision high
7+
* @id ql/omittable-exists
8+
* @tags maintainability
9+
*/
10+
11+
import ql
12+
import codeql_ql.style.ConjunctionParent
13+
14+
/**
15+
* Holds if `existsArgument` is the declaration of a variable in an `exists` formula,
16+
* and `use` is both its only use and an argument of a predicate that doesn't restrict
17+
* the corresponding parameter type.
18+
*/
19+
predicate omittableExists(VarDecl existsArgument, VarAccess use) {
20+
existsArgument = any(Exists e).getAnArgument() and
21+
use = unique( | | existsArgument.getAnAccess()) and
22+
exists(Call c, int argPos, Type paramType |
23+
c.getArgument(argPos) = use and paramType = c.getTarget().getParameterType(argPos)
24+
|
25+
existsArgument.getType() = paramType.getASuperType*() and
26+
not paramType instanceof DatabaseType
27+
)
28+
}
29+
30+
/** Holds if `p` is an exists variable (either declaration or use) that can be omitted. */
31+
predicate omittableExistsNode(AstNode p) { omittableExists(p, _) or omittableExists(_, p) }
32+
33+
from VarDecl existsArgument, VarAccess use
34+
where
35+
omittableExists(existsArgument, use) and
36+
ConjunctionParent<omittableExistsNode/1>::getConjunctionParent(existsArgument) =
37+
ConjunctionParent<omittableExistsNode/1>::getConjunctionParent(use)
38+
select existsArgument, "This exists variable can be omitted by using a don't-care expression $@.",
39+
use, "in this argument"

ql/ql/src/queries/style/RedundantAssignment.ql

Lines changed: 6 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
*/
1010

1111
import ql
12+
import codeql_ql.style.ConjunctionParent
13+
import codeql.GlobalValueNumbering as GVN
1214

1315
/**
1416
* A variable that is set equal to (assigned) a value one or more times.
@@ -40,8 +42,6 @@ class AssignedVariable extends VarDecl {
4042
}
4143
}
4244

43-
import codeql.GlobalValueNumbering as GVN
44-
4545
/**
4646
* Holds if `assigned1` and `assigned2` assigns the same value to `var`.
4747
* The assignments may be on different branches of a disjunction.
@@ -58,47 +58,14 @@ predicate candidateRedundantAssignment(AssignedVariable var, Expr assigned1, Exp
5858
assigned1 != assigned2
5959
}
6060

61-
/**
62-
* Gets a (transitive) parent of `p`, where the parent is not a disjunction, and `p` is a candidate assignment from `candidateRedundantAssignment`.
63-
*/
64-
AstNode getConjunctionParentRec(AstNode p) {
65-
candidateRedundantAssignment(_, p, _) and
66-
result = p
67-
or
68-
result = getConjunctionParentRec(p).getParent() and
69-
not result instanceof Disjunction and
70-
not result instanceof IfFormula and
71-
not result instanceof Implication and
72-
not result instanceof Negation and
73-
not result instanceof Predicate
74-
}
75-
76-
/**
77-
* Gets which level in the AST `p` is at.
78-
* E.g. the top-level is 0, the next level is 1, etc.
79-
*/
80-
int level(AstNode p) {
81-
p instanceof TopLevel and result = 0
82-
or
83-
result = level(p.getParent()) + 1
84-
}
85-
86-
/**
87-
* Gets the top-most parent of `p` that is not a disjunction.
88-
*/
89-
AstNode getConjunctionParent(AstNode p) {
90-
result =
91-
min(int level, AstNode parent |
92-
parent = getConjunctionParentRec(p) and level = level(parent)
93-
|
94-
parent order by level
95-
)
96-
}
61+
/** Holds if `p` is a candidate node for redundant assignment. */
62+
predicate candidateNode(AstNode p) { candidateRedundantAssignment(_, p, _) }
9763

9864
from AssignedVariable var, Expr assigned1, Expr assigned2
9965
where
10066
candidateRedundantAssignment(var, assigned1, assigned2) and
101-
getConjunctionParent(assigned1) = getConjunctionParent(assigned2) and
67+
ConjunctionParent<candidateNode/1>::getConjunctionParent(assigned1) =
68+
ConjunctionParent<candidateNode/1>::getConjunctionParent(assigned2) and
10269
// de-duplcation:
10370
(
10471
assigned1.getLocation().getStartLine() < assigned2.getLocation().getStartLine()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
| Test.qll:20:10:20:14 | i | This exists variable can be omitted by using a don't-care expression $@. | Test.qll:20:29:20:29 | i | in this argument |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
queries/style/OmittableExists.ql
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
predicate aPredicate(int i) { none() }
2+
3+
predicate anotherPredicate(int i) { none() }
4+
5+
predicate yetAnotherPredicate(int i, int y) { none() }
6+
7+
predicate dbTypePredicate(@location l) { none() }
8+
9+
string predicateWithResult(int i) { none() }
10+
11+
class SmallInt extends int {
12+
SmallInt() { this = [0 .. 10] }
13+
}
14+
15+
class Location extends @location {
16+
string toString() { result = "" }
17+
}
18+
19+
predicate test() {
20+
exists(int i | aPredicate(i)) // BAD
21+
or
22+
exists(int i | aPredicate(i) or anotherPredicate(i)) // BAD [NOT DETECTED]
23+
or
24+
exists(int i | aPredicate(i) and i.abs() = 0) // GOOD
25+
or
26+
exists(int i | aPredicate(i) and exists(int i2 | i = i2)) // GOOD
27+
or
28+
exists(int i | count(predicateWithResult(i)) = 0) // GOOD
29+
or
30+
exists(int i | count(int y | yetAnotherPredicate(i, y)) > 0) // GOOD
31+
or
32+
exists(int i | forex(int y | yetAnotherPredicate(i, y))) // GOOD
33+
or
34+
exists(int i | forall(int y | yetAnotherPredicate(i, y))) // GOOD
35+
or
36+
exists(SmallInt i | aPredicate(i)) // GOOD
37+
or
38+
exists(Location l | dbTypePredicate(l)) // GOOD
39+
}

0 commit comments

Comments
 (0)