Files
codeql/ql/src/codeql_ruby/ast/internal/Variable.qll
2021-02-04 15:30:58 +01:00

595 lines
17 KiB
Plaintext

private import TreeSitter
private import codeql.Locations
private import codeql_ruby.AST
private import codeql_ruby.ast.internal.Expr
private import codeql_ruby.ast.internal.Method
private import codeql_ruby.ast.internal.Pattern
private Generated::AstNode parentOf(Generated::AstNode n) {
exists(Generated::AstNode parent | parent = n.getParent() |
if
n =
[
parent.(Generated::Module).getName(), parent.(Generated::Class).getName(),
parent.(Generated::Class).getSuperclass(), parent.(Generated::SingletonClass).getValue(),
parent.(Generated::Method).getName(), parent.(Generated::SingletonMethod).getName(),
parent.(Generated::SingletonMethod).getObject()
]
then result = parent.getParent()
else result = parent
)
}
private Generated::AstNode parentOfNoScope(Generated::AstNode n) {
result = parentOf(n) and
not n = any(VariableScope s).getScopeElement()
}
private predicate instanceVariableAccess(
Generated::InstanceVariable var, string name, VariableScope scope, boolean instance
) {
name = var.getValue() and
scope = enclosingModuleOrClass(var) and
if hasEnclosingMethod(var) then instance = true else instance = false
}
private predicate classVariableAccess(Generated::ClassVariable var, string name, VariableScope scope) {
name = var.getValue() and
scope = enclosingModuleOrClass(var)
}
predicate hasEnclosingMethod(Generated::AstNode node) {
exists(Callable::Range method |
parentCallableScope*(enclosingScope(node)) = TCallableScope(method)
|
method instanceof Method::Range or
method instanceof SingletonMethod::Range
)
}
private TCallableScope parentCallableScope(TCallableScope scope) {
exists(Callable::Range c |
scope = TCallableScope(c) and
not c instanceof Method::Range and
not c instanceof SingletonMethod::Range
|
result = scope.(VariableScope).getOuterScope()
)
}
private VariableScope parentScope(VariableScope scope) {
not scope instanceof ModuleOrClassScope and
result = scope.getOuterScope()
}
private ModuleOrClassScope enclosingModuleOrClass(Generated::AstNode node) {
result = parentScope*(enclosingScope(node))
}
private predicate parameterAssignment(
CallableScope::Range scope, string name, Generated::Identifier i
) {
implicitParameterAssignmentNode(i, scope.getScopeElement()) and
name = i.getValue()
}
/** Holds if `scope` defines `name` in its parameter declaration at `i`. */
private predicate scopeDefinesParameterVariable(
CallableScope::Range scope, string name, Generated::Identifier i
) {
// In case of overlapping parameter names (e.g. `_`), only the first
// parameter will give rise to a variable
i =
min(Generated::Identifier other |
parameterAssignment(scope, name, other)
|
other order by other.getLocation().getStartLine(), other.getLocation().getStartColumn()
)
or
exists(Parameter p |
p = scope.getScopeElement().getParameter(_) and
name = p.(NamedParameter).getName()
|
i = p.(Generated::BlockParameter).getName() or
i = p.(Generated::HashSplatParameter).getName() or
i = p.(Generated::KeywordParameter).getName() or
i = p.(Generated::OptionalParameter).getName() or
i = p.(Generated::SplatParameter).getName()
)
}
/** Holds if `name` is assigned in `scope` at `i`. */
private predicate scopeAssigns(VariableScope scope, string name, Generated::Identifier i) {
(explicitAssignmentNode(i, _) or implicitAssignmentNode(i)) and
name = i.getValue() and
scope = enclosingScope(i)
}
/** Holds if location `one` starts strictly before location `two` */
pragma[inline]
private predicate strictlyBefore(Location one, Location two) {
one.getStartLine() < two.getStartLine()
or
one.getStartLine() = two.getStartLine() and one.getStartColumn() < two.getStartColumn()
}
/** A scope that may capture outer local variables. */
private class CapturingScope extends VariableScope {
CapturingScope() {
exists(Callable::Range c | c = this.getScopeElement() |
c instanceof Block::Range
or
c instanceof DoBlock::Range
or
c instanceof Lambda::Range // TODO: Check if this is actually the case
)
}
/** Holds if this scope inherits `name` from an outer scope `outer`. */
predicate inherits(string name, VariableScope outer) {
not scopeDefinesParameterVariable(this, name, _) and
(
outer = this.getOuterScope() and
(
scopeDefinesParameterVariable(outer, name, _)
or
exists(Generated::Identifier i |
scopeAssigns(outer, name, i) and
strictlyBefore(i.getLocation(), this.getLocation())
)
)
or
this.getOuterScope().(CapturingScope).inherits(name, outer)
)
}
}
cached
private module Cached {
/** Gets the enclosing scope for `node`. */
cached
VariableScope enclosingScope(Generated::AstNode node) {
result.getScopeElement() = parentOfNoScope*(parentOf(node))
}
cached
newtype TScope =
TGlobalScope() or
TTopLevelScope(Generated::Program node) or
TModuleScope(Generated::Module node) or
TClassScope(AstNode cls) {
cls instanceof Generated::Class or cls instanceof Generated::SingletonClass
} or
TCallableScope(Callable::Range c)
cached
newtype TVariable =
TGlobalVariable(string name) { name = any(Generated::GlobalVariable var).getValue() } or
TClassVariable(VariableScope scope, string name, Generated::AstNode decl) {
decl =
min(Generated::ClassVariable other |
classVariableAccess(other, name, scope)
|
other order by other.getLocation().getStartLine(), other.getLocation().getStartColumn()
)
} or
TInstanceVariable(VariableScope scope, string name, boolean instance, Generated::AstNode decl) {
decl =
min(Generated::InstanceVariable other |
instanceVariableAccess(other, name, scope, instance)
|
other order by other.getLocation().getStartLine(), other.getLocation().getStartColumn()
)
} or
TLocalVariable(VariableScope scope, string name, Generated::Identifier i) {
scopeDefinesParameterVariable(scope, name, i)
or
i =
min(Generated::Identifier other |
scopeAssigns(scope, name, other)
|
other order by other.getLocation().getStartLine(), other.getLocation().getStartColumn()
) and
not scopeDefinesParameterVariable(scope, name, _) and
not scope.(CapturingScope).inherits(name, _)
}
// Token types that can be vcalls
private class VcallToken = @token_identifier or @token_super;
/**
* Holds if `i` is an `identifier` node occurring in the context where it
* should be considered a VCALL. VCALL is the term that MRI/Ripper uses
* internally when there's an identifier without arguments or parentheses,
* i.e. it *might* be a method call, but it might also be a variable access,
* depending on the bindings in the current scope.
* ```rb
* foo # in MRI this is a VCALL, and the predicate should hold for this
* bar() # in MRI this would be an FCALL. Tree-sitter gives us a `call` node,
* # and the `method` field will be an `identifier`, but this predicate
* # will not hold for that identifier.
* ```
*/
cached
predicate vcall(VcallToken i) {
i = any(Generated::ArgumentList x).getChild(_)
or
i = any(Generated::Array x).getChild(_)
or
i = any(Generated::Assignment x).getRight()
or
i = any(Generated::Begin x).getChild(_)
or
i = any(Generated::BeginBlock x).getChild(_)
or
i = any(Generated::Binary x).getLeft()
or
i = any(Generated::Binary x).getRight()
or
i = any(Generated::Block x).getChild(_)
or
i = any(Generated::BlockArgument x).getChild()
or
i = any(Generated::Call x).getReceiver()
or
i = any(Generated::Case x).getValue()
or
i = any(Generated::Class x).getChild(_)
or
i = any(Generated::Conditional x).getCondition()
or
i = any(Generated::Conditional x).getConsequence()
or
i = any(Generated::Conditional x).getAlternative()
or
i = any(Generated::Do x).getChild(_)
or
i = any(Generated::DoBlock x).getChild(_)
or
i = any(Generated::ElementReference x).getChild(_)
or
i = any(Generated::ElementReference x).getObject()
or
i = any(Generated::Else x).getChild(_)
or
i = any(Generated::Elsif x).getCondition()
or
i = any(Generated::EndBlock x).getChild(_)
or
i = any(Generated::Ensure x).getChild(_)
or
i = any(Generated::Exceptions x).getChild(_)
or
i = any(Generated::HashSplatArgument x).getChild()
or
i = any(Generated::If x).getCondition()
or
i = any(Generated::IfModifier x).getCondition()
or
i = any(Generated::IfModifier x).getBody()
or
i = any(Generated::In x).getChild()
or
i = any(Generated::Interpolation x).getChild()
or
i = any(Generated::KeywordParameter x).getValue()
or
i = any(Generated::Method x).getChild(_)
or
i = any(Generated::Module x).getChild(_)
or
i = any(Generated::OperatorAssignment x).getRight()
or
i = any(Generated::OptionalParameter x).getValue()
or
i = any(Generated::Pair x).getKey()
or
i = any(Generated::Pair x).getValue()
or
i = any(Generated::ParenthesizedStatements x).getChild(_)
or
i = any(Generated::Pattern x).getChild()
or
i = any(Generated::Program x).getChild(_)
or
i = any(Generated::Range x).getChild(_)
or
i = any(Generated::RescueModifier x).getBody()
or
i = any(Generated::RescueModifier x).getHandler()
or
i = any(Generated::RightAssignmentList x).getChild(_)
or
i = any(Generated::ScopeResolution x).getScope()
or
i = any(Generated::SingletonClass x).getValue()
or
i = any(Generated::SingletonClass x).getChild(_)
or
i = any(Generated::SingletonMethod x).getChild(_)
or
i = any(Generated::SingletonMethod x).getObject()
or
i = any(Generated::SplatArgument x).getChild()
or
i = any(Generated::Superclass x).getChild()
or
i = any(Generated::Then x).getChild(_)
or
i = any(Generated::Unary x).getOperand()
or
i = any(Generated::Unless x).getCondition()
or
i = any(Generated::UnlessModifier x).getCondition()
or
i = any(Generated::UnlessModifier x).getBody()
or
i = any(Generated::Until x).getCondition()
or
i = any(Generated::UntilModifier x).getCondition()
or
i = any(Generated::UntilModifier x).getBody()
or
i = any(Generated::While x).getCondition()
or
i = any(Generated::WhileModifier x).getCondition()
or
i = any(Generated::WhileModifier x).getBody()
}
cached
predicate access(Generated::Identifier access, Variable variable) {
exists(string name | name = access.getValue() |
variable = enclosingScope(access).getVariable(name) and
not strictlyBefore(access.getLocation(), variable.getLocation()) and
// In case of overlapping parameter names, later parameters should not
// be considered accesses to the first parameter
if parameterAssignment(_, _, access)
then scopeDefinesParameterVariable(_, _, access)
else any()
or
exists(VariableScope declScope |
variable = declScope.getVariable(name) and
enclosingScope(access).(CapturingScope).inherits(name, declScope)
)
)
}
private class Access extends Generated::Token {
Access() { access(this, _) or this instanceof Generated::GlobalVariable }
}
cached
predicate explicitWriteAccess(Access access, Generated::AstNode assignment) {
explicitAssignmentNode(access, assignment)
}
cached
predicate implicitWriteAccess(Access access) {
implicitAssignmentNode(access)
or
scopeDefinesParameterVariable(_, _, access)
}
cached
predicate isCapturedAccess(LocalVariableAccess::Range access) {
access.getVariable().getDeclaringScope() != enclosingScope(access)
}
cached
predicate instanceVariableAccess(Generated::InstanceVariable var, InstanceVariable v) {
exists(string name, VariableScope scope, boolean instance |
v = TInstanceVariable(scope, name, instance, _) and
instanceVariableAccess(var, name, scope, instance)
)
}
cached
predicate classVariableAccess(Generated::ClassVariable var, ClassVariable variable) {
exists(VariableScope scope, string name |
variable = TClassVariable(scope, name, _) and
classVariableAccess(var, name, scope)
)
}
}
import Cached
module VariableScope {
abstract class Range extends TScope {
abstract string toString();
abstract AstNode getScopeElement();
}
}
module GlobalScope {
class Range extends VariableScope::Range, TGlobalScope {
override string toString() { result = "global scope" }
override AstNode getScopeElement() { none() }
}
}
module TopLevelScope {
class Range extends VariableScope::Range, TTopLevelScope {
override string toString() { result = "top-level scope" }
override AstNode getScopeElement() { TTopLevelScope(result) = this }
}
}
module ModuleScope {
class Range extends VariableScope::Range, TModuleScope {
override string toString() { result = "module scope" }
override AstNode getScopeElement() { TModuleScope(result) = this }
}
}
module ClassScope {
class Range extends VariableScope::Range, TClassScope {
override string toString() { result = "class scope" }
override AstNode getScopeElement() { TClassScope(result) = this }
}
}
module CallableScope {
class Range extends VariableScope::Range, TCallableScope {
private Callable::Range c;
Range() { this = TCallableScope(c) }
override string toString() {
(c instanceof Method or c instanceof SingletonMethod) and
result = "method scope"
or
c instanceof Lambda and
result = "lambda scope"
or
c instanceof Block and
result = "block scope"
}
override Callable::Range getScopeElement() { TCallableScope(result) = this }
}
}
module Variable {
class Range extends TVariable {
abstract string getName();
string toString() { result = this.getName() }
abstract Location getLocation();
abstract VariableScope getDeclaringScope();
}
}
module LocalVariable {
class Range extends Variable::Range, TLocalVariable {
private VariableScope scope;
private string name;
private Generated::Identifier i;
Range() { this = TLocalVariable(scope, name, i) }
final override string getName() { result = name }
final override Location getLocation() { result = i.getLocation() }
final override VariableScope getDeclaringScope() { result = scope }
final VariableAccess getDefiningAccess() { result = i }
}
}
module GlobalVariable {
class Range extends Variable::Range, TGlobalVariable {
private string name;
Range() { this = TGlobalVariable(name) }
final override string getName() { result = name }
final override Location getLocation() { none() }
final override VariableScope getDeclaringScope() { result = TGlobalScope() }
}
}
private class ModuleOrClassScope = TClassScope or TModuleScope or TTopLevelScope;
module InstanceVariable {
class Range extends Variable::Range, TInstanceVariable {
private ModuleOrClassScope scope;
private boolean instance;
private string name;
private Generated::AstNode decl;
Range() { this = TInstanceVariable(scope, name, instance, decl) }
final override string getName() { result = name }
final predicate isClassInstanceVariable() { instance = false }
final override Location getLocation() { result = decl.getLocation() }
final override VariableScope getDeclaringScope() { result = scope }
}
}
module ClassVariable {
class Range extends Variable::Range, TClassVariable {
private ModuleOrClassScope scope;
private string name;
private Generated::AstNode decl;
Range() { this = TClassVariable(scope, name, decl) }
final override string getName() { result = name }
final override Location getLocation() { result = decl.getLocation() }
final override VariableScope getDeclaringScope() { result = scope }
}
}
module VariableAccess {
abstract class Range extends Expr::Range {
abstract Variable getVariable();
}
}
module LocalVariableAccess {
class Range extends VariableAccess::Range, @token_identifier {
override Generated::Identifier generated;
LocalVariable variable;
Range() {
access(this, variable) and
(
explicitWriteAccess(this, _)
or
implicitWriteAccess(this)
or
vcall(this)
)
}
final override LocalVariable getVariable() { result = variable }
}
}
module GlobalVariableAccess {
class Range extends VariableAccess::Range, @token_global_variable {
GlobalVariable variable;
Range() { this.(Generated::GlobalVariable).getValue() = variable.getName() }
final override GlobalVariable getVariable() { result = variable }
}
}
module InstanceVariableAccess {
class Range extends VariableAccess::Range, @token_instance_variable {
InstanceVariable variable;
Range() { instanceVariableAccess(this, variable) }
final override InstanceVariable getVariable() { result = variable }
}
}
module ClassVariableAccess {
class Range extends VariableAccess::Range, @token_class_variable {
ClassVariable variable;
Range() { classVariableAccess(this, variable) }
final override ClassVariable getVariable() { result = variable }
}
}