diff --git a/cpp/ql/src/semmle/code/cpp/XML.qll b/cpp/ql/src/semmle/code/cpp/XML.qll index 5871fed0dddd..039244d32817 100755 --- a/cpp/ql/src/semmle/code/cpp/XML.qll +++ b/cpp/ql/src/semmle/code/cpp/XML.qll @@ -341,3 +341,63 @@ class XMLCharacters extends @xmlcharacters, XMLLocatable { /** Gets a printable representation of this XML character sequence. */ override string toString() { result = this.getCharacters() } } + +/** + * An alert suppression comment. + */ +class XMLSuppressionComment extends XMLComment { + string annotation; + + XMLSuppressionComment() { + // suppression comments must be single-line + not getText().matches("%\n%") and + ( + // match `lgtm[...]` anywhere in the comment + annotation = getText().regexpFind("(?i)\\blgtm\\s*\\[[^\\]]*\\]", _, _) + or + // match `lgtm` at the start of the comment and after semicolon + annotation = getText().regexpFind("(?i)(?<=^|;)\\s*lgtm(?!\\B|\\s*\\[)", _, _).trim() + ) + } + + /** Gets the suppression annotation in this comment. */ + string getAnnotation() { result = annotation } + + /** + * Holds if this comment applies to the range from column `startcolumn` of line `startline` + * to column `endcolumn` of line `endline` in file `filepath`. + */ + predicate covers(string filepath, int startline, int startcolumn, int endline, int endcolumn) { + this.getLocation().hasLocationInfo(filepath, startline, _, endline, endcolumn) and + startcolumn = 1 + } + + /** Gets the scope of this suppression. */ + XMLSuppressionScope getScope() { this = result.getSuppressionComment() } +} + +/** + * The scope of an alert suppression comment. + */ +class XMLSuppressionScope extends @xmlcomment { + XMLSuppressionScope() { this instanceof XMLSuppressionComment } + + /** Gets a suppression comment with this scope. */ + XMLSuppressionComment getSuppressionComment() { result = this } + + /** + * Holds if this element is at the specified location. + * The location spans column `startcolumn` of line `startline` to + * column `endcolumn` of line `endline` in file `filepath`. + * For more information, see + * [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html). + */ + predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + this.(XMLSuppressionComment).covers(filepath, startline, startcolumn, endline, endcolumn) + } + + /** Gets a textual representation of this element. */ + string toString() { result = "suppression range" } +} diff --git a/csharp/ql/src/XMLAlertSuppression.ql b/csharp/ql/src/XMLAlertSuppression.ql new file mode 100644 index 000000000000..7f55e8ab1739 --- /dev/null +++ b/csharp/ql/src/XMLAlertSuppression.ql @@ -0,0 +1,14 @@ +/** + * @name XML alert suppression + * @description Generates information about alert suppressions in XML files. + * @kind alert-suppression + * @id cs/xml-alert-suppression + */ + +import semmle.code.csharp.XML + +from XMLSuppressionComment c +select c, // suppression comment + c.getText(), // text of suppression comment (excluding delimiters) + c.getAnnotation(), // text of suppression annotation + c.getScope() // scope of suppression diff --git a/csharp/ql/src/semmle/code/csharp/XML.qll b/csharp/ql/src/semmle/code/csharp/XML.qll index 5871fed0dddd..039244d32817 100755 --- a/csharp/ql/src/semmle/code/csharp/XML.qll +++ b/csharp/ql/src/semmle/code/csharp/XML.qll @@ -341,3 +341,63 @@ class XMLCharacters extends @xmlcharacters, XMLLocatable { /** Gets a printable representation of this XML character sequence. */ override string toString() { result = this.getCharacters() } } + +/** + * An alert suppression comment. + */ +class XMLSuppressionComment extends XMLComment { + string annotation; + + XMLSuppressionComment() { + // suppression comments must be single-line + not getText().matches("%\n%") and + ( + // match `lgtm[...]` anywhere in the comment + annotation = getText().regexpFind("(?i)\\blgtm\\s*\\[[^\\]]*\\]", _, _) + or + // match `lgtm` at the start of the comment and after semicolon + annotation = getText().regexpFind("(?i)(?<=^|;)\\s*lgtm(?!\\B|\\s*\\[)", _, _).trim() + ) + } + + /** Gets the suppression annotation in this comment. */ + string getAnnotation() { result = annotation } + + /** + * Holds if this comment applies to the range from column `startcolumn` of line `startline` + * to column `endcolumn` of line `endline` in file `filepath`. + */ + predicate covers(string filepath, int startline, int startcolumn, int endline, int endcolumn) { + this.getLocation().hasLocationInfo(filepath, startline, _, endline, endcolumn) and + startcolumn = 1 + } + + /** Gets the scope of this suppression. */ + XMLSuppressionScope getScope() { this = result.getSuppressionComment() } +} + +/** + * The scope of an alert suppression comment. + */ +class XMLSuppressionScope extends @xmlcomment { + XMLSuppressionScope() { this instanceof XMLSuppressionComment } + + /** Gets a suppression comment with this scope. */ + XMLSuppressionComment getSuppressionComment() { result = this } + + /** + * Holds if this element is at the specified location. + * The location spans column `startcolumn` of line `startline` to + * column `endcolumn` of line `endline` in file `filepath`. + * For more information, see + * [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html). + */ + predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + this.(XMLSuppressionComment).covers(filepath, startline, startcolumn, endline, endcolumn) + } + + /** Gets a textual representation of this element. */ + string toString() { result = "suppression range" } +} diff --git a/csharp/ql/test/query-tests/AlertSuppression/Web.config b/csharp/ql/test/query-tests/AlertSuppression/Web.config new file mode 100644 index 000000000000..48ed61a50479 --- /dev/null +++ b/csharp/ql/test/query-tests/AlertSuppression/Web.config @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/csharp/ql/test/query-tests/AlertSuppression/XMLAlertSuppression.expected b/csharp/ql/test/query-tests/AlertSuppression/XMLAlertSuppression.expected new file mode 100644 index 000000000000..259b450fe34b --- /dev/null +++ b/csharp/ql/test/query-tests/AlertSuppression/XMLAlertSuppression.expected @@ -0,0 +1,24 @@ +| Web.config:1:1:2:1 | lgtm | lgtm | lgtm | Web.config:1:1:2:1 | suppression range | +| Web.config:2:1:3:1 | lgtm[cs/unused-reftype] | lgtm[cs/unused-reftype] | lgtm[cs/unused-reftype] | Web.config:2:1:3:1 | suppression range | +| Web.config:3:1:4:1 | lgtm[cs/unused-reftype, cs/unused-field] | lgtm[cs/unused-reftype, cs/unused-field] | lgtm[cs/unused-reftype, cs/unused-field] | Web.config:3:1:4:1 | suppression range | +| Web.config:4:1:5:1 | lgtm[@tag:nullness] | lgtm[@tag:nullness] | lgtm[@tag:nullness] | Web.config:4:1:5:1 | suppression range | +| Web.config:5:1:6:1 | lgtm[@tag:useless-code,cs/unused-reftype] | lgtm[@tag:useless-code,cs/unused-reftype] | lgtm[@tag:useless-code,cs/unused-reftype] | Web.config:5:1:6:1 | suppression range | +| Web.config:6:1:7:1 | lgtm[@expires:2017-06-11] | lgtm[@expires:2017-06-11] | lgtm[@expires:2017-06-11] | Web.config:6:1:7:1 | suppression range | +| Web.config:7:1:8:1 | lgtm[cs/unused-reftype] because I know better than lgtm | lgtm[cs/unused-reftype] because I know better than lgtm | lgtm[cs/unused-reftype] | Web.config:7:1:8:1 | suppression range | +| Web.config:8:1:9:1 | lgtm: blah blah | lgtm: blah blah | lgtm | Web.config:8:1:9:1 | suppression range | +| Web.config:9:1:10:1 | lgtm blah blah #falsepositive | lgtm blah blah #falsepositive | lgtm | Web.config:9:1:10:1 | suppression range | +| Web.config:10:1:11:1 | lgtm [cs/unused-reftype] | lgtm [cs/unused-reftype] | lgtm [cs/unused-reftype] | Web.config:10:1:11:1 | suppression range | +| Web.config:11:1:12:1 | lgtm[] | lgtm[] | lgtm[] | Web.config:11:1:12:1 | suppression range | +| Web.config:13:1:14:1 | lgtm | lgtm | lgtm | Web.config:13:1:14:1 | suppression range | +| Web.config:14:1:15:1 | lgtm | lgtm | lgtm | Web.config:14:1:15:1 | suppression range | +| Web.config:15:1:16:1 | lgtm [cs/unused-reftype] | lgtm [cs/unused-reftype] | lgtm [cs/unused-reftype] | Web.config:15:1:16:1 | suppression range | +| Web.config:18:1:19:1 | foo; lgtm | foo; lgtm | lgtm | Web.config:18:1:19:1 | suppression range | +| Web.config:19:1:20:1 | foo; lgtm[cs/unused-reftype] | foo; lgtm[cs/unused-reftype] | lgtm[cs/unused-reftype] | Web.config:19:1:20:1 | suppression range | +| Web.config:21:1:22:1 | foo lgtm[cs/unused-reftype] | foo lgtm[cs/unused-reftype] | lgtm[cs/unused-reftype] | Web.config:21:1:22:1 | suppression range | +| Web.config:23:1:24:1 | foo lgtm[cs/unused-reftype] bar | foo lgtm[cs/unused-reftype] bar | lgtm[cs/unused-reftype] | Web.config:23:1:24:1 | suppression range | +| Web.config:24:1:25:1 | LGTM! | LGTM! | LGTM | Web.config:24:1:25:1 | suppression range | +| Web.config:25:1:26:1 | LGTM[cs/unused-reftype] | LGTM[cs/unused-reftype] | LGTM[cs/unused-reftype] | Web.config:25:1:26:1 | suppression range | +| Web.config:26:1:27:1 | lgtm[cs/unused-reftype] and lgtm[cs/unused-field] | lgtm[cs/unused-reftype] and lgtm[cs/unused-field] | lgtm[cs/unused-field] | Web.config:26:1:27:1 | suppression range | +| Web.config:26:1:27:1 | lgtm[cs/unused-reftype] and lgtm[cs/unused-field] | lgtm[cs/unused-reftype] and lgtm[cs/unused-field] | lgtm[cs/unused-reftype] | Web.config:26:1:27:1 | suppression range | +| Web.config:27:1:27:39 | lgtm[cs/unused-reftype]; lgtm | lgtm[cs/unused-reftype]; lgtm | lgtm | Web.config:27:1:27:39 | suppression range | +| Web.config:27:1:27:39 | lgtm[cs/unused-reftype]; lgtm | lgtm[cs/unused-reftype]; lgtm | lgtm[cs/unused-reftype] | Web.config:27:1:27:39 | suppression range | diff --git a/csharp/ql/test/query-tests/AlertSuppression/XMLAlertSuppression.qlref b/csharp/ql/test/query-tests/AlertSuppression/XMLAlertSuppression.qlref new file mode 100644 index 000000000000..6d727304c2c8 --- /dev/null +++ b/csharp/ql/test/query-tests/AlertSuppression/XMLAlertSuppression.qlref @@ -0,0 +1 @@ +XMLAlertSuppression.ql \ No newline at end of file diff --git a/java/ql/src/semmle/code/xml/XML.qll b/java/ql/src/semmle/code/xml/XML.qll index 5871fed0dddd..039244d32817 100755 --- a/java/ql/src/semmle/code/xml/XML.qll +++ b/java/ql/src/semmle/code/xml/XML.qll @@ -341,3 +341,63 @@ class XMLCharacters extends @xmlcharacters, XMLLocatable { /** Gets a printable representation of this XML character sequence. */ override string toString() { result = this.getCharacters() } } + +/** + * An alert suppression comment. + */ +class XMLSuppressionComment extends XMLComment { + string annotation; + + XMLSuppressionComment() { + // suppression comments must be single-line + not getText().matches("%\n%") and + ( + // match `lgtm[...]` anywhere in the comment + annotation = getText().regexpFind("(?i)\\blgtm\\s*\\[[^\\]]*\\]", _, _) + or + // match `lgtm` at the start of the comment and after semicolon + annotation = getText().regexpFind("(?i)(?<=^|;)\\s*lgtm(?!\\B|\\s*\\[)", _, _).trim() + ) + } + + /** Gets the suppression annotation in this comment. */ + string getAnnotation() { result = annotation } + + /** + * Holds if this comment applies to the range from column `startcolumn` of line `startline` + * to column `endcolumn` of line `endline` in file `filepath`. + */ + predicate covers(string filepath, int startline, int startcolumn, int endline, int endcolumn) { + this.getLocation().hasLocationInfo(filepath, startline, _, endline, endcolumn) and + startcolumn = 1 + } + + /** Gets the scope of this suppression. */ + XMLSuppressionScope getScope() { this = result.getSuppressionComment() } +} + +/** + * The scope of an alert suppression comment. + */ +class XMLSuppressionScope extends @xmlcomment { + XMLSuppressionScope() { this instanceof XMLSuppressionComment } + + /** Gets a suppression comment with this scope. */ + XMLSuppressionComment getSuppressionComment() { result = this } + + /** + * Holds if this element is at the specified location. + * The location spans column `startcolumn` of line `startline` to + * column `endcolumn` of line `endline` in file `filepath`. + * For more information, see + * [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html). + */ + predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + this.(XMLSuppressionComment).covers(filepath, startline, startcolumn, endline, endcolumn) + } + + /** Gets a textual representation of this element. */ + string toString() { result = "suppression range" } +} diff --git a/javascript/ql/src/semmle/javascript/XML.qll b/javascript/ql/src/semmle/javascript/XML.qll index 5871fed0dddd..039244d32817 100755 --- a/javascript/ql/src/semmle/javascript/XML.qll +++ b/javascript/ql/src/semmle/javascript/XML.qll @@ -341,3 +341,63 @@ class XMLCharacters extends @xmlcharacters, XMLLocatable { /** Gets a printable representation of this XML character sequence. */ override string toString() { result = this.getCharacters() } } + +/** + * An alert suppression comment. + */ +class XMLSuppressionComment extends XMLComment { + string annotation; + + XMLSuppressionComment() { + // suppression comments must be single-line + not getText().matches("%\n%") and + ( + // match `lgtm[...]` anywhere in the comment + annotation = getText().regexpFind("(?i)\\blgtm\\s*\\[[^\\]]*\\]", _, _) + or + // match `lgtm` at the start of the comment and after semicolon + annotation = getText().regexpFind("(?i)(?<=^|;)\\s*lgtm(?!\\B|\\s*\\[)", _, _).trim() + ) + } + + /** Gets the suppression annotation in this comment. */ + string getAnnotation() { result = annotation } + + /** + * Holds if this comment applies to the range from column `startcolumn` of line `startline` + * to column `endcolumn` of line `endline` in file `filepath`. + */ + predicate covers(string filepath, int startline, int startcolumn, int endline, int endcolumn) { + this.getLocation().hasLocationInfo(filepath, startline, _, endline, endcolumn) and + startcolumn = 1 + } + + /** Gets the scope of this suppression. */ + XMLSuppressionScope getScope() { this = result.getSuppressionComment() } +} + +/** + * The scope of an alert suppression comment. + */ +class XMLSuppressionScope extends @xmlcomment { + XMLSuppressionScope() { this instanceof XMLSuppressionComment } + + /** Gets a suppression comment with this scope. */ + XMLSuppressionComment getSuppressionComment() { result = this } + + /** + * Holds if this element is at the specified location. + * The location spans column `startcolumn` of line `startline` to + * column `endcolumn` of line `endline` in file `filepath`. + * For more information, see + * [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html). + */ + predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + this.(XMLSuppressionComment).covers(filepath, startline, startcolumn, endline, endcolumn) + } + + /** Gets a textual representation of this element. */ + string toString() { result = "suppression range" } +} diff --git a/python/ql/src/semmle/python/xml/XML.qll b/python/ql/src/semmle/python/xml/XML.qll index 5871fed0dddd..039244d32817 100755 --- a/python/ql/src/semmle/python/xml/XML.qll +++ b/python/ql/src/semmle/python/xml/XML.qll @@ -341,3 +341,63 @@ class XMLCharacters extends @xmlcharacters, XMLLocatable { /** Gets a printable representation of this XML character sequence. */ override string toString() { result = this.getCharacters() } } + +/** + * An alert suppression comment. + */ +class XMLSuppressionComment extends XMLComment { + string annotation; + + XMLSuppressionComment() { + // suppression comments must be single-line + not getText().matches("%\n%") and + ( + // match `lgtm[...]` anywhere in the comment + annotation = getText().regexpFind("(?i)\\blgtm\\s*\\[[^\\]]*\\]", _, _) + or + // match `lgtm` at the start of the comment and after semicolon + annotation = getText().regexpFind("(?i)(?<=^|;)\\s*lgtm(?!\\B|\\s*\\[)", _, _).trim() + ) + } + + /** Gets the suppression annotation in this comment. */ + string getAnnotation() { result = annotation } + + /** + * Holds if this comment applies to the range from column `startcolumn` of line `startline` + * to column `endcolumn` of line `endline` in file `filepath`. + */ + predicate covers(string filepath, int startline, int startcolumn, int endline, int endcolumn) { + this.getLocation().hasLocationInfo(filepath, startline, _, endline, endcolumn) and + startcolumn = 1 + } + + /** Gets the scope of this suppression. */ + XMLSuppressionScope getScope() { this = result.getSuppressionComment() } +} + +/** + * The scope of an alert suppression comment. + */ +class XMLSuppressionScope extends @xmlcomment { + XMLSuppressionScope() { this instanceof XMLSuppressionComment } + + /** Gets a suppression comment with this scope. */ + XMLSuppressionComment getSuppressionComment() { result = this } + + /** + * Holds if this element is at the specified location. + * The location spans column `startcolumn` of line `startline` to + * column `endcolumn` of line `endline` in file `filepath`. + * For more information, see + * [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html). + */ + predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + this.(XMLSuppressionComment).covers(filepath, startline, startcolumn, endline, endcolumn) + } + + /** Gets a textual representation of this element. */ + string toString() { result = "suppression range" } +}