Skip to content

Commit 76fd710

Browse files
authored
Merge pull request github#4571 from MathiasVP/better-syntax-for-false-positives-and-negatives-inline-expectation
C++/Python: Better syntax for false positives and negatives in inline expectations
2 parents 89361a3 + 5680b2d commit 76fd710

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+489
-376
lines changed

cpp/ql/test/TestUtilities/InlineExpectationsTest.qll

Lines changed: 96 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -43,15 +43,15 @@
4343
* There is no need to write a `select` clause or query predicate. All of the differences between
4444
* expected results and actual results will be reported in the `failures()` query predicate.
4545
*
46-
* To annotate the test source code with an expected result, place a comment on the
46+
* To annotate the test source code with an expected result, place a comment starting with a `$` on the
4747
* same line as the expected result, with text of the following format as the body of the comment:
4848
*
49-
* `$tag=expected-value`
49+
* `tag=expected-value`
5050
*
5151
* Where `tag` is the value of the `tag` parameter from `hasActualResult()`, and `expected-value` is
5252
* the value of the `value` parameter from `hasActualResult()`. The `=expected-value` portion may be
5353
* omitted, in which case `expected-value` is treated as the empty string. Multiple expectations may
54-
* be placed in the same comment, as long as each is prefixed by a `$`. Any actual result that
54+
* be placed in the same comment. Any actual result that
5555
* appears on a line that does not contain a matching expected result comment will be reported with
5656
* a message of the form "Unexpected result: tag=value". Any expected result comment for which there
5757
* is no matching actual result will be reported with a message of the form
@@ -60,30 +60,33 @@
6060
* Example:
6161
* ```cpp
6262
* int i = x + 5; // $const=5
63-
* int j = y + (7 - 3) // $const=7 $const=3 $const=4 // The result of the subtraction is a constant.
63+
* int j = y + (7 - 3) // $const=7 const=3 const=4 // The result of the subtraction is a constant.
6464
* ```
6565
*
66-
* For tests that contain known false positives and false negatives, it is possible to further
67-
* annotate that a particular expected result is known to be a false positive, or that a particular
68-
* missing result is known to be a false negative:
66+
* For tests that contain known missing and spurious results, it is possible to further
67+
* annotate that a particular expected result is known to be spurious, or that a particular
68+
* missing result is known to be missing:
6969
*
70-
* `$f+:tag=expected-value` // False positive
71-
* `$f-:tag=expected-value` // False negative
70+
* `$ SPURIOUS: tag=expected-value` // Spurious result
71+
* `$ MISSING: tag=expected-value` // Missing result
7272
*
73-
* A false positive expectation is treated as any other expected result, except that if there is no
74-
* matching actual result, the message will be of the form "Fixed false positive: tag=value". A
75-
* false negative expectation is treated as if there were no expected result, except that if a
73+
* A spurious expectation is treated as any other expected result, except that if there is no
74+
* matching actual result, the message will be of the form "Fixed spurious result: tag=value". A
75+
* missing expectation is treated as if there were no expected result, except that if a
7676
* matching expected result is found, the message will be of the form
77-
* "Fixed false negative: tag=value".
77+
* "Fixed missing result: tag=value".
78+
*
79+
* A single line can contain all the expected, spurious and missing results of that line. For instance:
80+
* `$ tag1=value1 SPURIOUS: tag2=value2 MISSING: tag3=value3`.
7881
*
7982
* If the same result value is expected for two or more tags on the same line, there is a shorthand
8083
* notation available:
8184
*
82-
* `$tag1,tag2=expected-value`
85+
* `tag1,tag2=expected-value`
8386
*
8487
* is equivalent to:
8588
*
86-
* `$tag1=expected-value $tag2=expected-value`
89+
* `tag1=expected-value tag2=expected-value`
8790
*/
8891

