Skip to content

Commit 548e50c

Browse files
committed
QL: add quick-eval predicate to detect unqueryable code
1 parent 2250ebc commit 548e50c

File tree

1 file changed

+89
-43
lines changed

1 file changed

+89
-43
lines changed

ql/ql/src/codeql_ql/style/DeadCodeQuery.qll

Lines changed: 89 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,15 @@ private AstNode publicApi() {
2828
* Gets any AstNode that directly computes a result of a query.
2929
* I.e. a query predicate or the from-where-select.
3030
*/
31-
private AstNode queryable() {
31+
private AstNode queryPredicate() {
3232
// result = query relation that is "transitively" imported by a .ql file.
3333
PathProblemQuery::importsQueryRelation(result).asFile().getExtension() = "ql"
3434
or
3535
// the from-where-select
3636
result instanceof Select
3737
or
3838
// child of the above.
39-
result = queryable().getAChild()
39+
result = queryPredicate().getAChild()
4040
}
4141

4242
AstNode hackyShouldBeTreatedAsAlive() {
@@ -64,7 +64,7 @@ private AstNode alive() {
6464
result = publicApi()
6565
or
6666
// 2) everything that can be an output when running a query
67-
result = queryable()
67+
result = queryPredicate()
6868
or
6969
// 3) A module with an import that imports another file, the import can activate a file.
7070
result.(Module).getAMember().(Import).getResolvedModule().getFile() !=
@@ -73,90 +73,97 @@ private AstNode alive() {
7373
// 4) Things that aren't really alive, but that this query treats as live.
7474
result = hackyShouldBeTreatedAsAlive()
7575
or
76+
result instanceof TopLevel // toplevel is always alive.
77+
or
78+
// recurisve cases
79+
result = aliveStep(alive())
80+
}
81+
82+
private AstNode aliveStep(AstNode prev) {
7683
//
7784
// The recursive cases.
7885
//
79-
result.getEnclosingPredicate() = alive()
86+
result.getEnclosingPredicate() = prev
8087
or
81-
result = alive().(Call).getTarget()
88+
result = prev.(Call).getTarget()
8289
or
83-
alive().(ClassPredicate).overrides(result)
90+
prev.(ClassPredicate).overrides(result)
8491
or
85-
result.(ClassPredicate).overrides(alive())
92+
result.(ClassPredicate).overrides(prev)
8693
or
87-
result = alive().(PredicateExpr).getResolvedPredicate()
94+
result = prev.(PredicateExpr).getResolvedPredicate()
8895
or
8996
// if a sub-class is alive, then the super-class is alive.
90-
result = alive().(Class).getASuperType().getResolvedType().(ClassType).getDeclaration()
97+
result = prev.(Class).getASuperType().getResolvedType().(ClassType).getDeclaration()
9198
or
9299
// if the super class is alive and abstract, then any sub-class is alive.
93-
exists(Class sup | sup = alive() and sup.isAbstract() |
100+
exists(Class sup | sup = prev and sup.isAbstract() |
94101
sup = result.(Class).getASuperType().getResolvedType().(ClassType).getDeclaration()
95102
)
96103
or
97-
result = alive().(Class).getAChild() and
104+
result = prev.(Class).getAChild() and
98105
not result.hasAnnotation("private")
99106
or
100-
result = alive().getAnAnnotation()
107+
result = prev.getAnAnnotation()
101108
or
102-
result = alive().getQLDoc()
109+
result = prev.getQLDoc()
103110
or
104111
// any imported module is alive. We don't have to handle the "import a file"-case, those are treated as public APIs.
105-
result = alive().(Import).getResolvedModule().asModule()
112+
result = prev.(Import).getResolvedModule().asModule()
106113
or
107-
result = alive().(VarDecl).getType().getDeclaration()
114+
result = prev.(VarDecl).getType().getDeclaration()
108115
or
109-
result = alive().(FieldDecl).getVarDecl()
116+
result = prev.(FieldDecl).getVarDecl()
110117
or
111-
result = alive().(InlineCast).getType().getDeclaration()
118+
result = prev.(InlineCast).getType().getDeclaration()
112119
or
113120
// a class overrides some predicate, is the super-predicate is alive.
114121
exists(ClassPredicate pred, ClassPredicate sup |
115122
pred.hasAnnotation("override") and
116123
pred.overrides(sup) and
117124
result = pred.getParent() and
118-
sup.getParent() = alive()
125+
sup.getParent() = prev
119126
)
120127
or
121128
// if a class is alive, so is it's super-class
122129
result =
123-
[alive().(Class).getASuperType(), alive().(Class).getAnInstanceofType()]
130+
[prev.(Class).getASuperType(), prev.(Class).getAnInstanceofType()]
124131
.getResolvedType()
125132
.getDeclaration()
126133
or
127134
// if a class is alive and abstract, then any sub-class is alive.
128135
exists(Class clz, Class sup | result = clz |
129136
clz.getASuperType().getResolvedType().getDeclaration() = sup and
130137
sup.isAbstract() and
131-
sup = alive()
138+
sup = prev
132139
)
133140
or
134141
// a module containing something live, is also alive.
135-
result.(Module).getAMember() = alive()
142+
result.(Module).getAMember() = prev
136143
or
137-
result = alive().(Module).getAlias()
144+
result = prev.(Module).getAlias()
138145
or
139-
result.(NewType).getABranch() = alive()
146+
result.(NewType).getABranch() = prev
140147
or
141-
result = alive().(TypeExpr).getAChild()
148+
result = prev.(TypeExpr).getAChild()
142149
or
143-
result = alive().(FieldAccess).getDeclaration()
150+
result = prev.(FieldAccess).getDeclaration()
144151
or
145-
result = alive().(VarDecl).getTypeExpr()
152+
result = prev.(VarDecl).getTypeExpr()
146153
or
147-
result.(Import).getParent() = alive()
154+
result.(Import).getParent() = prev
148155
or
149-
result = alive().(NewType).getABranch()
156+
result = prev.(NewType).getABranch()
150157
or
151-
result = alive().(ModuleExpr).getAChild()
158+
result = prev.(ModuleExpr).getAChild()
152159
or
153-
result = alive().(ModuleExpr).getResolvedModule().asModule()
160+
result = prev.(ModuleExpr).getResolvedModule().asModule()
154161
or
155-
result = alive().(InstanceOf).getType().getResolvedType().getDeclaration()
162+
result = prev.(InstanceOf).getType().getResolvedType().getDeclaration()
156163
or
157-
result = alive().(Annotation).getAChild()
164+
result = prev.(Annotation).getAChild()
158165
or
159-
result = alive().(Predicate).getReturnType().getDeclaration()
166+
result = prev.(Predicate).getReturnType().getDeclaration()
160167
}
161168

162169
private AstNode deprecated() {
@@ -188,21 +195,60 @@ private AstNode classUnion() {
188195
result = classUnion().(ModuleExpr).getAChild()
189196
}
190197

198+
private AstNode benign() {
199+
not result.getLocation().getFile().getExtension() = ["ql", "qll"] or // ignore dbscheme files
200+
result instanceof BlockComment or
201+
not exists(result.toString()) or // <- invalid code
202+
// cached-stages pattern
203+
result.(Module).getAMember().(ClasslessPredicate).getName() = "forceStage" or
204+
result.(ClasslessPredicate).getName() = "forceStage" or
205+
result.getLocation().getFile().getBaseName() = "Caching.qll" or
206+
// sometimes contains dead code - ignore
207+
result.getLocation().getFile().getRelativePath().matches("%/tutorials/%") or
208+
result = classUnion()
209+
}
210+
191211
private predicate isDeadInternal(AstNode node) {
192212
not node = alive() and
193-
not node = deprecated() and
194-
not node = classUnion()
213+
not node = deprecated()
195214
}
196215

197216
predicate isDead(AstNode node) {
198217
isDeadInternal(node) and
199218
not isDeadInternal(node.getParent()) and
200-
not node instanceof BlockComment and
201-
exists(node.toString()) and // <- invalid code
202-
node.getLocation().getFile().getExtension() = ["ql", "qll"] and // ignore dbscheme files
203-
// cached-stages pattern
204-
not node.(Module).getAMember().(ClasslessPredicate).getName() = "forceStage" and
205-
not node.(ClasslessPredicate).getName() = "forceStage" and
206-
not node.getLocation().getFile().getBaseName() = "Caching.qll" and
207-
not node.getLocation().getFile().getRelativePath().matches("%/tutorials/%") // sometimes contains dead code - ignore
219+
not node = benign()
220+
}
221+
222+
/**
223+
* Gets an AST node that affects a query.
224+
*/
225+
private AstNode queryable() {
226+
//
227+
// The base cases.
228+
//
229+
// everything that can be an output when running a query
230+
result = queryPredicate()
231+
or
232+
// A module with an import that imports another file, the import can activate a file.
233+
result.(Module).getAMember().(Import).getResolvedModule().getFile() !=
234+
result.getLocation().getFile()
235+
or
236+
result instanceof TopLevel // toplevel is always alive.
237+
or
238+
// recurisve cases
239+
result = aliveStep(queryable())
240+
}
241+
242+
/**
243+
* Gets an AstNode that does not affect any query result.
244+
* Is interresting as an quick-eval target to investigate dead code.
245+
* (It is intentional that this predicate is a result of this predicate).
246+
*/
247+
AstNode unQueryable(string msg) {
248+
not result = queryable() and
249+
not result = deprecated() and
250+
not result = benign() and
251+
not result.getParent() = any(AstNode node | not node = queryable()) and
252+
msg = result.getLocation().getFile().getBaseName() and
253+
result.getLocation().getFile().getAbsolutePath().matches("%/javascript/%")
208254
}

0 commit comments

Comments
 (0)