Skip to content

Commit 2f29459

Browse files
authored
Merge pull request #19931 from michaelnebel/ql4ql/qualitytagcheck
Ql4ql: Quality query tagging.
2 parents 36ebe99 + 11c4a63 commit 2f29459

32 files changed

+292
-44
lines changed

docs/query-metadata-style-guide.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ Each code quality related query should have **one** of these two "top-level" cat
157157
* `@tags maintainability`–for queries that detect patterns that make it harder for developers to make changes to the code.
158158
* `@tags reliability`–for queries that detect issues that affect whether the code will perform as expected during execution.
159159

160-
In addition to the "top-level" categories, we will also add sub-categories to further group code quality related queries:
160+
In addition to the "top-level" categories, we may also add sub-categories to further group code quality related queries:
161161

162162
* `@tags maintainability`–for queries that detect patterns that make it harder for developers to make changes to the code.
163163
* `@tags readability`–for queries that detect confusing patterns that make it harder for developers to read the code.
@@ -171,6 +171,7 @@ In addition to the "top-level" categories, we will also add sub-categories to fu
171171
* `@tags concurrency`-for queries that detect concurrency related issues such as race conditions, deadlocks, thread safety, etc
172172
* `@tags error-handling`-for queries that detect issues related to unsafe error handling such as uncaught exceptions, etc
173173

174+
You may use sub-categories from both top-level categories on the same query. However, if you only use sub-categories from a single top-level category, then you must also tag the query with that top-level category.
174175

175176
There are also more specific `@tags` that can be added. See, the following pages for examples of the low-level tags:
176177

java/ql/src/Language Abuse/TypeVariableHidesType.ql

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66
* @problem.severity warning
77
* @precision medium
88
* @id java/type-variable-hides-type
9-
* @tags reliability
9+
* @tags quality
10+
* maintainability
1011
* readability
1112
* types
12-
* quality
1313
*/
1414

1515
import java

javascript/ql/src/Quality/UnhandledErrorInStreamPipeline.ql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* @problem.severity warning
77
* @precision high
88
* @tags quality
9-
* maintainability
9+
* reliability
1010
* error-handling
1111
* frameworks/nodejs
1212
*/

ql/ql/src/codeql/files/FileSystem.qll

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,8 @@ class File extends Container, Impl::File {
6363
/** Holds if this file was extracted from ordinary source code. */
6464
predicate fromSource() { any() }
6565
}
66+
67+
/** A test file. */
68+
class TestFile extends File {
69+
TestFile() { this.getRelativePath().matches("%/" + ["experimental", "examples", "test"] + "/%") }
70+
}

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

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -202,25 +202,43 @@ class QueryDoc extends QLDoc {
202202

203203
override string getAPrimaryQlClass() { result = "QueryDoc" }
204204

205-
/** Gets the @kind for the query */
205+
/** Gets the @kind for the query. */
206206
string getQueryKind() {
207207
result = this.getContents().regexpCapture("(?s).*@kind ([\\w-]+)\\s.*", 1)
208208
}
209209

210-
/** Gets the @name for the query */
210+
/** Gets the @name for the query. */
211211
string getQueryName() {
212212
result = this.getContents().regexpCapture("(?s).*@name (.+?)(?=\\n).*", 1)
213213
}
214214

215-
/** Gets the id part (without language) of the @id */
215+
/** Gets the id part (without language) of the @id. */
216216
string getQueryId() {
217217
result = this.getContents().regexpCapture("(?s).*@id (\\w+)/([\\w\\-/]+)\\s.*", 2)
218218
}
219219

220-
/** Gets the language of the @id */
220+
/** Gets the language of the @id. */
221221
string getQueryLanguage() {
222222
result = this.getContents().regexpCapture("(?s).*@id (\\w+)/([\\w\\-/]+)\\s.*", 1)
223223
}
224+
225+
/** Gets the @precision for the query. */
226+
string getQueryPrecision() {
227+
result = this.getContents().regexpCapture("(?s).*@precision ([\\w\\-]+)\\s.*", 1)
228+
}
229+
230+
/** Gets the @security-severity for the query. */
231+
string getQuerySecuritySeverity() {
232+
result = this.getContents().regexpCapture("(?s).*@security\\-severity ([\\d\\.]+)\\s.*", 1)
233+
}
234+
235+
/** Gets the individual @tags for the query, if any. */
236+
string getAQueryTag() {
237+
exists(string tags | tags = this.getContents().regexpCapture("(?s).*@tags ([^@]+)", 1) |
238+
result = tags.splitAt("*").trim() and
239+
result.regexpMatch("[\\w\\s\\-]+")
240+
)
241+
}
224242
}
225243

