Skip to content

Commit 68ca8e2

Browse files
committed
introduce consistency-checking utility predicates
1 parent c7c46ea commit 68ca8e2

File tree

1 file changed

+160
-0
lines changed

1 file changed

+160
-0
lines changed
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
import javascript
2+
3+
/**
4+
* A configuration for consistency checking.
5+
* Used to specify where the alerts are (the positives)
6+
* And which files should be included in the consistency-check.
7+
*
8+
* If no configuration is specified, then the default is that the all sinks from a `DataFlow::Configuration` are alerts, and all files are consistency-checked.
9+
*/
10+
abstract class ConsistencyConfiguration extends string {
11+
bindingset[this]
12+
ConsistencyConfiguration() { any() }
13+
14+
/**
15+
* Gets an alert that should be checked for consistency.
16+
* The alert must match with a `NOT OK` comment.
17+
*
18+
* And likewise a `OK` comment must not have a corresponding alert on the same line.
19+
*/
20+
DataFlow::Node getAnAlert() { result = getASink() }
21+
22+
/**
23+
* Gets a file to include in the consistency checking.
24+
*/
25+
File getAFile() { none() }
26+
}
27+
28+
/**
29+
* A line-comment that asserts whether a result exists at that line or not.
30+
* Can optionally include `[INCONSISTENCY]` to indicate that a consistency issue is expected at the location
31+
*/
32+
private class AssertionComment extends LineComment {
33+
boolean shouldHaveAlert;
34+
35+
AssertionComment() {
36+
if getText().regexpMatch("\\s*(NOT OK|BAD).*")
37+
then shouldHaveAlert = true
38+
else (
39+
getText().regexpMatch("\\s*(OK|GOOD).*") and shouldHaveAlert = false
40+
)
41+
}
42+
43+
/**
44+
* Holds if there should be an alert at this location
45+
*/
46+
predicate shouldHaveAlert() { shouldHaveAlert = true }
47+
48+
/**
49+
* Holds if a consistency issue is expected at this location.
50+
*/
51+
predicate expectConsistencyError() { getText().matches(["%[INCONSISTENCY]%"]) }
52+
}
53+
54+
private DataFlow::Node getASink() { exists(DataFlow::Configuration cfg | cfg.hasFlow(_, result)) }
55+
56+
/**
57+
* Gets all the alerts for consistency consistency checking.
58+
*/
59+
private DataFlow::Node alerts() {
60+
result = any(ConsistencyConfiguration res).getAnAlert()
61+
or
62+
not exists(ConsistencyConfiguration r) and
63+
result = getASink()
64+
}
65+
66+
/**
67+
* Gets an alert in `file` at `line`.
68+
* The `line` can be either the first or the last line of the alert.
69+
* And if no expression exists at `line`, then an alert on the next line is used.
70+
*/
71+
private DataFlow::Node getAlert(File file, int line) {
72+
result = alerts() and
73+
result.getFile() = file and
74+
(result.hasLocationInfo(_, _, _, line, _) or result.hasLocationInfo(_, line, _, _, _))
75+
or
76+
// The comment can be right above the result, so an alert also counts for the line above.
77+
not exists(Expr e |
78+
e.getFile() = file and [e.getLocation().getStartLine(), e.getLocation().getEndLine()] = line
79+
) and
80+
result = alerts() and
81+
result.getFile() = file and
82+
result.hasLocationInfo(_, line + 1, _, _, _)
83+
}
84+
85+
/**
86+
* Gets a comment that asserts either the existence or the absence of an alert in `file` at `line`.
87+
*/
88+
private AssertionComment getComment(File file, int line) {
89+
result.getLocation().getEndLine() = line and
90+
result.getFile() = file
91+
}
92+
93+
/**
94+
* Holds if there is a false positive in `file` at `line`
95+
*/
96+
private predicate falsePositive(File file, int line, AssertionComment comment) {
97+
exists(getAlert(file, line)) and
98+
comment = getComment(file, line) and
99+
not comment.shouldHaveAlert()
100+
}
101+
102+
/**
103+
* Holds if there is a false negative in `file` at `line`
104+
*/
105+
private predicate falseNegative(File file, int line, AssertionComment comment) {
106+
not exists(getAlert(file, line)) and
107+
comment = getComment(file, line) and
108+
comment.shouldHaveAlert()
109+
}
110+
111+
/**
112+
* Gets a file that should be included for consistency checking.
113+
*/
114+
private File getATestFile() {
115+
not exists(any(ConsistencyConfiguration res).getAFile()) and
116+
result = any(LineComment comment).getFile()
117+
or
118+
result = any(ConsistencyConfiguration res).getAFile()
119+
}
120+
121+
/**
122+
* Gets a description of the configuration that has a sink in `file` at `line`.
123+
* Or the empty string
124+
*/
125+
bindingset[file, line]
126+
private string getSinkDescription(File file, int line) {
127+
not exists(DataFlow::Configuration c | c.hasFlow(_, getAlert(file, line))) and result = ""
128+
or
129+
exists(DataFlow::Configuration c | c.hasFlow(_, getAlert(file, line)) |
130+
result = " for " + c
131+
)
132+
}
133+
134+
/**
135+
* Holds if there is a consistency-issue at `location` with description `msg`.
136+
* The consistency issue an unexpected false positive/negative.
137+
* Or that false positive/negative was expected, and none were found.
138+
*/
139+
query predicate consistencyIssue(string location, string msg, string commentText) {
140+
exists(File file, int line |
141+
file = getATestFile() and location = file.getRelativePath() + ":" + line
142+
|
143+
exists(AssertionComment comment |
144+
comment.getText().trim() = commentText and comment = getComment(file, line)
145+
|
146+
falsePositive(file, line, comment) and
147+
not comment.expectConsistencyError() and
148+
msg = "did not expected an alert, but found an alert" + getSinkDescription(file, line)
149+
or
150+
falseNegative(file, line, comment) and
151+
not comment.expectConsistencyError() and
152+
msg = "expected an alert, but found none"
153+
or
154+
not falsePositive(file, line, comment) and
155+
not falseNegative(file, line, comment) and
156+
comment.expectConsistencyError() and
157+
msg = "expected consistency issue, but found no such issue (" + comment.getText().trim() + ")"
158+
)
159+
)
160+
}

0 commit comments

Comments
 (0)