diff --git a/ql/lib/codeql/ruby/filters/GeneratedCode.qll b/ql/lib/codeql/ruby/filters/GeneratedCode.qll new file mode 100644 index 00000000000..18d12be3aac --- /dev/null +++ b/ql/lib/codeql/ruby/filters/GeneratedCode.qll @@ -0,0 +1,43 @@ +/** Provides classes for detecting generated code. */ + +private import ruby +private import codeql.ruby.ast.internal.TreeSitter + +/** A source file that contains generated code. */ +abstract class GeneratedCodeFile extends RubyFile { } + +/** A file contining comments suggesting it contains generated code. */ +class GeneratedCommentFile extends GeneratedCodeFile { + GeneratedCommentFile() { this = any(GeneratedCodeComment c).getLocation().getFile() } +} + +/** A comment line that indicates generated code. */ +abstract class GeneratedCodeComment extends Ruby::Comment { } + +/** + * A generic comment line that suggests that the file is generated. + */ +class GenericGeneratedCodeComment extends GeneratedCodeComment { + GenericGeneratedCodeComment() { + exists(string line, string entity, string was, string automatically | line = getValue() | + entity = "file|class|art[ei]fact|module|script" and + was = "was|is|has been" and + automatically = "automatically |mechanically |auto[- ]?" and + line.regexpMatch("(?i).*\\bThis (" + entity + ") (" + was + ") (" + automatically + + ")?generated\\b.*") + ) + } +} + +/** A comment warning against modifications. */ +class DontModifyMarkerComment extends GeneratedCodeComment { + DontModifyMarkerComment() { + exists(string line | line = getValue() | + line.regexpMatch("(?i).*\\bGenerated by\\b.*\\bDo not edit\\b.*") or + line.regexpMatch("(?i).*\\bAny modifications to this file will be lost\\b.*") + ) + } +} + +/** Holds if `file` looks like it contains generated code. */ +predicate isGeneratedCode(GeneratedCodeFile file) { any() } diff --git a/ql/src/AlertSuppression.ql b/ql/src/AlertSuppression.ql new file mode 100644 index 00000000000..0ea3c64d747 --- /dev/null +++ b/ql/src/AlertSuppression.ql @@ -0,0 +1,82 @@ +/** + * @name Alert suppression + * @description Generates information about alert suppressions. + * @kind alert-suppression + * @id rb/alert-suppression + */ + +import ruby +import codeql.ruby.ast.internal.TreeSitter + +/** + * An alert suppression comment. + */ +class SuppressionComment extends Ruby::Comment { + string annotation; + + SuppressionComment() { + // suppression comments must be single-line + this.getLocation().getStartLine() = this.getLocation().getEndLine() and + exists(string text | text = commentText(this) | + // match `lgtm[...]` anywhere in the comment + annotation = text.regexpFind("(?i)\\blgtm\\s*\\[[^\\]]*\\]", _, _) + or + // match `lgtm` at the start of the comment and after semicolon + annotation = text.regexpFind("(?i)(?<=^|;)\\s*lgtm(?!\\B|\\s*\\[)", _, _).trim() + ) + } + + /** + * Gets the text of this suppression comment. + */ + string getText() { result = commentText(this) } + + /** 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. */ + SuppressionScope getScope() { this = result.getSuppressionComment() } +} + +private string commentText(Ruby::Comment comment) { result = comment.getValue().suffix(1) } + +/** + * The scope of an alert suppression comment. + */ +class SuppressionScope extends @ruby_token_comment { + SuppressionScope() { this instanceof SuppressionComment } + + /** Gets a suppression comment with this scope. */ + SuppressionComment 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.(SuppressionComment).covers(filepath, startline, startcolumn, endline, endcolumn) + } + + /** Gets a textual representation of this element. */ + string toString() { result = "suppression range" } +} + +from SuppressionComment 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/ql/src/filters/ClassifyFiles.ql b/ql/src/filters/ClassifyFiles.ql new file mode 100644 index 00000000000..d194523e09d --- /dev/null +++ b/ql/src/filters/ClassifyFiles.ql @@ -0,0 +1,20 @@ +/** + * @name Classify files + * @description This query produces a list of all files in a database + * that are classified as generated code or test code. + * + * Used by LGTM. + * @kind file-classifier + * @id rb/file-classifier + */ + +import ruby +import codeql.ruby.filters.GeneratedCode + +predicate classify(File f, string category) { + f instanceof GeneratedCodeFile and category = "generated" +} + +from File f, string category +where classify(f, category) +select f, category diff --git a/ql/test/query-tests/AlertSuppression/.gitattributes b/ql/test/query-tests/AlertSuppression/.gitattributes new file mode 100644 index 00000000000..7ed66a396cf --- /dev/null +++ b/ql/test/query-tests/AlertSuppression/.gitattributes @@ -0,0 +1 @@ +TestWindows.java eol=crlf diff --git a/ql/test/query-tests/AlertSuppression/AlertSuppression.expected b/ql/test/query-tests/AlertSuppression/AlertSuppression.expected new file mode 100644 index 00000000000..840b13f61bc --- /dev/null +++ b/ql/test/query-tests/AlertSuppression/AlertSuppression.expected @@ -0,0 +1,48 @@ +| Test.rb:1:16:1:21 | # lgtm | lgtm | lgtm | Test.rb:1:1:1:21 | suppression range | +| Test.rb:2:1:2:32 | # lgtm[rb/confusing-method-name] | lgtm[rb/confusing-method-name] | lgtm[rb/confusing-method-name] | Test.rb:2:1:2:32 | suppression range | +| Test.rb:3:1:3:65 | # lgtm[rb/confusing-method-name, rb/non-short-circuit-evaluation] | lgtm[rb/confusing-method-name, rb/non-short-circuit-evaluation] | lgtm[rb/confusing-method-name, rb/non-short-circuit-evaluation] | Test.rb:3:1:3:65 | suppression range | +| Test.rb:4:1:4:23 | # lgtm[@tag:exceptions] | lgtm[@tag:exceptions] | lgtm[@tag:exceptions] | Test.rb:4:1:4:23 | suppression range | +| Test.rb:5:1:5:48 | # lgtm[@tag:exceptions,rb/confusing-method-name] | lgtm[@tag:exceptions,rb/confusing-method-name] | lgtm[@tag:exceptions,rb/confusing-method-name] | Test.rb:5:1:5:48 | suppression range | +| Test.rb:6:1:6:27 | # lgtm[@expires:2017-06-11] | lgtm[@expires:2017-06-11] | lgtm[@expires:2017-06-11] | Test.rb:6:1:6:27 | suppression range | +| Test.rb:7:1:7:78 | # lgtm[rb/confusing-method-name] does not seem confusing despite alert by lgtm | lgtm[rb/confusing-method-name] does not seem confusing despite alert by lgtm | lgtm[rb/confusing-method-name] | Test.rb:7:1:7:78 | suppression range | +| Test.rb:8:1:8:17 | # lgtm: blah blah | lgtm: blah blah | lgtm | Test.rb:8:1:8:17 | suppression range | +| Test.rb:9:1:9:31 | # lgtm blah blah #falsepositive | lgtm blah blah #falsepositive | lgtm | Test.rb:9:1:9:31 | suppression range | +| Test.rb:10:1:10:33 | #lgtm [rb/confusing-method-name] | lgtm [rb/confusing-method-name] | lgtm [rb/confusing-method-name] | Test.rb:10:1:10:33 | suppression range | +| Test.rb:11:1:11:8 | # lgtm[] | lgtm[] | lgtm[] | Test.rb:11:1:11:8 | suppression range | +| Test.rb:13:1:13:5 | #lgtm | lgtm | lgtm | Test.rb:13:1:13:5 | suppression range | +| Test.rb:14:1:14:6 | #\tlgtm | \tlgtm | lgtm | Test.rb:14:1:14:6 | suppression range | +| Test.rb:15:1:15:33 | # lgtm\t[rb/confusing-method-name] | lgtm\t[rb/confusing-method-name] | lgtm\t[rb/confusing-method-name] | Test.rb:15:1:15:33 | suppression range | +| Test.rb:18:1:18:11 | # foo; lgtm | foo; lgtm | lgtm | Test.rb:18:1:18:11 | suppression range | +| Test.rb:19:1:19:37 | # foo; lgtm[rb/confusing-method-name] | foo; lgtm[rb/confusing-method-name] | lgtm[rb/confusing-method-name] | Test.rb:19:1:19:37 | suppression range | +| Test.rb:21:1:21:36 | # foo lgtm[rb/confusing-method-name] | foo lgtm[rb/confusing-method-name] | lgtm[rb/confusing-method-name] | Test.rb:21:1:21:36 | suppression range | +| Test.rb:23:1:23:40 | # foo lgtm[rb/confusing-method-name] bar | foo lgtm[rb/confusing-method-name] bar | lgtm[rb/confusing-method-name] | Test.rb:23:1:23:40 | suppression range | +| Test.rb:24:1:24:7 | # LGTM! | LGTM! | LGTM | Test.rb:24:1:24:7 | suppression range | +| Test.rb:25:1:25:32 | # LGTM[rb/confusing-method-name] | LGTM[rb/confusing-method-name] | LGTM[rb/confusing-method-name] | Test.rb:25:1:25:32 | suppression range | +| Test.rb:26:1:26:73 | #lgtm[rb/confusing-method-name] and lgtm[rb/non-short-circuit-evaluation] | lgtm[rb/confusing-method-name] and lgtm[rb/non-short-circuit-evaluation] | lgtm[rb/confusing-method-name] | Test.rb:26:1:26:73 | suppression range | +| Test.rb:26:1:26:73 | #lgtm[rb/confusing-method-name] and lgtm[rb/non-short-circuit-evaluation] | lgtm[rb/confusing-method-name] and lgtm[rb/non-short-circuit-evaluation] | lgtm[rb/non-short-circuit-evaluation] | Test.rb:26:1:26:73 | suppression range | +| Test.rb:27:1:27:37 | #lgtm[rb/confusing-method-name]; lgtm | lgtm[rb/confusing-method-name]; lgtm | lgtm | Test.rb:27:1:27:37 | suppression range | +| Test.rb:27:1:27:37 | #lgtm[rb/confusing-method-name]; lgtm | lgtm[rb/confusing-method-name]; lgtm | lgtm[rb/confusing-method-name] | Test.rb:27:1:27:37 | suppression range | +| TestWindows.rb:1:23:1:29 | # lgtm\r | lgtm\r | lgtm | TestWindows.rb:1:1:1:29 | suppression range | +| TestWindows.rb:2:1:2:33 | # lgtm[rb/confusing-method-name]\r | lgtm[rb/confusing-method-name]\r | lgtm[rb/confusing-method-name] | TestWindows.rb:2:1:2:33 | suppression range | +| TestWindows.rb:3:1:3:66 | # lgtm[rb/confusing-method-name, rb/non-short-circuit-evaluation]\r | lgtm[rb/confusing-method-name, rb/non-short-circuit-evaluation]\r | lgtm[rb/confusing-method-name, rb/non-short-circuit-evaluation] | TestWindows.rb:3:1:3:66 | suppression range | +| TestWindows.rb:4:1:4:24 | # lgtm[@tag:exceptions]\r | lgtm[@tag:exceptions]\r | lgtm[@tag:exceptions] | TestWindows.rb:4:1:4:24 | suppression range | +| TestWindows.rb:5:1:5:49 | # lgtm[@tag:exceptions,rb/confusing-method-name]\r | lgtm[@tag:exceptions,rb/confusing-method-name]\r | lgtm[@tag:exceptions,rb/confusing-method-name] | TestWindows.rb:5:1:5:49 | suppression range | +| TestWindows.rb:6:1:6:28 | # lgtm[@expires:2017-06-11]\r | lgtm[@expires:2017-06-11]\r | lgtm[@expires:2017-06-11] | TestWindows.rb:6:1:6:28 | suppression range | +| TestWindows.rb:7:1:7:79 | # lgtm[rb/confusing-method-name] does not seem confusing despite alert by lgtm\r | lgtm[rb/confusing-method-name] does not seem confusing despite alert by lgtm\r | lgtm[rb/confusing-method-name] | TestWindows.rb:7:1:7:79 | suppression range | +| TestWindows.rb:8:1:8:18 | # lgtm: blah blah\r | lgtm: blah blah\r | lgtm | TestWindows.rb:8:1:8:18 | suppression range | +| TestWindows.rb:9:1:9:32 | # lgtm blah blah #falsepositive\r | lgtm blah blah #falsepositive\r | lgtm | TestWindows.rb:9:1:9:32 | suppression range | +| TestWindows.rb:10:1:10:34 | #lgtm [rb/confusing-method-name]\r | lgtm [rb/confusing-method-name]\r | lgtm [rb/confusing-method-name] | TestWindows.rb:10:1:10:34 | suppression range | +| TestWindows.rb:11:1:11:9 | # lgtm[]\r | lgtm[]\r | lgtm[] | TestWindows.rb:11:1:11:9 | suppression range | +| TestWindows.rb:13:1:13:6 | #lgtm\r | lgtm\r | lgtm | TestWindows.rb:13:1:13:6 | suppression range | +| TestWindows.rb:14:1:14:7 | #\tlgtm\r | \tlgtm\r | lgtm | TestWindows.rb:14:1:14:7 | suppression range | +| TestWindows.rb:15:1:15:34 | # lgtm\t[rb/confusing-method-name]\r | lgtm\t[rb/confusing-method-name]\r | lgtm\t[rb/confusing-method-name] | TestWindows.rb:15:1:15:34 | suppression range | +| TestWindows.rb:18:1:18:12 | # foo; lgtm\r | foo; lgtm\r | lgtm | TestWindows.rb:18:1:18:12 | suppression range | +| TestWindows.rb:19:1:19:38 | # foo; lgtm[rb/confusing-method-name]\r | foo; lgtm[rb/confusing-method-name]\r | lgtm[rb/confusing-method-name] | TestWindows.rb:19:1:19:38 | suppression range | +| TestWindows.rb:21:1:21:37 | # foo lgtm[rb/confusing-method-name]\r | foo lgtm[rb/confusing-method-name]\r | lgtm[rb/confusing-method-name] | TestWindows.rb:21:1:21:37 | suppression range | +| TestWindows.rb:23:1:23:41 | # foo lgtm[rb/confusing-method-name] bar\r | foo lgtm[rb/confusing-method-name] bar\r | lgtm[rb/confusing-method-name] | TestWindows.rb:23:1:23:41 | suppression range | +| TestWindows.rb:24:1:24:8 | # LGTM!\r | LGTM!\r | LGTM | TestWindows.rb:24:1:24:8 | suppression range | +| TestWindows.rb:25:1:25:33 | # LGTM[rb/confusing-method-name]\r | LGTM[rb/confusing-method-name]\r | LGTM[rb/confusing-method-name] | TestWindows.rb:25:1:25:33 | suppression range | +| TestWindows.rb:26:1:26:74 | #lgtm[rb/confusing-method-name] and lgtm[rb/non-short-circuit-evaluation]\r | lgtm[rb/confusing-method-name] and lgtm[rb/non-short-circuit-evaluation]\r | lgtm[rb/confusing-method-name] | TestWindows.rb:26:1:26:74 | suppression range | +| TestWindows.rb:26:1:26:74 | #lgtm[rb/confusing-method-name] and lgtm[rb/non-short-circuit-evaluation]\r | lgtm[rb/confusing-method-name] and lgtm[rb/non-short-circuit-evaluation]\r | lgtm[rb/non-short-circuit-evaluation] | TestWindows.rb:26:1:26:74 | suppression range | +| TestWindows.rb:27:1:27:38 | #lgtm[rb/confusing-method-name]; lgtm\r | lgtm[rb/confusing-method-name]; lgtm\r | lgtm | TestWindows.rb:27:1:27:38 | suppression range | +| TestWindows.rb:27:1:27:38 | #lgtm[rb/confusing-method-name]; lgtm\r | lgtm[rb/confusing-method-name]; lgtm\r | lgtm[rb/confusing-method-name] | TestWindows.rb:27:1:27:38 | suppression range | diff --git a/ql/test/query-tests/AlertSuppression/AlertSuppression.qlref b/ql/test/query-tests/AlertSuppression/AlertSuppression.qlref new file mode 100644 index 00000000000..9d7833eccae --- /dev/null +++ b/ql/test/query-tests/AlertSuppression/AlertSuppression.qlref @@ -0,0 +1 @@ +AlertSuppression.ql diff --git a/ql/test/query-tests/AlertSuppression/Test.rb b/ql/test/query-tests/AlertSuppression/Test.rb new file mode 100644 index 00000000000..028d5b87aa7 --- /dev/null +++ b/ql/test/query-tests/AlertSuppression/Test.rb @@ -0,0 +1,28 @@ +class Test end # lgtm +# lgtm[rb/confusing-method-name] +# lgtm[rb/confusing-method-name, rb/non-short-circuit-evaluation] +# lgtm[@tag:exceptions] +# lgtm[@tag:exceptions,rb/confusing-method-name] +# lgtm[@expires:2017-06-11] +# lgtm[rb/confusing-method-name] does not seem confusing despite alert by lgtm +# lgtm: blah blah +# lgtm blah blah #falsepositive +#lgtm [rb/confusing-method-name] +# lgtm[] +# lgtmfoo +#lgtm +# lgtm +# lgtm [rb/confusing-method-name] +# foolgtm[rb/confusing-method-name] +# foolgtm +# foo; lgtm +# foo; lgtm[rb/confusing-method-name] +# foo lgtm +# foo lgtm[rb/confusing-method-name] +# foo lgtm bar +# foo lgtm[rb/confusing-method-name] bar +# LGTM! +# LGTM[rb/confusing-method-name] +#lgtm[rb/confusing-method-name] and lgtm[rb/non-short-circuit-evaluation] +#lgtm[rb/confusing-method-name]; lgtm + diff --git a/ql/test/query-tests/AlertSuppression/TestWindows.rb b/ql/test/query-tests/AlertSuppression/TestWindows.rb new file mode 100644 index 00000000000..1e69a66ee11 --- /dev/null +++ b/ql/test/query-tests/AlertSuppression/TestWindows.rb @@ -0,0 +1,28 @@ +class TestWindows end # lgtm +# lgtm[rb/confusing-method-name] +# lgtm[rb/confusing-method-name, rb/non-short-circuit-evaluation] +# lgtm[@tag:exceptions] +# lgtm[@tag:exceptions,rb/confusing-method-name] +# lgtm[@expires:2017-06-11] +# lgtm[rb/confusing-method-name] does not seem confusing despite alert by lgtm +# lgtm: blah blah +# lgtm blah blah #falsepositive +#lgtm [rb/confusing-method-name] +# lgtm[] +# lgtmfoo +#lgtm +# lgtm +# lgtm [rb/confusing-method-name] +# foolgtm[rb/confusing-method-name] +# foolgtm +# foo; lgtm +# foo; lgtm[rb/confusing-method-name] +# foo lgtm +# foo lgtm[rb/confusing-method-name] +# foo lgtm bar +# foo lgtm[rb/confusing-method-name] bar +# LGTM! +# LGTM[rb/confusing-method-name] +#lgtm[rb/confusing-method-name] and lgtm[rb/non-short-circuit-evaluation] +#lgtm[rb/confusing-method-name]; lgtm +