226244
class BlockComment extends TBlockComment, Comment {
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/**
2+
* @name Missing quality metadata
3+
* @description Quality queries should have exactly one top-level category and if sub-categories are used, the appropriate top-level category should be used.
4+
* @kind problem
5+
* @problem.severity warning
6+
* @precision very-high
7+
* @id ql/missing-quality-metadata
8+
* @tags correctness
9+
*/
10+
11+
import ql
12+
13+
private predicate hasQualityTag(QueryDoc doc) { doc.getAQueryTag() = "quality" }
14+
15+
private predicate correctTopLevelCategorisation(QueryDoc doc) {
16+
strictcount(string s | s = doc.getAQueryTag() and s = ["maintainability", "reliability"]) = 1
17+
}
18+
19+
private predicate reliabilitySubCategory(QueryDoc doc) {
20+
doc.getAQueryTag() = ["correctness", "performance", "concurrency", "error-handling"]
21+
}
22+
23+
private predicate maintainabilitySubCategory(QueryDoc doc) {
24+
doc.getAQueryTag() = ["readability", "useless-code", "complexity"]
25+
}
26+
27+
from TopLevel t, QueryDoc doc, string msg
28+
where
29+
doc = t.getQLDoc() and
30+
not t.getLocation().getFile() instanceof TestFile and
31+
hasQualityTag(doc) and
32+
(
33+
not correctTopLevelCategorisation(doc) and
34+
msg =
35+
"This query file has incorrect top-level categorisation. It should have exactly one top-level category, either `@tags maintainability` or `@tags reliability`."
36+
or
37+
correctTopLevelCategorisation(doc) and
38+
(
39+
doc.getAQueryTag() = "reliability" and
40+
not reliabilitySubCategory(doc) and
41+
maintainabilitySubCategory(doc) and
42+
msg =
43+
"This query file has a sub-category of maintainability but has the `@tags reliability` tag."
44+
or
45+
doc.getAQueryTag() = "maintainability" and
46+
not maintainabilitySubCategory(doc) and
47+
reliabilitySubCategory(doc) and
48+
msg =
49+
"This query file has a sub-category of reliability but has the `@tags maintainability` tag."
50+
)
51+
)
52+
select doc, msg
Lines changed: 15 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
22
* @name Missing security metadata
3-
* @description Security queries should have both a `@tag security` and a `@security-severity` tag.
3+
* @description Security queries should have both a `@tags security` and a `@security-severity` tag.
44
* @kind problem
55
* @problem.severity warning
66
* @precision very-high
@@ -10,45 +10,26 @@
1010

1111
import ql
1212

13-
predicate missingSecuritySeverity(QLDoc doc) {
14-
exists(string s | s = doc.getContents() |
15-
exists(string securityTag | securityTag = s.splitAt("@") |
16-
securityTag.matches("tags%security%")
17-
) and
18-
exists(string precisionTag | precisionTag = s.splitAt("@") |
19-
precisionTag.matches("precision %")
20-
) and
21-
not exists(string securitySeverity | securitySeverity = s.splitAt("@") |
22-
securitySeverity.matches("security-severity %")
23-
)
24-
)
13+
predicate missingSecuritySeverity(QueryDoc doc) {
14+
doc.getAQueryTag() = "security" and
15+
exists(doc.getQueryPrecision()) and
16+
not exists(doc.getQuerySecuritySeverity())
2517
}
2618

27-
predicate missingSecurityTag(QLDoc doc) {
28-
exists(string s | s = doc.getContents() |
29-
exists(string securitySeverity | securitySeverity = s.splitAt("@") |
30-
securitySeverity.matches("security-severity %")
31-
) and
32-
exists(string precisionTag | precisionTag = s.splitAt("@") |
33-
precisionTag.matches("precision %")
34-
) and
35-
not exists(string securityTag | securityTag = s.splitAt("@") |
36-
securityTag.matches("tags%security%")
37-
)
38-
)
19+
predicate missingSecurityTag(QueryDoc doc) {
20+
exists(doc.getQuerySecuritySeverity()) and
21+
exists(doc.getQueryPrecision()) and
22+
not doc.getAQueryTag() = "security"
3923
}
4024

41-
from TopLevel t, string msg
25+
from TopLevel t, QueryDoc doc, string msg
4226
where
43-
t.getLocation().getFile().getBaseName().matches("%.ql") and
44-
not t.getLocation()
45-
.getFile()
46-
.getRelativePath()
47-
.matches("%/" + ["experimental", "examples", "test"] + "/%") and
27+
doc = t.getQLDoc() and
28+
not t.getLocation().getFile() instanceof TestFile and
4829
(
49-
missingSecuritySeverity(t.getQLDoc()) and
30+
missingSecuritySeverity(doc) and
5031
msg = "This query file is missing a `@security-severity` tag."
5132
or
52-
missingSecurityTag(t.getQLDoc()) and msg = "This query file is missing a `@tag security`."
33+
missingSecurityTag(doc) and msg = "This query file is missing a `@tags security`."
5334
)
54-
select t, msg
35+
select doc, msg
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
| testcases/BadQualityMaintainabilityWrongToplevel.ql:1:1:11:3 | QueryDoc | This query file has a sub-category of reliability but has the `@tags maintainability` tag. |
2+
| testcases/BadQualityMultipleTopLevel.ql:1:1:11:3 | QueryDoc | This query file has incorrect top-level categorisation. It should have exactly one top-level category, either `@tags maintainability` or `@tags reliability`. |
3+
| testcases/BadQualityNoToplevel.ql:1:1:10:3 | QueryDoc | This query file has incorrect top-level categorisation. It should have exactly one top-level category, either `@tags maintainability` or `@tags reliability`. |
4+
| testcases/BadQualityReliabilityWrongToplevel.ql:1:1:11:3 | QueryDoc | This query file has a sub-category of maintainability but has the `@tags reliability` tag. |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
queries/style/MissingQualityMetadata.ql

ql/ql/test/queries/style/MissingQualityMetadata/testcases/BadQualityMaintainabilityWrongToplevel.expected

Whitespace-only changes.

0 commit comments

Comments
 (0)