8992
private import InlineExpectationsTestPrivate
@@ -126,7 +129,7 @@ abstract class InlineExpectationsTest extends string {
126129
(
127130
exists(FalseNegativeExpectation falseNegative |
128131
falseNegative.matchesActualResult(actualResult) and
129-
message = "Fixed false negative:" + falseNegative.getExpectationText()
132+
message = "Fixed missing result:" + falseNegative.getExpectationText()
130133
)
131134
or
132135
not exists(ValidExpectation expectation | expectation.matchesActualResult(actualResult)) and
@@ -143,7 +146,7 @@ abstract class InlineExpectationsTest extends string {
143146
message = "Missing result:" + expectation.getExpectationText()
144147
or
145148
expectation instanceof FalsePositiveExpectation and
146-
message = "Fixed false positive:" + expectation.getExpectationText()
149+
message = "Fixed spurious result:" + expectation.getExpectationText()
147150
)
148151
)
149152
or
@@ -160,20 +163,79 @@ abstract class InlineExpectationsTest extends string {
160163
* is treated as part of the expected results, except that the comment may contain a `//` sequence
161164
* to treat the remainder of the line as a regular (non-interpreted) comment.
162165
*/
163-
private string expectationCommentPattern() { result = "\\s*(\\$(?:[^/]|/[^/])*)(?://.*)?" }
166+
private string expectationCommentPattern() { result = "\\s*\\$((?:[^/]|/[^/])*)(?://.*)?" }
164167

165168
/**
166-
* RegEx pattern to match a single expected result, not including the leading `$`. It starts with an
167-
* optional `f+:` or `f-:`, followed by one or more comma-separated tags containing only letters,
168-
* `-`, and `_`, optionally followed by `=` and the expected value.
169+
* The possible columns in an expectation comment. The `TDefaultColumn` branch represents the first
170+
* column in a comment. This column is not precedeeded by a name. `TNamedColumn(name)` represents a
171+
* column containing expected results preceeded by the string `name:`.
169172
*/
170-
private string expectationPattern() {
171-
result = "(?:(f(?:\\+|-)):)?((?:[A-Za-z-_]+)(?:\\s*,\\s*[A-Za-z-_]+)*)(?:=(.*))?"
173+
private newtype TColumn =
174+
TDefaultColumn() or
175+
TNamedColumn(string name) { name = ["MISSING", "SPURIOUS"] }
176+
177+
bindingset[start, content]
178+
private int getEndOfColumnPosition(int start, string content) {
179+
result =
180+
min(string name, int cand |
181+
exists(TNamedColumn(name)) and
182+
cand = content.indexOf(name + ":") and
183+
cand > start
184+
|
185+
cand
186+
)
187+
or
188+
not exists(string name |
189+
exists(TNamedColumn(name)) and
190+
content.indexOf(name + ":") > start
191+
) and
192+
result = content.length()
193+
}
194+
195+
private predicate getAnExpectation(
196+
LineComment comment, TColumn column, string expectation, string tags, string value
197+
) {
198+
exists(string content |
199+
content = comment.getContents().regexpCapture(expectationCommentPattern(), 1) and
200+
(
201+
column = TDefaultColumn() and
202+
exists(int end |
203+
end = getEndOfColumnPosition(0, content) and
204+
expectation = content.prefix(end).regexpFind(expectationPattern(), _, _).trim()
205+
)
206+
or
207+
exists(string name, int start, int end |
208+
column = TNamedColumn(name) and
209+
start = content.indexOf(name + ":") + name.length() + 1 and
210+
end = getEndOfColumnPosition(start, content) and
211+
expectation = content.substring(start, end).regexpFind(expectationPattern(), _, _).trim()
212+
)
213+
)
214+
) and
215+
tags = expectation.regexpCapture(expectationPattern(), 1) and
216+
if exists(expectation.regexpCapture(expectationPattern(), 2))
217+
then value = expectation.regexpCapture(expectationPattern(), 2)
218+
else value = ""
172219
}
173220

174-
private string getAnExpectation(LineComment comment) {
175-
result = comment.getContents().regexpCapture(expectationCommentPattern(), 1).splitAt("$").trim() and
176-
result != ""
221+
private string getColumnString(TColumn column) {
222+
column = TDefaultColumn() and result = ""
223+
or
224+
column = TNamedColumn(result)
225+
}
226+
227+
/**
228+
* RegEx pattern to match a single expected result, not including the leading `$`. It consists of one or
229+
* more comma-separated tags containing only letters, digits, `-` and `_` (note that the first character
230+
* must not be a digit), optionally followed by `=` and the expected value.
231+
*/
232+
private string expectationPattern() {
233+
exists(string tag, string tags, string value |
234+
tag = "[A-Za-z-_][A-Za-z-_0-9]*" and
235+
tags = "((?:" + tag + ")(?:\\s*,\\s*" + tag + ")*)" and
236+
value = "((?:\"[^\"]*\"|'[^']*'|\\S+)*)" and
237+
result = tags + "(?:=" + value + ")?"
238+
)
177239
}
178240

179241
private newtype TFailureLocatable =
@@ -183,24 +245,14 @@ private newtype TFailureLocatable =
183245
test.hasActualResult(___location, element, tag, value)
184246
} or
185247
TValidExpectation(LineComment comment, string tag, string value, string knownFailure) {
186-
exists(string expectation |
187-
expectation = getAnExpectation(comment) and
188-
expectation.regexpMatch(expectationPattern()) and
189-
tag = expectation.regexpCapture(expectationPattern(), 2).splitAt(",").trim() and
190-
(
191-
if exists(expectation.regexpCapture(expectationPattern(), 3))
192-
then value = expectation.regexpCapture(expectationPattern(), 3)
193-
else value = ""
194-
) and
195-
(
196-
if exists(expectation.regexpCapture(expectationPattern(), 1))
197-
then knownFailure = expectation.regexpCapture(expectationPattern(), 1)
198-
else knownFailure = ""
199-
)
248+
exists(TColumn column, string tags |
249+
getAnExpectation(comment, column, _, tags, value) and
250+
tag = tags.splitAt(",") and
251+
knownFailure = getColumnString(column)
200252
)
201253
} or
202254
TInvalidExpectation(LineComment comment, string expectation) {
203-
expectation = getAnExpectation(comment) and
255+
getAnExpectation(comment, _, expectation, _, _) and
204256
not expectation.regexpMatch(expectationPattern())
205257
}
206258

@@ -265,16 +317,17 @@ private class ValidExpectation extends Expectation, TValidExpectation {
265317
}
266318
}
267319

320+
/* Note: These next three classes correspond to all the possible values of type `TColumn`. */
268321
class GoodExpectation extends ValidExpectation {
269322
GoodExpectation() { getKnownFailure() = "" }
270323
}
271324

272325
class FalsePositiveExpectation extends ValidExpectation {
273-
FalsePositiveExpectation() { getKnownFailure() = "f+" }
326+
FalsePositiveExpectation() { getKnownFailure() = "SPURIOUS" }
274327
}
275328

276329
class FalseNegativeExpectation extends ValidExpectation {
277-
FalseNegativeExpectation() { getKnownFailure() = "f-" }
330+
FalseNegativeExpectation() { getKnownFailure() = "MISSING" }
278331
}
279332

280333
class InvalidExpectation extends Expectation, TInvalidExpectation {

cpp/ql/test/library-tests/dataflow/fields/A.cpp

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -40,21 +40,21 @@ class A
4040
cc.insert(nullptr);
4141
ct.insert(new C());
4242
sink(&cc); // no flow
43-
sink(&ct); // $ast $f-:ir
43+
sink(&ct); // $ ast MISSING: ir
4444
}
4545
void f1()
4646
{
4747
C *c = new C();
4848
B *b = B::make(c);
49-
sink(b->c); // $ast $f-:ir
49+
sink(b->c); // $ast MISSING: ir
5050
}
5151

5252
void f2()
5353
{
5454
B *b = new B();
5555
b->set(new C1());
56-
sink(b->get()); // $ast $ir=55:12
57-
sink((new B(new C()))->get()); // $ast $ir
56+
sink(b->get()); // $ ast ir=55:12
57+
sink((new B(new C()))->get()); // $ ast ir
5858
}
5959

6060
void f3()
@@ -63,7 +63,7 @@ class A
6363
B *b2;
6464
b2 = setOnB(b1, new C2());
6565
sink(b1->c); // no flow
66-
sink(b2->c); // $ast $f-:ir
66+
sink(b2->c); // $ ast MISSING: ir
6767
}
6868

6969
void f4()
@@ -72,7 +72,7 @@ class A
7272
B *b2;
7373
b2 = setOnBWrap(b1, new C2());
7474
sink(b1->c); // no flow
75-
sink(b2->c); // $ast $f-:ir
75+
sink(b2->c); // $ ast MISSING: ir
7676
}
7777

7878
B *setOnBWrap(B *b1, C *c)
@@ -104,7 +104,7 @@ class A
104104
{
105105
if (C1 *c1 = dynamic_cast<C1 *>(c))
106106
{
107-
sink(c1->a); // $ast,ir
107+
sink(c1->a); // $ ast,ir
108108
}
109109
C *cc;
110110
if (C2 *c2 = dynamic_cast<C2 *>(c))
@@ -117,7 +117,7 @@ class A
117117
}
118118
if (C1 *c1 = dynamic_cast<C1 *>(cc))
119119
{
120-
sink(c1->a); //$f+:ast
120+
sink(c1->a); // $ SPURIOUS: ast
121121
}
122122
}
123123

