Ignore include/prepend statements in blocks

Include and prepend statements are rarely used in block in normal code and when
used in normal code they tend to be in blocks that are passed to methods like
`module_eval` which is a builtin method that evaluates a block in the context
of some other module (typically created with Module.new). We currently don't attempt
to track such "dynamically" constructed modules, and ignoring such modules
 and the `module_eval` calls on them seems fine for now.

Another, much more frequent use of include/prepend statements in blocks is in Rspec.describe and
Rspec.context method calls in tests. Rspec also evaluates those blocks in the context of some
special Rspec class. Precisely tracking such calls during the initial construction of the module/class
hierarchy would be really hard and there would be little benefit because the interesting modules and classes of
an application are not defined in test files.
This commit is contained in:
Arthur Baars
2021-04-13 11:30:41 +02:00
parent 280fe73063
commit 754bfdd136
2 changed files with 22 additions and 2 deletions

View File

@@ -1,4 +1,5 @@
private import codeql.Locations
private import codeql_ruby.AST
private import codeql_ruby.ast.Call
private import codeql_ruby.ast.Constant
private import codeql_ruby.ast.Expr
@@ -169,7 +170,7 @@ private class IncludeOrPrependCall extends MethodCall {
string getTarget() {
result = resolveScopeExpr(this.getReceiver(), _)
or
result = qualifiedModuleName(this.getEnclosingModule()) and
result = qualifiedModuleName(enclosingModule(this)) and
(
this.getReceiver() instanceof Self
or
@@ -178,6 +179,25 @@ private class IncludeOrPrependCall extends MethodCall {
}
}
/**
* A variant of AstNode::getEnclosingModule that excludes
* results that are enclosed in a block. This is a bit wrong because
* it could lead to false negatives. However, `include` statements in
* blocks are very rare in normal code. The majority of cases are in calls
* to methods like `module_eval` and `Rspec.describe` / `Rspec.context`. These
* methods evaluate the block in the context of some other module/class instead of
* the enclosing one.
*/
private ModuleBase enclosingModule(AstNode node) {
exists(AstNode parent | parent = node.getParent() |
result = parent
or
not parent instanceof ModuleBase and
not parent instanceof Block and
result = enclosingModule(parent)
)
}
private string prepends(string qname) {
exists(IncludeOrPrependCall m |
m.getMethodName() = "prepend" and

View File

@@ -125,7 +125,7 @@ moduleTypes
| modules.rb:83:1:86:3 | Other | modules.rb:83:1:86:3 | Other |
| modules.rb:84:3:85:5 | Foo1 | modules.rb:84:3:85:5 | Other::Foo1 |
| modules.rb:88:1:93:3 | IncludeTest | modules.rb:88:1:93:3 | IncludeTest |
| modules.rb:91:3:92:5 | Y | modules.rb:91:3:92:5 | Other::Foo1::Y |
| modules.rb:91:3:92:5 | Y | modules.rb:91:3:92:5 | Test::Foo1::Y |
| modules.rb:95:1:99:3 | IncludeTest2 | modules.rb:95:1:99:3 | IncludeTest2 |
| modules.rb:97:3:98:5 | Z | modules.rb:97:3:98:5 | Test::Foo1::Z |
| modules.rb:101:1:105:3 | PrependTest | modules.rb:101:1:105:3 | PrependTest |