Skip to content

Commit fe3b3e8

Browse files
committed
QL: ql/unqueryable-code query
1 parent b45f56a commit fe3b3e8

File tree

2 files changed

+72
-8
lines changed

2 files changed

+72
-8
lines changed

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

Lines changed: 55 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ private AstNode publicApi() {
3030
*/
3131
private AstNode queryPredicate() {
3232
// result = query relation that is "transitively" imported by a .ql file.
33-
PathProblemQuery::importsQueryRelation(result).asFile().getExtension() = "ql"
33+
// PathProblemQuery::importsQueryRelation(result).asFile().getExtension() = "ql"
34+
// any query predicate. Query predicates are usually meant to be used.
35+
result.(Predicate).hasAnnotation("query")
3436
or
3537
// the from-where-select
3638
result instanceof Select
@@ -200,8 +202,9 @@ private AstNode benign() {
200202
result instanceof BlockComment or
201203
not exists(result.toString()) or // <- invalid code
202204
// cached-stages pattern
203-
result.(Module).getAMember().(ClasslessPredicate).getName() = "forceStage" or
204-
result.(ClasslessPredicate).getName() = "forceStage" or
205+
result.(Module).getAMember().(ClasslessPredicate).getName() =
206+
["forceStage", "forceCachingInSameStageforceCachingInSameStage"] or
207+
result.(ClasslessPredicate).getName() = ["forceStage", "forceCachingInSameStage"] or
205208
result.getLocation().getFile().getBaseName() = "Caching.qll" or
206209
// sometimes contains dead code - ignore
207210
result.getLocation().getFile().getRelativePath().matches("%/tutorials/%") or
@@ -235,20 +238,64 @@ private AstNode queryable() {
235238
or
236239
result instanceof TopLevel // toplevel is always alive.
237240
or
241+
result = hackyShouldBeTreatedAsAlive()
242+
or
243+
// The below prevents the query from being too loud. The files below contain a lot of unqueryable code.
244+
// I think some of it is from some languages not using all features of a shared library, but I'm not sure (haven't look much into it).
245+
result
246+
.getLocation()
247+
.getFile()
248+
.getBaseName()
249+
.matches(["DataFlowImpl", "SsaImplCommon", "FlowSummary"] + "%")
250+
or
238251
// recurisve cases
239252
result = aliveStep(queryable())
240253
}
241254

255+
// The benign cases are mostly
256+
private AstNode benignUnqueryable() {
257+
result = benign() or
258+
// cached-stages pattern
259+
// sometimes contains dead code - ignore
260+
result.(Module).getName() = "Debugging" or
261+
result.getLocation().getFile() = benignUnqueryableFile()
262+
}
263+
264+
pragma[noinline]
265+
private File benignUnqueryableFile() {
266+
result.getAbsolutePath().matches("%/explore/%") or
267+
result.getRelativePath().matches("%/tutorials/%") or
268+
result.getRelativePath().matches("%/experimental/%") or
269+
result.getBaseName() =
270+
[
271+
"Expr.qll", "TypeScript.qll", "YAML.qll", "Tokens.qll", "Instruction.qll", "Persistence.qll",
272+
"ES2015Modules.qll"
273+
] or // lots of classes that exist for completeness
274+
result.getBaseName() = ["CachedStages.qll", "Caching.qll", "tutorial.qll"] or
275+
result.getBaseName() = "PrettyPrintAst.qll" or // it's dead code, but seems intentional
276+
result.getBaseName() = ["CryptoAlgorithmNames.qll", "SensitiveDataHeuristics.qll"] or // not all langs use all the things
277+
// some more identical files
278+
result.getBaseName() = "ReachableBlock.qll" or
279+
// QL-for-QL tests contain plenty of unqueryable code on purpose
280+
result.getAbsolutePath().matches("%/ql/ql/test%")
281+
}
282+
242283
/**
243284
* Gets an AstNode that does not affect any query result.
244285
* Is interresting as an quick-eval target to investigate dead code.
245286
* (It is intentional that this predicate is a result of this predicate).
246287
*/
247-
AstNode unQueryable(string msg) {
288+
AstNode unQueryable() {
248289
not result = queryable() and
249290
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/%")
291+
not result = benignUnqueryable() and
292+
not result.getParent() = any(AstNode node | not node = queryable())
254293
}
294+
295+
AstNode unqueryableLang(string lang) {
296+
lang = "/" + ["cpp", "csharp", "java", "javascript", "python", "ruby"] + "/" and
297+
result = unQueryable() and
298+
result.getLocation().getFile().getAbsolutePath().matches("%" + lang + "%")
299+
}
300+
301+
int countLang(string lang) { result = strictcount(unqueryableLang(lang)) }
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/**
2+
* @name Unqueryable code
3+
* @description Code that cannot affect the outcome of any query is suspicous.
4+
* @kind problem
5+
* @problem.severity recommendation
6+
* @id ql/unqueryable-code
7+
* @precision high
8+
*/
9+
10+
import ql
11+
import codeql_ql.style.DeadCodeQuery
12+
13+
from AstNode node
14+
where
15+
node = unQueryable() and
16+
node.getLocation().getFile().getAbsolutePath().matches("%/internal/%")
17+
select node, "Code cannot affect the outcome of any query."

0 commit comments

Comments
 (0)