mirror of
https://github.com/github/codeql.git
synced 2026-02-20 08:53:49 +01:00
595 lines
17 KiB
Plaintext
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 }
|
|
}
|
|
}
|