mirror of
https://github.com/github/codeql.git
synced 2026-02-19 16:33:40 +01:00
Model local variables
This commit is contained in:
214
ql/src/codeql_ruby/Variables.qll
Normal file
214
ql/src/codeql_ruby/Variables.qll
Normal file
@@ -0,0 +1,214 @@
|
||||
/** Provides classes for modeling program variables. */
|
||||
|
||||
private import ast
|
||||
private import codeql.Locations
|
||||
|
||||
private newtype TScope =
|
||||
TTopLevelScope(Program node) or
|
||||
TModuleScope(Module node) or
|
||||
TClassScope(AstNode cls) { cls instanceof Class or cls instanceof SingletonClass } or
|
||||
TMethodScope(AstNode method) { method instanceof Method or method instanceof SingletonMethod } or
|
||||
TBlockScope(AstNode block) { block instanceof Block or block instanceof DoBlock }
|
||||
|
||||
/** A scope in which variables can be declared. */
|
||||
class VariableScope extends TScope {
|
||||
/** Gets a textual representation of this element. */
|
||||
abstract string toString();
|
||||
|
||||
/** Gets the program element this scope is associated with, if any. */
|
||||
abstract AstNode getScopeElement();
|
||||
|
||||
/** Gets the location of the program element this scope is associated with, if any. */
|
||||
final Location getLocation() { result = getScopeElement().getLocation() }
|
||||
|
||||
/**
|
||||
* Gets a variable that is visible in this scope.
|
||||
*
|
||||
* A variable is visible if it is either declared in this scope, or in some outer scope
|
||||
* (only when this scope is a block scope).
|
||||
*/
|
||||
final Variable getAVariable() { result.getDeclaringScope() = this }
|
||||
|
||||
/**
|
||||
* Gets the variable with the given name that is visible in this scope.
|
||||
*
|
||||
* A variable is visible if it is either declared in this scope, or in some outer scope
|
||||
* (only when this scope is a block scope).
|
||||
*/
|
||||
Variable getVariable(string name) {
|
||||
result = getAVariable() and
|
||||
result.getValue() = name
|
||||
}
|
||||
}
|
||||
|
||||
private AstNode parent(AstNode n) {
|
||||
result = n.getParent() and
|
||||
not n = any(VariableScope s).getScopeElement()
|
||||
}
|
||||
|
||||
/** Gets the enclosing scope for `node`. */
|
||||
private VariableScope enclosingScope(AstNode node) {
|
||||
result.getScopeElement() = parent*(node.getParent())
|
||||
}
|
||||
|
||||
/** Holds if `scope` defines `var` as a parameter. */
|
||||
private predicate scopeDefinesParameter(VariableScope scope, Identifier var) {
|
||||
var in [scope
|
||||
.(BlockScope)
|
||||
.getScopeElement()
|
||||
.getAFieldOrChild()
|
||||
.(BlockParameters)
|
||||
.getAFieldOrChild+(),
|
||||
scope
|
||||
.(MethodScope)
|
||||
.getScopeElement()
|
||||
.getAFieldOrChild()
|
||||
.(MethodParameters)
|
||||
.getAFieldOrChild+()]
|
||||
}
|
||||
|
||||
/** Holds if `var` is assigned in `scope`. */
|
||||
private predicate scopeAssigns(VariableScope scope, Identifier var) {
|
||||
var in [any(Assignment assign).getLeft(), any(OperatorAssignment assign).getLeft()] and
|
||||
scope = enclosingScope(var)
|
||||
}
|
||||
|
||||
/** Holds if location `one` starts strictly before location `two` */
|
||||
pragma[inline]
|
||||
predicate strictlyBefore(Location one, Location two) {
|
||||
one.getStartLine() < two.getStartLine()
|
||||
or
|
||||
one.getStartLine() = two.getStartLine() and one.getStartColumn() < two.getStartColumn()
|
||||
}
|
||||
|
||||
cached
|
||||
private VariableScope blockOuterScopes(BlockScope block) { result = block.getOuterScope+() }
|
||||
|
||||
/** A variable declared in a scope. */
|
||||
class Variable extends Identifier {
|
||||
VariableScope scope;
|
||||
|
||||
Variable() {
|
||||
scopeDefinesParameter(scope, this)
|
||||
or
|
||||
scopeAssigns(scope, this) and
|
||||
not exists(Identifier other, VariableScope outer | other.getValue() = this.getValue() |
|
||||
(outer = scope or outer = blockOuterScopes(scope)) and
|
||||
(
|
||||
scopeDefinesParameter(outer, other)
|
||||
or
|
||||
scopeAssigns(outer, other) and
|
||||
strictlyBefore(other.getLocation(), this.getLocation())
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the name of this variable. */
|
||||
final string getName() { result = this.getValue() }
|
||||
|
||||
/** Gets the scope this variable is declared in. */
|
||||
final VariableScope getDeclaringScope() { result = scope }
|
||||
|
||||
/** Gets an access to this variable. */
|
||||
VariableAccess getAnAccess() { result.getVariable() = this }
|
||||
}
|
||||
|
||||
/** A parameter. */
|
||||
class Parameter extends Variable {
|
||||
Parameter() { scopeDefinesParameter(scope, this) }
|
||||
|
||||
final override ParameterAccess getAnAccess() { result = super.getAnAccess() }
|
||||
}
|
||||
|
||||
/** A local variable. */
|
||||
class LocalVariable extends Variable {
|
||||
LocalVariable() { not scopeDefinesParameter(scope, this) }
|
||||
|
||||
final override LocalVariableAccess getAnAccess() { result = super.getAnAccess() }
|
||||
}
|
||||
|
||||
/**
|
||||
* An identifier that refers to a variable.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* ```
|
||||
* function f(o) {
|
||||
* var w = 0, { x : y, z } = o; // `o` is a variable access
|
||||
* o = null; // `o` is a variable access
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
class VariableAccess extends Identifier {
|
||||
Variable variable;
|
||||
|
||||
VariableAccess() {
|
||||
exists(VariableScope scope | scope = enclosingScope(this) |
|
||||
variable = scope.getVariable(this.getValue()) and
|
||||
not strictlyBefore(this.getLocation(), variable.getLocation())
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the variable this identifier refers to.
|
||||
*/
|
||||
Variable getVariable() { result = variable }
|
||||
}
|
||||
|
||||
/** An identifier that refers to a parameter. */
|
||||
class ParameterAccess extends VariableAccess {
|
||||
override Parameter variable;
|
||||
|
||||
final override Parameter getVariable() { result = variable }
|
||||
}
|
||||
|
||||
/** An identifier that refers to a local variable. */
|
||||
class LocalVariableAccess extends VariableAccess {
|
||||
override LocalVariable variable;
|
||||
|
||||
final override LocalVariable getVariable() { result = super.getVariable() }
|
||||
}
|
||||
|
||||
/** A top-level scope. */
|
||||
class TopLevelScope extends VariableScope, TTopLevelScope {
|
||||
final override string toString() { result = "top-level scope" }
|
||||
|
||||
final override AstNode getScopeElement() { TTopLevelScope(result) = this }
|
||||
}
|
||||
|
||||
/** A module scope. */
|
||||
class ModuleScope extends VariableScope, TModuleScope {
|
||||
final override string toString() { result = "module scope" }
|
||||
|
||||
final override Module getScopeElement() { TModuleScope(result) = this }
|
||||
}
|
||||
|
||||
/** A class scope. */
|
||||
class ClassScope extends VariableScope, TClassScope {
|
||||
final override string toString() { result = "class scope" }
|
||||
|
||||
final override AstNode getScopeElement() { TClassScope(result) = this }
|
||||
}
|
||||
|
||||
/** A method scope. */
|
||||
class MethodScope extends VariableScope, TMethodScope {
|
||||
final override string toString() { result = "method scope" }
|
||||
|
||||
final override AstNode getScopeElement() { TMethodScope(result) = this }
|
||||
}
|
||||
|
||||
/** A block scope. */
|
||||
class BlockScope extends VariableScope, TBlockScope {
|
||||
final override string toString() { result = "block scope" }
|
||||
|
||||
final override AstNode getScopeElement() { TBlockScope(result) = this }
|
||||
|
||||
/** Gets the scope in which this scope is nested, if any. */
|
||||
final VariableScope getOuterScope() { result = enclosingScope(this.getScopeElement()) }
|
||||
|
||||
final override Variable getVariable(string name) {
|
||||
if exists(VariableScope.super.getVariable(name))
|
||||
then result = VariableScope.super.getVariable(name)
|
||||
else result = this.getOuterScope().getVariable(name)
|
||||
}
|
||||
}
|
||||
42
ql/test/library-tests/variables/nested_scopes.rb
Normal file
42
ql/test/library-tests/variables/nested_scopes.rb
Normal file
@@ -0,0 +1,42 @@
|
||||
def a
|
||||
puts "method a"
|
||||
end
|
||||
class C
|
||||
a = 5
|
||||
module M
|
||||
a = 4
|
||||
module N
|
||||
a = 3
|
||||
class D
|
||||
a = 2
|
||||
def show_a
|
||||
a = 1
|
||||
puts a
|
||||
a.times do |a|
|
||||
a.times do | x; a|
|
||||
a = 6
|
||||
a.times { |x| puts a }
|
||||
end
|
||||
end
|
||||
end
|
||||
def show_a2 a
|
||||
puts a
|
||||
end
|
||||
puts a
|
||||
end
|
||||
def self.show
|
||||
puts a # not a variable, but a call to a()
|
||||
end
|
||||
class << self
|
||||
a = 10
|
||||
puts a
|
||||
end
|
||||
puts a
|
||||
end
|
||||
puts a
|
||||
end
|
||||
puts a
|
||||
end
|
||||
d = C::M::N::D.new
|
||||
d.show_a
|
||||
|
||||
13
ql/test/library-tests/variables/scopes.rb
Normal file
13
ql/test/library-tests/variables/scopes.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
def a ; "x" end
|
||||
1.times do | x |
|
||||
puts a # not a local variable
|
||||
a = 3
|
||||
puts a # local variable
|
||||
end
|
||||
a = 6
|
||||
puts a
|
||||
1.times do | x |
|
||||
puts a # local variable from top-level
|
||||
a = 3
|
||||
puts a # local variable from top-level
|
||||
end
|
||||
71
ql/test/library-tests/variables/varaccess.expected
Normal file
71
ql/test/library-tests/variables/varaccess.expected
Normal file
@@ -0,0 +1,71 @@
|
||||
variableAccess
|
||||
| nested_scopes.rb:5:3:5:3 | a | nested_scopes.rb:4:1:39:3 | class scope |
|
||||
| nested_scopes.rb:7:5:7:5 | a | nested_scopes.rb:6:3:37:5 | module scope |
|
||||
| nested_scopes.rb:9:7:9:7 | a | nested_scopes.rb:8:5:35:7 | module scope |
|
||||
| nested_scopes.rb:11:9:11:9 | a | nested_scopes.rb:10:7:26:9 | class scope |
|
||||
| nested_scopes.rb:13:11:13:11 | a | nested_scopes.rb:12:9:21:11 | method scope |
|
||||
| nested_scopes.rb:14:16:14:16 | a | nested_scopes.rb:12:9:21:11 | method scope |
|
||||
| nested_scopes.rb:15:11:15:11 | a | nested_scopes.rb:12:9:21:11 | method scope |
|
||||
| nested_scopes.rb:15:23:15:23 | a | nested_scopes.rb:15:19:20:13 | block scope |
|
||||
| nested_scopes.rb:16:13:16:13 | a | nested_scopes.rb:15:19:20:13 | block scope |
|
||||
| nested_scopes.rb:16:26:16:26 | x | nested_scopes.rb:16:21:19:15 | block scope |
|
||||
| nested_scopes.rb:16:29:16:29 | a | nested_scopes.rb:16:21:19:15 | block scope |
|
||||
| nested_scopes.rb:17:15:17:15 | a | nested_scopes.rb:16:21:19:15 | block scope |
|
||||
| nested_scopes.rb:18:15:18:15 | a | nested_scopes.rb:16:21:19:15 | block scope |
|
||||
| nested_scopes.rb:18:26:18:26 | x | nested_scopes.rb:18:23:18:36 | block scope |
|
||||
| nested_scopes.rb:18:34:18:34 | a | nested_scopes.rb:16:21:19:15 | block scope |
|
||||
| nested_scopes.rb:22:21:22:21 | a | nested_scopes.rb:22:9:24:11 | method scope |
|
||||
| nested_scopes.rb:23:16:23:16 | a | nested_scopes.rb:22:9:24:11 | method scope |
|
||||
| nested_scopes.rb:25:14:25:14 | a | nested_scopes.rb:10:7:26:9 | class scope |
|
||||
| nested_scopes.rb:31:11:31:11 | a | nested_scopes.rb:30:7:33:9 | class scope |
|
||||
| nested_scopes.rb:32:16:32:16 | a | nested_scopes.rb:30:7:33:9 | class scope |
|
||||
| nested_scopes.rb:34:12:34:12 | a | nested_scopes.rb:8:5:35:7 | module scope |
|
||||
| nested_scopes.rb:36:10:36:10 | a | nested_scopes.rb:6:3:37:5 | module scope |
|
||||
| nested_scopes.rb:38:8:38:8 | a | nested_scopes.rb:4:1:39:3 | class scope |
|
||||
| nested_scopes.rb:40:1:40:1 | d | nested_scopes.rb:1:1:42:1 | top-level scope |
|
||||
| nested_scopes.rb:41:1:41:1 | d | nested_scopes.rb:1:1:42:1 | top-level scope |
|
||||
| scopes.rb:2:14:2:14 | x | scopes.rb:2:9:6:3 | block scope |
|
||||
| scopes.rb:4:4:4:4 | a | scopes.rb:2:9:6:3 | block scope |
|
||||
| scopes.rb:5:9:5:9 | a | scopes.rb:2:9:6:3 | block scope |
|
||||
| scopes.rb:7:1:7:1 | a | scopes.rb:1:1:13:3 | top-level scope |
|
||||
| scopes.rb:8:6:8:6 | a | scopes.rb:1:1:13:3 | top-level scope |
|
||||
| scopes.rb:9:14:9:14 | x | scopes.rb:9:9:13:3 | block scope |
|
||||
| scopes.rb:10:9:10:9 | a | scopes.rb:1:1:13:3 | top-level scope |
|
||||
| scopes.rb:11:4:11:4 | a | scopes.rb:1:1:13:3 | top-level scope |
|
||||
| scopes.rb:12:9:12:9 | a | scopes.rb:1:1:13:3 | top-level scope |
|
||||
parameterAccess
|
||||
| nested_scopes.rb:15:23:15:23 | a | nested_scopes.rb:15:19:20:13 | block scope |
|
||||
| nested_scopes.rb:16:13:16:13 | a | nested_scopes.rb:15:19:20:13 | block scope |
|
||||
| nested_scopes.rb:16:26:16:26 | x | nested_scopes.rb:16:21:19:15 | block scope |
|
||||
| nested_scopes.rb:16:29:16:29 | a | nested_scopes.rb:16:21:19:15 | block scope |
|
||||
| nested_scopes.rb:17:15:17:15 | a | nested_scopes.rb:16:21:19:15 | block scope |
|
||||
| nested_scopes.rb:18:15:18:15 | a | nested_scopes.rb:16:21:19:15 | block scope |
|
||||
| nested_scopes.rb:18:26:18:26 | x | nested_scopes.rb:18:23:18:36 | block scope |
|
||||
| nested_scopes.rb:18:34:18:34 | a | nested_scopes.rb:16:21:19:15 | block scope |
|
||||
| nested_scopes.rb:22:21:22:21 | a | nested_scopes.rb:22:9:24:11 | method scope |
|
||||
| nested_scopes.rb:23:16:23:16 | a | nested_scopes.rb:22:9:24:11 | method scope |
|
||||
| scopes.rb:2:14:2:14 | x | scopes.rb:2:9:6:3 | block scope |
|
||||
| scopes.rb:9:14:9:14 | x | scopes.rb:9:9:13:3 | block scope |
|
||||
localVariableAccess
|
||||
| nested_scopes.rb:5:3:5:3 | a | nested_scopes.rb:4:1:39:3 | class scope |
|
||||
| nested_scopes.rb:7:5:7:5 | a | nested_scopes.rb:6:3:37:5 | module scope |
|
||||
| nested_scopes.rb:9:7:9:7 | a | nested_scopes.rb:8:5:35:7 | module scope |
|
||||
| nested_scopes.rb:11:9:11:9 | a | nested_scopes.rb:10:7:26:9 | class scope |
|
||||
| nested_scopes.rb:13:11:13:11 | a | nested_scopes.rb:12:9:21:11 | method scope |
|
||||
| nested_scopes.rb:14:16:14:16 | a | nested_scopes.rb:12:9:21:11 | method scope |
|
||||
| nested_scopes.rb:15:11:15:11 | a | nested_scopes.rb:12:9:21:11 | method scope |
|
||||
| nested_scopes.rb:25:14:25:14 | a | nested_scopes.rb:10:7:26:9 | class scope |
|
||||
| nested_scopes.rb:31:11:31:11 | a | nested_scopes.rb:30:7:33:9 | class scope |
|
||||
| nested_scopes.rb:32:16:32:16 | a | nested_scopes.rb:30:7:33:9 | class scope |
|
||||
| nested_scopes.rb:34:12:34:12 | a | nested_scopes.rb:8:5:35:7 | module scope |
|
||||
| nested_scopes.rb:36:10:36:10 | a | nested_scopes.rb:6:3:37:5 | module scope |
|
||||
| nested_scopes.rb:38:8:38:8 | a | nested_scopes.rb:4:1:39:3 | class scope |
|
||||
| nested_scopes.rb:40:1:40:1 | d | nested_scopes.rb:1:1:42:1 | top-level scope |
|
||||
| nested_scopes.rb:41:1:41:1 | d | nested_scopes.rb:1:1:42:1 | top-level scope |
|
||||
| scopes.rb:4:4:4:4 | a | scopes.rb:2:9:6:3 | block scope |
|
||||
| scopes.rb:5:9:5:9 | a | scopes.rb:2:9:6:3 | block scope |
|
||||
| scopes.rb:7:1:7:1 | a | scopes.rb:1:1:13:3 | top-level scope |
|
||||
| scopes.rb:8:6:8:6 | a | scopes.rb:1:1:13:3 | top-level scope |
|
||||
| scopes.rb:10:9:10:9 | a | scopes.rb:1:1:13:3 | top-level scope |
|
||||
| scopes.rb:11:4:11:4 | a | scopes.rb:1:1:13:3 | top-level scope |
|
||||
| scopes.rb:12:9:12:9 | a | scopes.rb:1:1:13:3 | top-level scope |
|
||||
13
ql/test/library-tests/variables/varaccess.ql
Normal file
13
ql/test/library-tests/variables/varaccess.ql
Normal file
@@ -0,0 +1,13 @@
|
||||
import codeql_ruby.Variables
|
||||
|
||||
query predicate variableAccess(VariableAccess var, VariableScope scope) {
|
||||
scope = var.getVariable().getDeclaringScope()
|
||||
}
|
||||
|
||||
query predicate parameterAccess(ParameterAccess var, VariableScope scope) {
|
||||
scope = var.getVariable().getDeclaringScope()
|
||||
}
|
||||
|
||||
query predicate localVariableAccess(LocalVariableAccess var, VariableScope scope) {
|
||||
scope = var.getVariable().getDeclaringScope()
|
||||
}
|
||||
35
ql/test/library-tests/variables/variable.expected
Normal file
35
ql/test/library-tests/variables/variable.expected
Normal file
@@ -0,0 +1,35 @@
|
||||
variable
|
||||
| nested_scopes.rb:5:3:5:3 | a |
|
||||
| nested_scopes.rb:7:5:7:5 | a |
|
||||
| nested_scopes.rb:9:7:9:7 | a |
|
||||
| nested_scopes.rb:11:9:11:9 | a |
|
||||
| nested_scopes.rb:13:11:13:11 | a |
|
||||
| nested_scopes.rb:15:23:15:23 | a |
|
||||
| nested_scopes.rb:16:26:16:26 | x |
|
||||
| nested_scopes.rb:16:29:16:29 | a |
|
||||
| nested_scopes.rb:18:26:18:26 | x |
|
||||
| nested_scopes.rb:22:21:22:21 | a |
|
||||
| nested_scopes.rb:31:11:31:11 | a |
|
||||
| nested_scopes.rb:40:1:40:1 | d |
|
||||
| scopes.rb:2:14:2:14 | x |
|
||||
| scopes.rb:4:4:4:4 | a |
|
||||
| scopes.rb:7:1:7:1 | a |
|
||||
| scopes.rb:9:14:9:14 | x |
|
||||
parameter
|
||||
| nested_scopes.rb:15:23:15:23 | a |
|
||||
| nested_scopes.rb:16:26:16:26 | x |
|
||||
| nested_scopes.rb:16:29:16:29 | a |
|
||||
| nested_scopes.rb:18:26:18:26 | x |
|
||||
| nested_scopes.rb:22:21:22:21 | a |
|
||||
| scopes.rb:2:14:2:14 | x |
|
||||
| scopes.rb:9:14:9:14 | x |
|
||||
localVariable
|
||||
| nested_scopes.rb:5:3:5:3 | a |
|
||||
| nested_scopes.rb:7:5:7:5 | a |
|
||||
| nested_scopes.rb:9:7:9:7 | a |
|
||||
| nested_scopes.rb:11:9:11:9 | a |
|
||||
| nested_scopes.rb:13:11:13:11 | a |
|
||||
| nested_scopes.rb:31:11:31:11 | a |
|
||||
| nested_scopes.rb:40:1:40:1 | d |
|
||||
| scopes.rb:4:4:4:4 | a |
|
||||
| scopes.rb:7:1:7:1 | a |
|
||||
7
ql/test/library-tests/variables/variable.ql
Normal file
7
ql/test/library-tests/variables/variable.ql
Normal file
@@ -0,0 +1,7 @@
|
||||
import codeql_ruby.Variables
|
||||
|
||||
query predicate variable(Variable v) { any() }
|
||||
|
||||
query predicate parameter(Parameter p) { any() }
|
||||
|
||||
query predicate localVariable(LocalVariable v) { any() }
|
||||
17
ql/test/library-tests/variables/varscopes.expected
Normal file
17
ql/test/library-tests/variables/varscopes.expected
Normal file
@@ -0,0 +1,17 @@
|
||||
| nested_scopes.rb:1:1:3:3 | method scope |
|
||||
| nested_scopes.rb:1:1:42:1 | top-level scope |
|
||||
| nested_scopes.rb:4:1:39:3 | class scope |
|
||||
| nested_scopes.rb:6:3:37:5 | module scope |
|
||||
| nested_scopes.rb:8:5:35:7 | module scope |
|
||||
| nested_scopes.rb:10:7:26:9 | class scope |
|
||||
| nested_scopes.rb:12:9:21:11 | method scope |
|
||||
| nested_scopes.rb:15:19:20:13 | block scope |
|
||||
| nested_scopes.rb:16:21:19:15 | block scope |
|
||||
| nested_scopes.rb:18:23:18:36 | block scope |
|
||||
| nested_scopes.rb:22:9:24:11 | method scope |
|
||||
| nested_scopes.rb:27:7:29:9 | method scope |
|
||||
| nested_scopes.rb:30:7:33:9 | class scope |
|
||||
| scopes.rb:1:1:1:15 | method scope |
|
||||
| scopes.rb:1:1:13:3 | top-level scope |
|
||||
| scopes.rb:2:9:6:3 | block scope |
|
||||
| scopes.rb:9:9:13:3 | block scope |
|
||||
3
ql/test/library-tests/variables/varscopes.ql
Normal file
3
ql/test/library-tests/variables/varscopes.ql
Normal file
@@ -0,0 +1,3 @@
|
||||
import codeql_ruby.Variables
|
||||
|
||||
select any(VariableScope x)
|
||||
Reference in New Issue
Block a user