@@ -129,7 +129,7 @@ class A
129129
{
130130
B *b = new B();
131131
f7(b);
132-
sink(b->c); // $ast,ir
132+
sink(b->c); // $ ast,ir
133133
}
134134

135135
class D
@@ -149,9 +149,9 @@ class A
149149
{
150150
B *b = new B();
151151
D *d = new D(b, r());
152-
sink(d->b); // $ast,ir=143:25 $ast,ir=150:12
153-
sink(d->b->c); // $ast $f-:ir
154-
sink(b->c); // $ast,ir
152+
sink(d->b); // $ ast,ir=143:25 ast,ir=150:12
153+
sink(d->b->c); // $ ast MISSING: ir
154+
sink(b->c); // $ ast,ir
155155
}
156156

157157
void f10()
@@ -162,11 +162,11 @@ class A
162162
MyList *l3 = new MyList(nullptr, l2);
163163
sink(l3->head); // no flow, b is nested beneath at least one ->next
164164
sink(l3->next->head); // no flow
165-
sink(l3->next->next->head); // $ast $f-:ir
165+
sink(l3->next->next->head); // $ ast MISSING: ir
166166
sink(l3->next->next->next->head); // no flow
167167
for (MyList *l = l3; l != nullptr; l = l->next)
168168
{
169-
sink(l->head); // $ast $f-:ir
169+
sink(l->head); // $ ast MISSING: ir
170170
}
171171
}
172172

