11/**
22 * A test query that verifies assertions about the API graph embedded in source-code comments.
33 *
4- * An assertion is a comment of the form `def <path>` or `use <path>`, and asserts that
5- * there is a def/use feature reachable from the root along the given path (described using
6- * s-expression syntax), and its associated data-flow node must start on the same line as the
7- * comment.
4+ * An assertion is a comment of the form `def=<path>` or `use=<path>`, and asserts that
5+ * there is a def/use feature reachable from the root along the given path, and its
6+ * associated data-flow node must start on the same line as the comment.
87 *
9- * We also support negative assertions of the form `! def <path>` or `! use <path>`, which assert
8+ * We also support negative assertions of the form `MISSING: def <path>` or `MISSING: use <path>`, which assert
109 * that there _isn't_ a node with the given path on the same line.
1110 *
1211 * The query only produces output for failed assertions, meaning that it should have no output
@@ -39,44 +38,55 @@ private string getLoc(DataFlow::Node nd) {
3938 * An assertion matching a data-flow node against an API-graph feature.
4039 */
4140class Assertion extends Comment {
42- string polarity ;
4341 string expectedKind ;
4442 string expectedLoc ;
43+ string path ;
44+ string polarity ;
4545
4646 Assertion ( ) {
4747 exists ( string txt , string rex |
4848 txt = this .getText ( ) .trim ( ) and
49- rex = "(!?) (def|use) .*"
49+ rex = ".*?((?:MISSING: )?) (def|use)=([\\w\\(\\)\"\\.\\-\\/\\@\\:]*) .*"
5050 |
5151 polarity = txt .regexpCapture ( rex , 1 ) and
5252 expectedKind = txt .regexpCapture ( rex , 2 ) and
53+ path = txt .regexpCapture ( rex , 3 ) and
5354 expectedLoc = this .getFile ( ) .getAbsolutePath ( ) + ":" + this .getLocation ( ) .getStartLine ( )
5455 )
5556 }
5657
57- string getEdgeLabel ( int i ) { result = this .getText ( ) .regexpFind ( "(?<=\\()[^()]+" , i , _) .trim ( ) }
58+ string getEdgeLabel ( int i ) {
59+ // matches a single edge. E.g. `getParameter(1)` or `getMember("foo")`.
60+ // The lookbehind/lookahead ensure that the boundary is correct, that is
61+ // either the edge is next to a ".", or it's the end of the string.
62+ result = path .regexpFind ( "(?<=\\.|^)([\\w\\(\\)\"\\-\\/\\@\\:]+)(?=\\.|$)" , i , _) .trim ( )
63+ }
5864
5965 int getPathLength ( ) { result = max ( int i | exists ( this .getEdgeLabel ( i ) ) ) + 1 }
6066
67+ predicate isNegative ( ) { polarity = "MISSING: " }
68+
6169 API:: Node lookup ( int i ) {
62- i = this . getPathLength ( ) and
70+ i = 0 and
6371 result = API:: root ( )
6472 or
6573 result =
66- this .lookup ( i + 1 )
67- .getASuccessor ( any ( API:: Label:: ApiLabel label | label .toString ( ) = this .getEdgeLabel ( i ) ) )
74+ this .lookup ( i - 1 )
75+ .getASuccessor ( any ( API:: Label:: ApiLabel label |
76+ label .toString ( ) = this .getEdgeLabel ( i - 1 )
77+ ) )
6878 }
6979
70- predicate isNegative ( ) { polarity = "!" }
80+ API :: Node lookup ( ) { result = this . lookup ( this . getPathLength ( ) ) }
7181
72- predicate holds ( ) { getLoc ( getNode ( this .lookup ( 0 ) , expectedKind ) ) = expectedLoc }
82+ predicate holds ( ) { getLoc ( getNode ( this .lookup ( ) , expectedKind ) ) = expectedLoc }
7383
7484 string tryExplainFailure ( ) {
7585 exists ( int i , API:: Node nd , string prefix , string suffix |
7686 nd = this .lookup ( i ) and
77- i > 0 and
78- not exists ( this .lookup ( [ 0 .. i - 1 ] ) ) and
79- prefix = nd + " has no outgoing edge labelled " + this .getEdgeLabel ( i - 1 ) + ";" and
87+ i < getPathLength ( ) and
88+ not exists ( this .lookup ( [ i + 1 .. getPathLength ( ) ] ) ) and
89+ prefix = nd + " has no outgoing edge labelled " + this .getEdgeLabel ( i ) + ";" and
8090 if exists ( nd .getASuccessor ( ) )
8191 then
8292 suffix =
@@ -91,13 +101,13 @@ class Assertion extends Comment {
91101 result = prefix + " " + suffix
92102 )
93103 or
94- exists ( API:: Node nd , string kind | nd = this .lookup ( 0 ) |
104+ exists ( API:: Node nd , string kind | nd = this .lookup ( ) |
95105 exists ( getNode ( nd , kind ) ) and
96106 not exists ( getNode ( nd , expectedKind ) ) and
97107 result = "Expected " + expectedKind + " node, but found " + kind + " node."
98108 )
99109 or
100- exists ( DataFlow:: Node nd | nd = getNode ( this .lookup ( 0 ) , expectedKind ) |
110+ exists ( DataFlow:: Node nd | nd = getNode ( this .lookup ( ) , expectedKind ) |
101111 not getLoc ( nd ) = expectedLoc and
102112 result = "Node not found on this line (but there is one on line " + min ( getLoc ( nd ) ) + ")."
103113 )
0 commit comments