Support synthesis of blocks (without a new variable scope)

This commit is contained in:
Alex Ford
2021-10-22 20:19:10 +01:00
committed by Tom Hvitved
parent 04df56d1c0
commit a743067dc8
5 changed files with 75 additions and 15 deletions

View File

@@ -226,3 +226,22 @@ class BraceBlock extends Block, TBraceBlock {
final override string getAPrimaryQlClass() { result = "BraceBlock" }
}
/**
* A synthesized block, such as the block synthesized from the body of
* a `for` loop.
*/
class SynthBlock extends Block, TBlockSynth {
SynthBlock() { this = TBlockSynth(_, _) }
final override Parameter getParameter(int n) { synthChild(this, n, result) }
final override Stmt getStmt(int i) {
i >= 0 and
synthChild(this, i + this.getNumberOfParameters(), result)
}
final override string toString() { result = "{ ... }" }
final override string getAPrimaryQlClass() { result = "SynthBlock" }
}

View File

@@ -3,22 +3,12 @@ private import internal.AST
private import internal.Scope
private import internal.TreeSitter
class Scope extends AstNode, TScopeType {
private Scope::Range range;
class Scope extends AstNode, TScopeType instanceof ScopeImpl {
Scope getOuterScope() { result = super.getOuterScopeImpl() }
Scope() { range = toGenerated(this) }
Variable getAVariable() { result = super.getAVariableImpl() }
/** Gets the scope in which this scope is nested, if any. */
Scope getOuterScope() { toGenerated(result) = range.getOuterScope() }
/** Gets a variable that is declared in this scope. */
final Variable getAVariable() { result.getDeclaringScope() = this }
/** Gets the variable declared in this scope with the given name, if any. */
final Variable getVariable(string name) {
result = this.getAVariable() and
result.getName() = name
}
Variable getVariable(string name) { result = super.getVariableImpl(name) }
}
class SelfScope extends Scope, TSelfScopeType { }

View File

@@ -93,6 +93,7 @@ private module Cached {
} or
TBlockArgument(Ruby::BlockArgument g) or
TBlockParameter(Ruby::BlockParameter g) or
TBlockSynth(AST::AstNode parent, int i) { mkSynthChild(BlockKind(), parent, i) } or
TBraceBlock(Ruby::Block g) { not g.getParent() instanceof Ruby::Lambda } or
TBreakStmt(Ruby::Break g) or
TCaseEqExpr(Ruby::Binary g) { g instanceof @ruby_binary_equalequalequal } or
@@ -491,6 +492,8 @@ private module Cached {
or
result = TBitwiseXorExprSynth(parent, i)
or
result = TBlockSynth(parent, i)
or
result = TClassVariableAccessSynth(parent, i, _)
or
result = TConstantReadAccessSynth(parent, i, _)
@@ -644,7 +647,7 @@ class TCallable = TMethodBase or TLambda or TBlock;
class TMethodBase = TMethod or TSingletonMethod;
class TBlock = TDoBlock or TBraceBlock;
class TBlock = TDoBlock or TBraceBlock or TBlockSynth;
class TModuleBase = TToplevel or TNamespace or TSingletonClass;

View File

@@ -1,7 +1,9 @@
private import TreeSitter
private import codeql.ruby.AST
private import codeql.ruby.ast.Scope
private import codeql.ruby.ast.internal.AST
private import codeql.ruby.ast.internal.Parameter
private import codeql.ruby.ast.internal.Variable
class TScopeType = TMethodBase or TModuleLike or TBlockLike;
@@ -15,6 +17,10 @@ private class TBlockLike = TDoBlock or TLambda or TBlock or TEndBlock;
private class TModuleLike = TToplevel or TModuleDeclaration or TClassDeclaration or TSingletonClass;
private class TScopeReal = TMethodBase or TModuleLike or TDoBlock or TLambda or TBraceBlock;
private class TScopeSynth = TBlockSynth;
module Scope {
class TypeRange = Callable::TypeRange or ModuleBase::TypeRange or @ruby_end_block;
@@ -128,3 +134,44 @@ Scope::Range scopeOf(Ruby::AstNode n) {
not p instanceof Scope::Range and result = scopeOf(p)
)
}
abstract class ScopeImpl extends AstNode, TScopeType {
abstract Scope getOuterScopeImpl();
abstract Variable getAVariableImpl();
final Variable getVariableImpl(string name) {
result = this.getAVariableImpl() and
result.getName() = name
}
}
private class ScopeRealImpl extends ScopeImpl, TScopeReal {
private Scope::Range range;
ScopeRealImpl() { range = toGenerated(this) }
override Scope getOuterScopeImpl() { toGenerated(result) = range.getOuterScope() }
override Variable getAVariableImpl() { result.getDeclaringScope() = this }
}
// We desugar for loops by implementing them as calls to `each` with a block
// argument. Though this is how the desugaring is described in the MRI parser,
// in practice there is not a real nested scope created, so variables that
// may appear to be local to the loop body (e.g. the iteration variable) are
// scoped to the outer scope rather than the loop body.
private class ScopeSynthImpl extends ScopeImpl, TScopeSynth {
ScopeSynthImpl() { this = TBlockSynth(_, _) }
override Scope getOuterScopeImpl() { scopeOf(toGeneratedInclSynth(this)) = toGenerated(result) }
override Variable getAVariableImpl() {
// Synthesized variables introduced as parameters to this scope
// As this variable is also synthetic, it is genuinely local to this scope.
exists(SimpleParameter p | p = TSimpleParameterSynth(this, _) |
p.getVariable() = result and
exists(TLocalVariableAccessSynth(p, _, result))
)
}
}

View File

@@ -15,6 +15,7 @@ newtype SynthKind =
BitwiseAndExprKind() or
BitwiseOrExprKind() or
BitwiseXorExprKind() or
BlockKind() or
ClassVariableAccessKind(ClassVariable v) or
DivExprKind() or
ExponentExprKind() or