cpp/ql/test/library-tests/dataflow/fields/B.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ class B
66
Elem *e = new Elem();
77
Box1 *b1 = new Box1(e, nullptr);
88
Box2 *b2 = new Box2(b1);
9-
sink(b2->box1->elem1); // $ast $f-:ir
9+
sink(b2->box1->elem1); // $ ast MISSING: ir
1010
sink(b2->box1->elem2); // no flow
1111
}
1212

@@ -16,7 +16,7 @@ class B
1616
Box1 *b1 = new B::Box1(nullptr, e);
1717
Box2 *b2 = new Box2(b1);
1818
sink(b2->box1->elem1); // no flow
19-
sink(b2->box1->elem2); // $ast $f-:ir
19+
sink(b2->box1->elem2); // $ ast MISSING: ir
2020
}
2121

2222
static void sink(void *o) {}

cpp/ql/test/library-tests/dataflow/fields/C.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,10 @@ class C
2626

2727
void func()
2828
{
29-
sink(s1); // $ast $ir
30-
sink(s2); // $f-:ast $f-:ir
31-
sink(s3); // $ast $ir
32-
sink(s4); // $f-:ast $f-:ir
29+
sink(s1); // $ast ir
30+
sink(s2); // $ MISSING: ast,ir
31+
sink(s3); // $ast ir
32+
sink(s4); // $ MISSING: ast,ir
3333
}
3434

3535
static void sink(const void *o) {}

cpp/ql/test/library-tests/dataflow/fields/D.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class D {
1919
};
2020

2121
static void sinkWrap(Box2* b2) {
22-
sink(b2->getBox1()->getElem()); // $ast=28:15 $ast=35:15 $ast=42:15 $ast=49:15 $f-:ir
22+
sink(b2->getBox1()->getElem()); // $ast=28:15 ast=35:15 ast=42:15 ast=49:15 MISSING: ir
2323
}
2424

2525
Box2* boxfield;
@@ -61,6 +61,6 @@ class D {
6161

6262
private:
6363
void f5b() {
64-
sink(boxfield->box->elem); // $ast $f-:ir
64+
sink(boxfield->box->elem); // $ ast MISSING: ir
6565
}
6666
};

cpp/ql/test/library-tests/dataflow/fields/E.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ void sink(char *b);
1818

1919
void handlePacket(packet *p)
2020
{
21-
sink(p->data.buffer); // $ast $f-:ir
21+
sink(p->data.buffer); // $ ast MISSING: ir
2222
}
2323

2424
void f(buf* b)
@@ -28,7 +28,7 @@ void f(buf* b)
2828
argument_source(raw);
2929
argument_source(b->buffer);
3030
argument_source(p.data.buffer);
31-
sink(raw); // $ast $f-:ir
32-
sink(b->buffer); // $ast $f-:ir
31+
sink(raw); // $ ast MISSING: ir
32+
sink(b->buffer); // $ ast MISSING: ir
3333
handlePacket(&p);
3434
}

0 commit comments

Comments
 (0)