mirror of
https://github.com/github/codeql.git
synced 2026-04-26 17:25:19 +02:00
Ruby: Reimplement flow through captured variables using field flow
This commit is contained in:
@@ -117,6 +117,17 @@ class MethodCall extends Call instanceof MethodCallImpl {
|
||||
*/
|
||||
final Block getBlock() { result = super.getBlockImpl() }
|
||||
|
||||
/**
|
||||
* Gets the block argument of this method call, if any.
|
||||
* ```rb
|
||||
* foo(&block)
|
||||
* ```
|
||||
*/
|
||||
final BlockArgument getBlockArgument() { result = this.getAnArgument() }
|
||||
|
||||
/** Holds if this method call has a block or block argument. */
|
||||
final predicate hasBlock() { exists(this.getBlock()) or exists(this.getBlockArgument()) }
|
||||
|
||||
/**
|
||||
* Holds if the safe navigation operator (`&.`) is used in this call.
|
||||
* ```rb
|
||||
|
||||
@@ -202,7 +202,10 @@ module ExprNodes {
|
||||
override LhsExpr getExpr() { result = super.getExpr() }
|
||||
|
||||
/** Gets a variable used in (or introduced by) this LHS. */
|
||||
Variable getAVariable() { result = e.(VariableAccess).getVariable() }
|
||||
deprecated Variable getAVariable() { result = e.(VariableAccess).getVariable() }
|
||||
|
||||
/** Gets the variable used in (or introduced by) this LHS. */
|
||||
Variable getVariable() { result = e.(VariableAccess).getVariable() }
|
||||
}
|
||||
|
||||
private class AssignExprChildMapping extends ExprChildMapping, AssignExpr {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
/** Provides classes and predicates for defining flow summaries. */
|
||||
|
||||
import codeql.ruby.AST
|
||||
private import codeql.ruby.CFG
|
||||
private import codeql.ruby.typetracking.TypeTracker
|
||||
import codeql.ruby.DataFlow
|
||||
private import internal.FlowSummaryImpl as Impl
|
||||
private import internal.DataFlowDispatch
|
||||
@@ -158,3 +160,66 @@ abstract class SimpleSummarizedCallable extends SummarizedCallable {
|
||||
}
|
||||
|
||||
class RequiredSummaryComponentStack = Impl::Public::RequiredSummaryComponentStack;
|
||||
|
||||
/**
|
||||
* Provides a set of special flow summaries to ensure that callbacks passed into
|
||||
* library methods will be passed as `self` arguments into themeselves. That is,
|
||||
* we are assuming that callbacks passed into library methods will be called, which is
|
||||
* needed for flow through captured variables.
|
||||
*/
|
||||
private module LibraryCallbackSummaries {
|
||||
private predicate libraryCall(CfgNodes::ExprNodes::CallCfgNode call) {
|
||||
not exists(getTarget(call))
|
||||
}
|
||||
|
||||
private class LibraryBlockMethod extends SummarizedCallable {
|
||||
LibraryBlockMethod() { this = "<library method accepting a block>" }
|
||||
|
||||
final override MethodCall getACall() {
|
||||
libraryCall(result.getAControlFlowNode()) and
|
||||
result.hasBlock()
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[block]" and
|
||||
output = "Argument[block].Parameter[lambda-self]" and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
private DataFlow::LocalSourceNode trackLambdaCreation(TypeTracker t) {
|
||||
t.start() and
|
||||
lambdaCreation(result, TLambdaCallKind(), _)
|
||||
or
|
||||
exists(TypeTracker t2 | result = trackLambdaCreation(t2).track(t2, t)) and
|
||||
not result instanceof DataFlow::SelfParameterNode
|
||||
}
|
||||
|
||||
private predicate libraryCallHasLambdaArg(CfgNodes::ExprNodes::CallCfgNode call, int i) {
|
||||
exists(CfgNodes::ExprCfgNode arg |
|
||||
arg = call.getArgument(i) and
|
||||
arg = trackLambdaCreation(TypeTracker::end()).getALocalUse().asExpr() and
|
||||
libraryCall(call) and
|
||||
not arg instanceof CfgNodes::ExprNodes::BlockArgumentCfgNode
|
||||
)
|
||||
}
|
||||
|
||||
private class LibraryLambdaMethod extends SummarizedCallable {
|
||||
private int i;
|
||||
|
||||
LibraryLambdaMethod() {
|
||||
this = "<library method accepting a lambda at index " + i + ">" and
|
||||
i in [0 .. 10]
|
||||
}
|
||||
|
||||
final override MethodCall getACall() {
|
||||
libraryCallHasLambdaArg(result.getAControlFlowNode(), i)
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[" + i + "]" and
|
||||
output = "Argument[" + i + "].Parameter[lambda-self]" and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,8 +21,7 @@ private class SelfLocalSourceNode extends DataFlow::LocalSourceNode {
|
||||
SelfLocalSourceNode() {
|
||||
self = this.(SelfParameterNodeImpl).getSelfVariable()
|
||||
or
|
||||
self = this.(SsaSelfDefinitionNode).getVariable() and
|
||||
not LocalFlow::localFlowSsaParamInput(_, this)
|
||||
self = this.(SsaSelfDefinitionNode).getVariable()
|
||||
}
|
||||
|
||||
/** Gets the `self` variable. */
|
||||
@@ -424,6 +423,7 @@ private module Cached {
|
||||
cached
|
||||
newtype TArgumentPosition =
|
||||
TSelfArgumentPosition() or
|
||||
TLambdaSelfArgumentPosition() or
|
||||
TBlockArgumentPosition() or
|
||||
TPositionalArgumentPosition(int pos) {
|
||||
exists(Call c | exists(c.getArgument(pos)))
|
||||
@@ -446,6 +446,7 @@ private module Cached {
|
||||
cached
|
||||
newtype TParameterPosition =
|
||||
TSelfParameterPosition() or
|
||||
TLambdaSelfParameterPosition() or
|
||||
TBlockParameterPosition() or
|
||||
TPositionalParameterPosition(int pos) {
|
||||
pos = any(Parameter p).getPosition()
|
||||
@@ -941,20 +942,24 @@ private module TrackSingletonMethodOnInstanceInput implements CallGraphConstruct
|
||||
private predicate paramReturnFlow(
|
||||
DataFlow::Node nodeFrom, DataFlow::PostUpdateNode nodeTo, StepSummary summary
|
||||
) {
|
||||
exists(RelevantCall call, DataFlow::Node arg, DataFlow::ParameterNode p, Expr nodeFromPreExpr |
|
||||
exists(
|
||||
RelevantCall call, DataFlow::Node arg, DataFlow::ParameterNode p,
|
||||
CfgNodes::ExprCfgNode nodeFromPreExpr
|
||||
|
|
||||
TypeTrackerSpecific::callStep(call, arg, p) and
|
||||
nodeTo.getPreUpdateNode() = arg and
|
||||
summary.toString() = "return" and
|
||||
(
|
||||
nodeFromPreExpr = nodeFrom.(DataFlow::PostUpdateNode).getPreUpdateNode().asExpr().getExpr()
|
||||
nodeFromPreExpr = nodeFrom.(DataFlow::PostUpdateNode).getPreUpdateNode().asExpr()
|
||||
or
|
||||
nodeFromPreExpr = nodeFrom.asExpr().getExpr() and
|
||||
singletonMethodOnInstance(_, _, nodeFromPreExpr)
|
||||
nodeFromPreExpr = nodeFrom.asExpr() and
|
||||
singletonMethodOnInstance(_, _, nodeFromPreExpr.getExpr())
|
||||
)
|
||||
|
|
||||
nodeFromPreExpr = p.getParameter().(NamedParameter).getVariable().getAnAccess()
|
||||
nodeFromPreExpr =
|
||||
LocalFlow::getParameterDefNode(p.getParameter()).getDefinitionExt().getARead()
|
||||
or
|
||||
nodeFromPreExpr = p.(SelfParameterNodeImpl).getSelfVariable().getAnAccess()
|
||||
nodeFromPreExpr = p.(SelfParameterNodeImpl).getSelfDefinition().getARead()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1276,6 +1281,9 @@ class ParameterPosition extends TParameterPosition {
|
||||
/** Holds if this position represents a `self` parameter. */
|
||||
predicate isSelf() { this = TSelfParameterPosition() }
|
||||
|
||||
/** Holds if this position represents a reference to a lambda itself. Only used for tracking flow through captured variables. */
|
||||
predicate isLambdaSelf() { this = TLambdaSelfParameterPosition() }
|
||||
|
||||
/** Holds if this position represents a block parameter. */
|
||||
predicate isBlock() { this = TBlockParameterPosition() }
|
||||
|
||||
@@ -1313,6 +1321,8 @@ class ParameterPosition extends TParameterPosition {
|
||||
string toString() {
|
||||
this.isSelf() and result = "self"
|
||||
or
|
||||
this.isLambdaSelf() and result = "lambda self"
|
||||
or
|
||||
this.isBlock() and result = "block"
|
||||
or
|
||||
exists(int pos | this.isPositional(pos) and result = "position " + pos)
|
||||
@@ -1342,6 +1352,9 @@ class ArgumentPosition extends TArgumentPosition {
|
||||
/** Holds if this position represents a `self` argument. */
|
||||
predicate isSelf() { this = TSelfArgumentPosition() }
|
||||
|
||||
/** Holds if this position represents a lambda `self` argument. Only used for tracking flow through captured variables. */
|
||||
predicate isLambdaSelf() { this = TLambdaSelfArgumentPosition() }
|
||||
|
||||
/** Holds if this position represents a block argument. */
|
||||
predicate isBlock() { this = TBlockArgumentPosition() }
|
||||
|
||||
@@ -1374,6 +1387,8 @@ class ArgumentPosition extends TArgumentPosition {
|
||||
string toString() {
|
||||
this.isSelf() and result = "self"
|
||||
or
|
||||
this.isLambdaSelf() and result = "lambda self"
|
||||
or
|
||||
this.isBlock() and result = "block"
|
||||
or
|
||||
exists(int pos | this.isPositional(pos) and result = "position " + pos)
|
||||
@@ -1393,16 +1408,24 @@ class ArgumentPosition extends TArgumentPosition {
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate parameterPositionIsNotSelf(ParameterPosition ppos) { not ppos.isSelf() }
|
||||
private predicate parameterPositionIsNotSelf(ParameterPosition ppos) {
|
||||
not ppos.isSelf() and
|
||||
not ppos.isLambdaSelf()
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate argumentPositionIsNotSelf(ArgumentPosition apos) { not apos.isSelf() }
|
||||
private predicate argumentPositionIsNotSelf(ArgumentPosition apos) {
|
||||
not apos.isSelf() and
|
||||
not apos.isLambdaSelf()
|
||||
}
|
||||
|
||||
/** Holds if arguments at position `apos` match parameters at position `ppos`. */
|
||||
pragma[nomagic]
|
||||
predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) {
|
||||
ppos.isSelf() and apos.isSelf()
|
||||
or
|
||||
ppos.isLambdaSelf() and apos.isLambdaSelf()
|
||||
or
|
||||
ppos.isBlock() and apos.isBlock()
|
||||
or
|
||||
exists(int pos | ppos.isPositional(pos) and apos.isPositional(pos))
|
||||
@@ -1441,8 +1464,6 @@ predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) {
|
||||
* This is a temporary hook to support technical debt in the Go language; do not use.
|
||||
*/
|
||||
pragma[inline]
|
||||
predicate golangSpecificParamArgFilter(
|
||||
DataFlowCall call, DataFlow::ParameterNode p, ArgumentNode arg
|
||||
) {
|
||||
predicate golangSpecificParamArgFilter(DataFlowCall call, ParameterNodeImpl p, ArgumentNode arg) {
|
||||
any()
|
||||
}
|
||||
|
||||
@@ -17,6 +17,9 @@ module RubyDataFlow implements InputSig {
|
||||
import Private
|
||||
import Public
|
||||
|
||||
// includes `LambdaSelfParameterNode`, which is not part of the public API
|
||||
class ParameterNode = Private::ParameterNodeImpl;
|
||||
|
||||
predicate isParameterNode(ParameterNode p, DataFlowCallable c, ParameterPosition pos) {
|
||||
Private::isParameterNode(p, c, pos)
|
||||
}
|
||||
|
||||
@@ -83,13 +83,13 @@ module LocalFlow {
|
||||
def instanceof Ssa::PhiNode
|
||||
or
|
||||
def instanceof SsaImpl::PhiReadNode
|
||||
//TODO: or def instanceof LocalFlow::UncertainExplicitSsaDefinition
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `nodeFrom` is a node for SSA definition `def`, which can reach `next`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate localFlowSsaInputFromDef(
|
||||
SsaDefinitionExtNode nodeFrom, SsaImpl::DefinitionExt def, SsaInputDefinitionExtNode next
|
||||
) {
|
||||
@@ -102,20 +102,23 @@ module LocalFlow {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `exprFrom` is a last read of SSA definition `def`, which
|
||||
* Holds if `nodeFrom` is a last read of SSA definition `def`, which
|
||||
* can reach `next`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate localFlowSsaInputFromRead(
|
||||
CfgNodes::ExprCfgNode exprFrom, SsaImpl::DefinitionExt def, SsaInputDefinitionExtNode next
|
||||
SsaImpl::DefinitionExt def, Node nodeFrom, SsaInputDefinitionExtNode next
|
||||
) {
|
||||
exists(BasicBlock bb, int i |
|
||||
exists(BasicBlock bb, int i, CfgNodes::ExprCfgNode exprFrom |
|
||||
SsaImpl::lastRefBeforeRedefExt(def, bb, i, next.getDefinitionExt()) and
|
||||
exprFrom = bb.getNode(i) and
|
||||
exprFrom.getExpr() instanceof VariableReadAccess
|
||||
exprFrom.getExpr() instanceof VariableReadAccess and
|
||||
exprFrom = [nodeFrom.asExpr(), nodeFrom.(PostUpdateNode).getPreUpdateNode().asExpr()]
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the SSA definition node corresponding to parameter `p`. */
|
||||
pragma[nomagic]
|
||||
SsaDefinitionExtNode getParameterDefNode(NamedParameter p) {
|
||||
exists(BasicBlock bb, int i |
|
||||
bb.getNode(i).getAstNode() = p.getDefiningAccess() and
|
||||
@@ -123,18 +126,14 @@ module LocalFlow {
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the SSA definition node corresponding to the implicit `self` parameter for `m`. */
|
||||
private SsaDefinitionExtNode getSelfParameterDefNode(MethodBase m) {
|
||||
result.getDefinitionExt().(Ssa::SelfDefinition).getSourceVariable().getDeclaringScope() = m
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `nodeFrom` is a parameter node, and `nodeTo` is a corresponding SSA node.
|
||||
*/
|
||||
predicate localFlowSsaParamInput(Node nodeFrom, SsaDefinitionExtNode nodeTo) {
|
||||
nodeTo = getParameterDefNode(nodeFrom.(ParameterNodeImpl).getParameter())
|
||||
pragma[nomagic]
|
||||
predicate localFlowSsaParamInput(ParameterNodeImpl nodeFrom, SsaDefinitionExtNode nodeTo) {
|
||||
nodeTo = getParameterDefNode(nodeFrom.getParameter())
|
||||
or
|
||||
nodeTo = getSelfParameterDefNode(nodeFrom.(SelfParameterNodeImpl).getMethod())
|
||||
nodeTo.getDefinitionExt() = nodeFrom.(SelfParameterNodeImpl).getSelfDefinition()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -147,39 +146,30 @@ module LocalFlow {
|
||||
|
||||
/**
|
||||
* Holds if there is a local flow step from `nodeFrom` to `nodeTo` involving
|
||||
* some SSA definition.
|
||||
* SSA definition `def`.
|
||||
*/
|
||||
private predicate localSsaFlowStep(Node nodeFrom, Node nodeTo) {
|
||||
exists(SsaImpl::DefinitionExt def |
|
||||
// Flow from assignment into SSA definition
|
||||
def.(Ssa::WriteDefinition).assigns(nodeFrom.asExpr()) and
|
||||
nodeTo.(SsaDefinitionExtNode).getDefinitionExt() = def
|
||||
or
|
||||
// Flow from SSA definition to first read
|
||||
def = nodeFrom.(SsaDefinitionExtNode).getDefinitionExt() and
|
||||
firstReadExt(def, nodeTo.asExpr())
|
||||
or
|
||||
// Flow from read to next read
|
||||
localSsaFlowStepUseUse(def, nodeFrom.(PostUpdateNode).getPreUpdateNode(), nodeTo)
|
||||
or
|
||||
// Flow into phi (read) SSA definition node from def
|
||||
localFlowSsaInputFromDef(nodeFrom, def, nodeTo)
|
||||
)
|
||||
pragma[nomagic]
|
||||
predicate localSsaFlowStep(SsaImpl::DefinitionExt def, Node nodeFrom, Node nodeTo) {
|
||||
// Flow from assignment into SSA definition
|
||||
def.(Ssa::WriteDefinition).assigns(nodeFrom.asExpr()) and
|
||||
nodeTo.(SsaDefinitionExtNode).getDefinitionExt() = def
|
||||
or
|
||||
localFlowSsaParamInput(nodeFrom, nodeTo)
|
||||
// TODO
|
||||
// or
|
||||
// // Flow into uncertain SSA definition
|
||||
// exists(LocalFlow::UncertainExplicitSsaDefinition uncertain |
|
||||
// localFlowSsaInput(nodeFrom, def, uncertain) and
|
||||
// uncertain = nodeTo.(SsaDefinitionNode).getDefinition() and
|
||||
// def = uncertain.getPriorDefinition()
|
||||
// )
|
||||
// Flow from SSA definition to first read
|
||||
def = nodeFrom.(SsaDefinitionExtNode).getDefinitionExt() and
|
||||
firstReadExt(def, nodeTo.asExpr())
|
||||
or
|
||||
// Flow from post-update read to next read
|
||||
localSsaFlowStepUseUse(def, nodeFrom.(PostUpdateNode).getPreUpdateNode(), nodeTo)
|
||||
or
|
||||
// Flow into phi (read) SSA definition node from def
|
||||
localFlowSsaInputFromDef(nodeFrom, def, nodeTo)
|
||||
or
|
||||
localFlowSsaParamInput(nodeFrom, nodeTo) and
|
||||
def = nodeTo.(SsaDefinitionExtNode).getDefinitionExt()
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
predicate localFlowStepCommon(Node nodeFrom, Node nodeTo) {
|
||||
localSsaFlowStep(nodeFrom, nodeTo)
|
||||
or
|
||||
nodeFrom.asExpr() = nodeTo.asExpr().(CfgNodes::ExprNodes::BlockArgumentCfgNode).getValue()
|
||||
or
|
||||
nodeFrom.asExpr() = getALastEvalNode(nodeTo.asExpr())
|
||||
@@ -218,7 +208,7 @@ module LocalFlow {
|
||||
}
|
||||
}
|
||||
|
||||
/** An argument of a call (including qualifier arguments, excluding block arguments). */
|
||||
/** An argument of a call (including qualifier arguments and block arguments). */
|
||||
private class Argument extends CfgNodes::ExprCfgNode {
|
||||
private CfgNodes::ExprNodes::CallCfgNode call;
|
||||
private ArgumentPosition arg;
|
||||
@@ -239,7 +229,11 @@ private class Argument extends CfgNodes::ExprCfgNode {
|
||||
arg.isKeyword(p.getKey().getConstantValue().getSymbol())
|
||||
)
|
||||
or
|
||||
this = call.getReceiver() and arg.isSelf()
|
||||
this = call.getReceiver() and
|
||||
arg.isSelf()
|
||||
or
|
||||
lambdaCallExpr(call, this) and
|
||||
arg.isLambdaSelf()
|
||||
or
|
||||
this = call.getAnArgument() and
|
||||
this.getExpr() instanceof HashSplatExpr and
|
||||
@@ -250,6 +244,13 @@ private class Argument extends CfgNodes::ExprCfgNode {
|
||||
this.getExpr() instanceof SplatExpr and
|
||||
arg.isSplat(pos)
|
||||
)
|
||||
or
|
||||
this = call.getAnArgument() and
|
||||
this.getExpr() instanceof BlockArgument and
|
||||
arg.isBlock()
|
||||
or
|
||||
this = call.getBlock() and
|
||||
arg.isBlock()
|
||||
}
|
||||
|
||||
/** Holds if this expression is the `i`th argument of `c`. */
|
||||
@@ -266,16 +267,127 @@ predicate isNonConstantExpr(CfgNodes::ExprCfgNode n) {
|
||||
|
||||
/** Provides logic related to captured variables. */
|
||||
module VariableCapture {
|
||||
class CapturedVariable extends LocalVariable {
|
||||
CapturedVariable() { this.isCaptured() }
|
||||
private import codeql.dataflow.VariableCapture as Shared
|
||||
|
||||
CfgScope getCfgScope() {
|
||||
exists(Scope scope | scope = this.getDeclaringScope() |
|
||||
result = scope
|
||||
or
|
||||
result = scope.(ModuleBase).getCfgScope()
|
||||
)
|
||||
private predicate closureFlowStep(CfgNodes::ExprCfgNode e1, CfgNodes::ExprCfgNode e2) {
|
||||
e1 = getALastEvalNode(e2)
|
||||
or
|
||||
exists(Ssa::Definition def |
|
||||
def.getARead() = e2 and
|
||||
def.getAnUltimateDefinition().(Ssa::WriteDefinition).assigns(e1)
|
||||
)
|
||||
}
|
||||
|
||||
private module CaptureInput implements Shared::InputSig {
|
||||
private import ruby as R
|
||||
private import codeql.ruby.controlflow.ControlFlowGraph
|
||||
private import codeql.ruby.controlflow.BasicBlocks as BasicBlocks
|
||||
|
||||
class Location = R::Location;
|
||||
|
||||
class BasicBlock extends BasicBlocks::BasicBlock {
|
||||
Callable getEnclosingCallable() { result = this.getScope() }
|
||||
}
|
||||
|
||||
BasicBlock getImmediateBasicBlockDominator(BasicBlock bb) {
|
||||
result = bb.getImmediateDominator()
|
||||
}
|
||||
|
||||
BasicBlock getABasicBlockSuccessor(BasicBlock bb) { result = bb.getASuccessor() }
|
||||
|
||||
class CapturedVariable extends LocalVariable {
|
||||
CapturedVariable() { this.isCaptured() }
|
||||
|
||||
Callable getCallable() {
|
||||
exists(Scope scope | scope = this.getDeclaringScope() |
|
||||
result = scope
|
||||
or
|
||||
result = scope.(ModuleBase).getCfgScope()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
abstract class CapturedParameter extends CapturedVariable {
|
||||
abstract ParameterNode getParameterNode();
|
||||
}
|
||||
|
||||
private class CapturedNamedParameter extends CapturedParameter {
|
||||
private NamedParameter p;
|
||||
|
||||
CapturedNamedParameter() { this = p.getVariable() }
|
||||
|
||||
override ParameterNode getParameterNode() { result.asParameter() = p }
|
||||
}
|
||||
|
||||
private class CapturedSelfParameter extends CapturedParameter, SelfVariable {
|
||||
override SelfParameterNode getParameterNode() { this = result.getSelfVariable() }
|
||||
}
|
||||
|
||||
class Expr extends CfgNodes::ExprCfgNode {
|
||||
predicate hasCfgNode(BasicBlock bb, int i) { this = bb.getNode(i) }
|
||||
}
|
||||
|
||||
class VariableWrite extends Expr, CfgNodes::ExprNodes::AssignExprCfgNode {
|
||||
CapturedVariable v;
|
||||
|
||||
VariableWrite() { v = this.getLhs().getVariable() }
|
||||
|
||||
CapturedVariable getVariable() { result = v }
|
||||
}
|
||||
|
||||
class VariableRead extends Expr instanceof CfgNodes::ExprNodes::LocalVariableReadAccessCfgNode {
|
||||
CapturedVariable v;
|
||||
|
||||
VariableRead() { v = super.getVariable() }
|
||||
|
||||
CapturedVariable getVariable() { result = v }
|
||||
}
|
||||
|
||||
class ClosureExpr extends Expr {
|
||||
Callable c;
|
||||
|
||||
ClosureExpr() { lambdaCreationExpr(this, _, c) }
|
||||
|
||||
predicate hasBody(Callable body) { body = c }
|
||||
|
||||
predicate hasAliasedAccess(Expr f) { closureFlowStep+(this, f) and not closureFlowStep(f, _) }
|
||||
}
|
||||
|
||||
class Callable extends CfgScope {
|
||||
predicate isConstructor() { none() }
|
||||
}
|
||||
}
|
||||
|
||||
class CapturedVariable = CaptureInput::CapturedVariable;
|
||||
|
||||
class ClosureExpr = CaptureInput::ClosureExpr;
|
||||
|
||||
module Flow = Shared::Flow<CaptureInput>;
|
||||
|
||||
private Flow::ClosureNode asClosureNode(Node n) {
|
||||
result = n.(CaptureNode).getSynthesizedCaptureNode()
|
||||
or
|
||||
result.(Flow::ExprNode).getExpr() = n.asExpr()
|
||||
or
|
||||
result.(Flow::VariableWriteSourceNode).getVariableWrite().getRhs() = n.asExpr()
|
||||
or
|
||||
result.(Flow::ExprPostUpdateNode).getExpr() = n.(PostUpdateNode).getPreUpdateNode().asExpr()
|
||||
or
|
||||
result.(Flow::ParameterNode).getParameter().getParameterNode() = n
|
||||
or
|
||||
result.(Flow::ThisParameterNode).getCallable() = n.(LambdaSelfParameterNode).getCallable()
|
||||
}
|
||||
|
||||
predicate storeStep(Node node1, Content::CapturedVariableContent c, Node node2) {
|
||||
Flow::storeStep(asClosureNode(node1), c.getVariable(), asClosureNode(node2))
|
||||
}
|
||||
|
||||
predicate readStep(Node node1, Content::CapturedVariableContent c, Node node2) {
|
||||
Flow::readStep(asClosureNode(node1), c.getVariable(), asClosureNode(node2))
|
||||
}
|
||||
|
||||
predicate valueStep(Node node1, Node node2) {
|
||||
Flow::localFlowStep(asClosureNode(node1), asClosureNode(node2))
|
||||
}
|
||||
|
||||
class CapturedSsaDefinitionExt extends SsaImpl::DefinitionExt {
|
||||
@@ -331,6 +443,7 @@ private module Cached {
|
||||
p instanceof SplatParameter
|
||||
} or
|
||||
TSelfParameterNode(MethodBase m) or
|
||||
TLambdaSelfParameterNode(Callable c) { lambdaCreationExpr(_, _, c) } or
|
||||
TBlockParameterNode(MethodBase m) or
|
||||
TSynthHashSplatParameterNode(DataFlowCallable c) {
|
||||
isParameterNode(_, c, any(ParameterPosition p | p.isKeyword(_)))
|
||||
@@ -366,7 +479,8 @@ private module Cached {
|
||||
TSynthSplatArgumentNode(CfgNodes::ExprNodes::CallCfgNode c) {
|
||||
exists(Argument arg, ArgumentPosition pos | pos.isPositional(_) | arg.isArgumentOf(c, pos)) and
|
||||
not exists(Argument arg, ArgumentPosition pos | pos.isSplat(_) | arg.isArgumentOf(c, pos))
|
||||
}
|
||||
} or
|
||||
TCaptureNode(VariableCapture::Flow::SynthesizedCaptureNode cn)
|
||||
|
||||
class TSourceParameterNode =
|
||||
TNormalParameterNode or TBlockParameterNode or TSelfParameterNode or
|
||||
@@ -386,21 +500,23 @@ private module Cached {
|
||||
predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) {
|
||||
LocalFlow::localFlowStepCommon(nodeFrom, nodeTo)
|
||||
or
|
||||
LocalFlow::localSsaFlowStepUseUse(_, nodeFrom, nodeTo) and
|
||||
not FlowSummaryImpl::Private::Steps::prohibitsUseUseFlow(nodeFrom, _)
|
||||
or
|
||||
// Flow into phi node from read
|
||||
exists(CfgNodes::ExprCfgNode exprFrom |
|
||||
LocalFlow::localFlowSsaInputFromRead(exprFrom, _, nodeTo)
|
||||
exists(SsaImpl::DefinitionExt def |
|
||||
// captured variables are handled by the shared `VariableCapture` library
|
||||
not def instanceof VariableCapture::CapturedSsaDefinitionExt
|
||||
|
|
||||
exprFrom = nodeFrom.asExpr() and
|
||||
LocalFlow::localSsaFlowStep(def, nodeFrom, nodeTo)
|
||||
or
|
||||
LocalFlow::localSsaFlowStepUseUse(def, nodeFrom, nodeTo) and
|
||||
not FlowSummaryImpl::Private::Steps::prohibitsUseUseFlow(nodeFrom, _)
|
||||
or
|
||||
exprFrom = nodeFrom.(PostUpdateNode).getPreUpdateNode().asExpr()
|
||||
LocalFlow::localFlowSsaInputFromRead(def, nodeFrom, nodeTo) and
|
||||
not FlowSummaryImpl::Private::Steps::prohibitsUseUseFlow(nodeFrom, _)
|
||||
)
|
||||
or
|
||||
FlowSummaryImpl::Private::Steps::summaryLocalStep(nodeFrom.(FlowSummaryNode).getSummaryNode(),
|
||||
nodeTo.(FlowSummaryNode).getSummaryNode(), true)
|
||||
or
|
||||
VariableCapture::valueStep(nodeFrom, nodeTo)
|
||||
}
|
||||
|
||||
/** This is the local flow predicate that is exposed. */
|
||||
@@ -408,6 +524,8 @@ private module Cached {
|
||||
predicate localFlowStepImpl(Node nodeFrom, Node nodeTo) {
|
||||
LocalFlow::localFlowStepCommon(nodeFrom, nodeTo)
|
||||
or
|
||||
LocalFlow::localSsaFlowStep(_, nodeFrom, nodeTo)
|
||||
or
|
||||
LocalFlow::localSsaFlowStepUseUse(_, nodeFrom, nodeTo)
|
||||
or
|
||||
// Simple flow through library code is included in the exposed local
|
||||
@@ -422,13 +540,11 @@ private module Cached {
|
||||
predicate localFlowStepTypeTracker(Node nodeFrom, Node nodeTo) {
|
||||
LocalFlow::localFlowStepCommon(nodeFrom, nodeTo)
|
||||
or
|
||||
LocalFlow::localSsaFlowStep(_, nodeFrom, nodeTo)
|
||||
or
|
||||
LocalFlow::localSsaFlowStepUseUse(_, nodeFrom, nodeTo)
|
||||
or
|
||||
// Flow into phi node from read
|
||||
exists(CfgNodes::ExprCfgNode exprFrom |
|
||||
LocalFlow::localFlowSsaInputFromRead(exprFrom, _, nodeTo) and
|
||||
exprFrom = [nodeFrom.asExpr(), nodeFrom.(PostUpdateNode).getPreUpdateNode().asExpr()]
|
||||
)
|
||||
LocalFlow::localFlowSsaInputFromRead(_, nodeFrom, nodeTo)
|
||||
or
|
||||
VariableCapture::flowInsensitiveStep(nodeFrom, nodeTo)
|
||||
}
|
||||
@@ -527,6 +643,7 @@ private module Cached {
|
||||
name = [input, output].regexpFind("(?<=(^|\\.)Field\\[)[^\\]]+(?=\\])", _, _).trim()
|
||||
)
|
||||
} or
|
||||
TCapturedVariableContent(VariableCapture::CapturedVariable v) or
|
||||
// Only used by type-tracking
|
||||
TAttributeName(string name) { name = any(SetterMethodCall c).getTargetName() }
|
||||
|
||||
@@ -549,7 +666,8 @@ private module Cached {
|
||||
TUnknownElementContentApprox() or
|
||||
TKnownIntegerElementContentApprox() or
|
||||
TKnownElementContentApprox(string approx) { approx = approxKnownElementIndex(_) } or
|
||||
TNonElementContentApprox(Content c) { not c instanceof Content::ElementContent }
|
||||
TNonElementContentApprox(Content c) { not c instanceof Content::ElementContent } or
|
||||
TCapturedVariableContentApprox(VariableCapture::CapturedVariable v)
|
||||
}
|
||||
|
||||
class TElementContent = TKnownElementContent or TUnknownElementContent;
|
||||
@@ -577,6 +695,10 @@ predicate nodeIsHidden(Node n) {
|
||||
n instanceof SynthSplatArgParameterNode
|
||||
or
|
||||
n instanceof SynthSplatParameterElementNode
|
||||
or
|
||||
n instanceof LambdaSelfParameterNode
|
||||
or
|
||||
n instanceof CaptureNode
|
||||
}
|
||||
|
||||
/** An SSA definition, viewed as a node in a data flow graph. */
|
||||
@@ -624,7 +746,7 @@ class CapturedVariableNode extends NodeImpl, TCapturedVariableNode {
|
||||
/** Gets the captured variable represented by this node. */
|
||||
VariableCapture::CapturedVariable getVariable() { result = variable }
|
||||
|
||||
override CfgScope getCfgScope() { result = variable.getCfgScope() }
|
||||
override CfgScope getCfgScope() { result = variable.getCallable() }
|
||||
|
||||
override Location getLocationImpl() { result = variable.getLocation() }
|
||||
|
||||
@@ -653,6 +775,24 @@ class ReturningStatementNode extends NodeImpl, TReturningNode {
|
||||
override string toStringImpl() { result = n.toString() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A synthesized data flow node representing a closure object that tracks
|
||||
* captured variables.
|
||||
*/
|
||||
class CaptureNode extends NodeImpl, TCaptureNode {
|
||||
private VariableCapture::Flow::SynthesizedCaptureNode cn;
|
||||
|
||||
CaptureNode() { this = TCaptureNode(cn) }
|
||||
|
||||
VariableCapture::Flow::SynthesizedCaptureNode getSynthesizedCaptureNode() { result = cn }
|
||||
|
||||
override CfgScope getCfgScope() { result = cn.getEnclosingCallable() }
|
||||
|
||||
override Location getLocationImpl() { result = cn.getLocation() }
|
||||
|
||||
override string toStringImpl() { result = cn.toString() }
|
||||
}
|
||||
|
||||
private module ParameterNodes {
|
||||
abstract class ParameterNodeImpl extends NodeImpl {
|
||||
abstract Parameter getParameter();
|
||||
@@ -722,6 +862,11 @@ private module ParameterNodes {
|
||||
|
||||
final MethodBase getMethod() { result = method }
|
||||
|
||||
/** Gets the corresponding SSA `self` definition, if any. */
|
||||
Ssa::SelfDefinition getSelfDefinition() {
|
||||
result.getSourceVariable().getDeclaringScope() = method
|
||||
}
|
||||
|
||||
/** Gets the underlying `self` variable. */
|
||||
final SelfVariable getSelfVariable() { result.getDeclaringScope() = method }
|
||||
|
||||
@@ -735,14 +880,43 @@ private module ParameterNodes {
|
||||
|
||||
override Location getLocationImpl() { result = method.getLocation() }
|
||||
|
||||
override string toStringImpl() { result = "self in " + method.toString() }
|
||||
override string toStringImpl() { result = "self in " + method }
|
||||
}
|
||||
|
||||
/**
|
||||
* The value of a lambda itself at function entry, viewed as a node in a data
|
||||
* flow graph.
|
||||
*
|
||||
* This is used for tracking flow through captured variables, and we use a
|
||||
* separate node and parameter/argument positions in order to distinguish
|
||||
* "lambda self" from "normal self", as lambdas may also access outer `self`
|
||||
* variables (through variable capture).
|
||||
*/
|
||||
class LambdaSelfParameterNode extends ParameterNodeImpl, TLambdaSelfParameterNode {
|
||||
private Callable callable;
|
||||
|
||||
LambdaSelfParameterNode() { this = TLambdaSelfParameterNode(callable) }
|
||||
|
||||
final Callable getCallable() { result = callable }
|
||||
|
||||
override Parameter getParameter() { none() }
|
||||
|
||||
override predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) {
|
||||
callable = c.asCallable() and pos.isLambdaSelf()
|
||||
}
|
||||
|
||||
override CfgScope getCfgScope() { result = callable }
|
||||
|
||||
override Location getLocationImpl() { result = callable.getLocation() }
|
||||
|
||||
override string toStringImpl() { result = "lambda self in " + callable }
|
||||
}
|
||||
|
||||
/**
|
||||
* The value of a block parameter at function entry, viewed as a node in a data
|
||||
* flow graph.
|
||||
*/
|
||||
class BlockParameterNode extends ParameterNodeImpl, TBlockParameterNode {
|
||||
class BlockParameterNode extends ParameterNodeImpl, ArgumentNode, TBlockParameterNode {
|
||||
private MethodBase method;
|
||||
|
||||
BlockParameterNode() { this = TBlockParameterNode(method) }
|
||||
@@ -757,6 +931,20 @@ private module ParameterNodes {
|
||||
c.asCallable() = method and pos.isBlock()
|
||||
}
|
||||
|
||||
CfgNodes::ExprNodes::CallCfgNode getAYieldCall() {
|
||||
this.getMethod() = result.getExpr().(YieldCall).getEnclosingMethod()
|
||||
}
|
||||
|
||||
// needed for variable capture flow
|
||||
override predicate sourceArgumentOf(CfgNodes::ExprNodes::CallCfgNode call, ArgumentPosition pos) {
|
||||
call = this.getAYieldCall() and
|
||||
pos.isLambdaSelf()
|
||||
}
|
||||
|
||||
override predicate argumentOf(DataFlowCall call, ArgumentPosition pos) {
|
||||
this.sourceArgumentOf(call.asCall(), pos)
|
||||
}
|
||||
|
||||
override CfgScope getCfgScope() { result = method }
|
||||
|
||||
override Location getLocationImpl() {
|
||||
@@ -1027,36 +1215,6 @@ private module ArgumentNodes {
|
||||
}
|
||||
}
|
||||
|
||||
/** A data-flow node that represents the `self` argument of a call. */
|
||||
class SelfArgumentNode extends ExplicitArgumentNode {
|
||||
SelfArgumentNode() { arg.isArgumentOf(_, any(ArgumentPosition pos | pos.isSelf())) }
|
||||
}
|
||||
|
||||
/** A data-flow node that represents a block argument. */
|
||||
class BlockArgumentNode extends ArgumentNode {
|
||||
BlockArgumentNode() {
|
||||
this.asExpr().getExpr() instanceof BlockArgument or
|
||||
exists(CfgNodes::ExprNodes::CallCfgNode c | c.getBlock() = this.asExpr())
|
||||
}
|
||||
|
||||
override predicate argumentOf(DataFlowCall call, ArgumentPosition pos) {
|
||||
this.sourceArgumentOf(call.asCall(), pos)
|
||||
}
|
||||
|
||||
override predicate sourceArgumentOf(CfgNodes::ExprNodes::CallCfgNode call, ArgumentPosition pos) {
|
||||
pos.isBlock() and
|
||||
(
|
||||
this.asExpr() = call.getBlock()
|
||||
or
|
||||
exists(CfgNodes::ExprCfgNode arg |
|
||||
arg = call.getAnArgument() and
|
||||
this.asExpr() = arg and
|
||||
arg.getExpr() instanceof BlockArgument
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class SummaryArgumentNode extends FlowSummaryNode, ArgumentNode {
|
||||
private DataFlowCall call_;
|
||||
private ArgumentPosition pos_;
|
||||
@@ -1335,7 +1493,7 @@ private module OutNodes {
|
||||
|
||||
import OutNodes
|
||||
|
||||
predicate jumpStepTypeTracker(Node pred, Node succ) {
|
||||
predicate jumpStep(Node pred, Node succ) {
|
||||
succ.asExpr().getExpr().(ConstantReadAccess).getValue() = pred.asExpr().getExpr()
|
||||
or
|
||||
FlowSummaryImpl::Private::Steps::summaryJumpStep(pred.(FlowSummaryNode).getSummaryNode(),
|
||||
@@ -1344,16 +1502,6 @@ predicate jumpStepTypeTracker(Node pred, Node succ) {
|
||||
any(AdditionalJumpStep s).step(pred, succ)
|
||||
}
|
||||
|
||||
predicate jumpStep(Node pred, Node succ) {
|
||||
jumpStepTypeTracker(pred, succ)
|
||||
or
|
||||
SsaImpl::captureFlowIn(_, pred.(SsaDefinitionExtNode).getDefinitionExt(),
|
||||
succ.(SsaDefinitionExtNode).getDefinitionExt())
|
||||
or
|
||||
SsaImpl::captureFlowOut(_, pred.(SsaDefinitionExtNode).getDefinitionExt(),
|
||||
succ.(SsaDefinitionExtNode).getDefinitionExt())
|
||||
}
|
||||
|
||||
private ContentSet getKeywordContent(string name) {
|
||||
exists(ConstantValue::ConstantSymbolValue key |
|
||||
result.isSingleton(TKnownElementContent(key)) and
|
||||
@@ -1439,6 +1587,9 @@ predicate storeStep(Node node1, ContentSet c, Node node2) {
|
||||
)
|
||||
or
|
||||
storeStepCommon(node1, c, node2)
|
||||
or
|
||||
VariableCapture::storeStep(node1, any(Content::CapturedVariableContent v | c.isSingleton(v)),
|
||||
node2)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1487,6 +1638,8 @@ predicate readStep(Node node1, ContentSet c, Node node2) {
|
||||
c = getPositionalContent(e.getReadPosition())
|
||||
)
|
||||
or
|
||||
VariableCapture::readStep(node1, any(Content::CapturedVariableContent v | c.isSingleton(v)), node2)
|
||||
or
|
||||
readStepCommon(node1, c, node2)
|
||||
}
|
||||
|
||||
@@ -1521,27 +1674,60 @@ predicate expectsContent(Node n, ContentSet c) {
|
||||
}
|
||||
|
||||
private newtype TDataFlowType =
|
||||
TTodoDataFlowType() or
|
||||
TTodoDataFlowType2() // Add a dummy value to prevent bad functionality-induced joins arising from a type of size 1.
|
||||
TLambdaDataFlowType(Callable c) { c = any(LambdaSelfParameterNode n).getCallable() } or
|
||||
TUnknownDataFlowType()
|
||||
|
||||
class DataFlowType extends TDataFlowType {
|
||||
string toString() { result = "" }
|
||||
}
|
||||
|
||||
predicate typeStrongerThan(DataFlowType t1, DataFlowType t2) { none() }
|
||||
predicate typeStrongerThan(DataFlowType t1, DataFlowType t2) {
|
||||
t1 = TLambdaDataFlowType(_) and
|
||||
t2 = TUnknownDataFlowType()
|
||||
}
|
||||
|
||||
private predicate mustHaveLambdaType(CfgNodes::ExprCfgNode e, Callable c) {
|
||||
exists(VariableCapture::ClosureExpr ce | ce.hasBody(c) |
|
||||
e = ce or
|
||||
ce.hasAliasedAccess(e)
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the type of `n` used for type pruning. */
|
||||
DataFlowType getNodeType(Node n) { result = TTodoDataFlowType() and exists(n) }
|
||||
DataFlowType getNodeType(Node n) {
|
||||
result = TLambdaDataFlowType(n.(LambdaSelfParameterNode).getCallable())
|
||||
or
|
||||
exists(Callable c |
|
||||
mustHaveLambdaType(n.asExpr(), c) and
|
||||
result = TLambdaDataFlowType(c)
|
||||
)
|
||||
or
|
||||
not n instanceof LambdaSelfParameterNode and
|
||||
not mustHaveLambdaType(n.asExpr(), _) and
|
||||
result = TUnknownDataFlowType()
|
||||
}
|
||||
|
||||
/** Gets a string representation of a `DataFlowType`. */
|
||||
string ppReprType(DataFlowType t) { none() }
|
||||
|
||||
pragma[inline]
|
||||
private predicate compatibleTypesNonSymRefl(DataFlowType t1, DataFlowType t2) {
|
||||
t1 = TLambdaDataFlowType(_) and
|
||||
t2 = TUnknownDataFlowType()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `t1` and `t2` are compatible, that is, whether data can flow from
|
||||
* a node of type `t1` to a node of type `t2`.
|
||||
*/
|
||||
pragma[inline]
|
||||
predicate compatibleTypes(DataFlowType t1, DataFlowType t2) { any() }
|
||||
predicate compatibleTypes(DataFlowType t1, DataFlowType t2) {
|
||||
t1 = t2
|
||||
or
|
||||
compatibleTypesNonSymRefl(t1, t2)
|
||||
or
|
||||
compatibleTypesNonSymRefl(t2, t1)
|
||||
}
|
||||
|
||||
abstract class PostUpdateNodeImpl extends Node {
|
||||
/** Gets the node before the state update. */
|
||||
@@ -1583,6 +1769,17 @@ private module PostUpdateNodes {
|
||||
|
||||
override Node getPreUpdateNode() { result = pre }
|
||||
}
|
||||
|
||||
private class CapturePostUpdateNode extends PostUpdateNodeImpl, CaptureNode {
|
||||
private CaptureNode pre;
|
||||
|
||||
CapturePostUpdateNode() {
|
||||
VariableCapture::Flow::capturePostUpdateNode(this.getSynthesizedCaptureNode(),
|
||||
pre.getSynthesizedCaptureNode())
|
||||
}
|
||||
|
||||
override Node getPreUpdateNode() { result = pre }
|
||||
}
|
||||
}
|
||||
|
||||
private import PostUpdateNodes
|
||||
@@ -1623,22 +1820,27 @@ newtype LambdaCallKind =
|
||||
TLambdaCallKind()
|
||||
|
||||
/** Holds if `creation` is an expression that creates a lambda of kind `kind` for `c`. */
|
||||
predicate lambdaCreation(Node creation, LambdaCallKind kind, DataFlowCallable c) {
|
||||
private predicate lambdaCreationExpr(CfgNodes::ExprCfgNode creation, LambdaCallKind kind, Callable c) {
|
||||
kind = TYieldCallKind() and
|
||||
creation.asExpr().getExpr() = c.asCallable().(Block)
|
||||
creation.getExpr() = c.(Block)
|
||||
or
|
||||
kind = TLambdaCallKind() and
|
||||
(
|
||||
creation.asExpr().getExpr() = c.asCallable().(Lambda)
|
||||
creation.getExpr() = c.(Lambda)
|
||||
or
|
||||
creation.asExpr() =
|
||||
creation =
|
||||
any(CfgNodes::ExprNodes::MethodCallCfgNode mc |
|
||||
c.asCallable() = mc.getBlock().getExpr() and
|
||||
c = mc.getBlock().getExpr() and
|
||||
isProcCreationCall(mc.getExpr())
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `creation` is an expression that creates a lambda of kind `kind` for `c`. */
|
||||
predicate lambdaCreation(Node creation, LambdaCallKind kind, DataFlowCallable c) {
|
||||
lambdaCreationExpr(creation.asExpr(), kind, c.asCallable())
|
||||
}
|
||||
|
||||
/** Holds if `call` is a call to `lambda`, `proc`, or `Proc.new` */
|
||||
pragma[nomagic]
|
||||
private predicate isProcCreationCall(MethodCall call) {
|
||||
@@ -1648,20 +1850,24 @@ private predicate isProcCreationCall(MethodCall call) {
|
||||
call.getReceiver().(ConstantReadAccess).getAQualifiedName() = "Proc"
|
||||
}
|
||||
|
||||
/** Holds if `mc` is a call to `receiver.call`. */
|
||||
private predicate lambdaCallExpr(
|
||||
CfgNodes::ExprNodes::MethodCallCfgNode mc, CfgNodes::ExprCfgNode receiver
|
||||
) {
|
||||
receiver = mc.getReceiver() and
|
||||
mc.getExpr().getMethodName() = "call"
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `call` is a from-source lambda call of kind `kind` where `receiver`
|
||||
* is the lambda expression.
|
||||
*/
|
||||
predicate lambdaSourceCall(CfgNodes::ExprNodes::CallCfgNode call, LambdaCallKind kind, Node receiver) {
|
||||
kind = TYieldCallKind() and
|
||||
receiver.(BlockParameterNode).getMethod() = call.getExpr().(YieldCall).getEnclosingMethod()
|
||||
call = receiver.(BlockParameterNode).getAYieldCall()
|
||||
or
|
||||
kind = TLambdaCallKind() and
|
||||
call =
|
||||
any(CfgNodes::ExprNodes::MethodCallCfgNode mc |
|
||||
receiver.asExpr() = mc.getReceiver() and
|
||||
mc.getExpr().getMethodName() = "call"
|
||||
)
|
||||
lambdaCallExpr(call, receiver.asExpr())
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1687,8 +1893,11 @@ predicate additionalLambdaFlowStep(Node nodeFrom, Node nodeTo, boolean preserves
|
||||
* One example would be to allow flow like `p.foo = p.bar;`, which is disallowed
|
||||
* by default as a heuristic.
|
||||
*/
|
||||
predicate allowParameterReturnInSelf(ParameterNode p) {
|
||||
predicate allowParameterReturnInSelf(ParameterNodeImpl p) {
|
||||
FlowSummaryImpl::Private::summaryAllowParameterReturnInSelf(p)
|
||||
or
|
||||
VariableCapture::Flow::heuristicAllowInstanceParameterReturnInSelf(p.(SelfParameterNode)
|
||||
.getCallable())
|
||||
}
|
||||
|
||||
/** An approximated `Content`. */
|
||||
@@ -1765,4 +1974,4 @@ class AdditionalJumpStep extends Unit {
|
||||
*
|
||||
* Argument `arg` is part of a path from a source to a sink, and `p` is the target parameter.
|
||||
*/
|
||||
int getAdditionalFlowIntoCallNodeTerm(ArgumentNode arg, ParameterNode p) { none() }
|
||||
int getAdditionalFlowIntoCallNodeTerm(ArgumentNode arg, ParameterNodeImpl p) { none() }
|
||||
|
||||
@@ -568,6 +568,18 @@ module Content {
|
||||
|
||||
/** Gets `AttributeNameContent` of the given name. */
|
||||
AttributeNameContent getAttributeName(string name) { result.getName() = name }
|
||||
|
||||
/** A captured variable. */
|
||||
class CapturedVariableContent extends Content, TCapturedVariableContent {
|
||||
private LocalVariable v;
|
||||
|
||||
CapturedVariableContent() { this = TCapturedVariableContent(v) }
|
||||
|
||||
/** Gets the captured variable. */
|
||||
LocalVariable getVariable() { result = v }
|
||||
|
||||
override string toString() { result = "captured " + v }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -765,14 +777,14 @@ module BarrierGuard<guardChecksSig/3 guardChecks> {
|
||||
* This is restricted to calls where the variable is captured inside a
|
||||
* block.
|
||||
*/
|
||||
private Ssa::Definition getAMaybeGuardedCapturedDef() {
|
||||
private Ssa::CapturedEntryDefinition getAMaybeGuardedCapturedDef() {
|
||||
exists(
|
||||
CfgNodes::ExprCfgNode g, boolean branch, CfgNodes::ExprCfgNode testedNode,
|
||||
Ssa::Definition def, CfgNodes::ExprNodes::CallCfgNode call
|
||||
|
|
||||
def.getARead() = testedNode and
|
||||
guardChecks(g, testedNode, branch) and
|
||||
SsaImpl::captureFlowIn(call, def, result) and
|
||||
def.getSourceVariable() = result.getSourceVariable() and
|
||||
guardControlsBlock(g, call.getBasicBlock(), branch) and
|
||||
result.getBasicBlock().getScope() = call.getExpr().(MethodCall).getBlock()
|
||||
)
|
||||
@@ -830,14 +842,14 @@ abstract deprecated class BarrierGuard extends CfgNodes::ExprCfgNode {
|
||||
* This is restricted to calls where the variable is captured inside a
|
||||
* block.
|
||||
*/
|
||||
private Ssa::Definition getAMaybeGuardedCapturedDef() {
|
||||
private Ssa::CapturedEntryDefinition getAMaybeGuardedCapturedDef() {
|
||||
exists(
|
||||
boolean branch, CfgNodes::ExprCfgNode testedNode, Ssa::Definition def,
|
||||
CfgNodes::ExprNodes::CallCfgNode call
|
||||
|
|
||||
def.getARead() = testedNode and
|
||||
this.checks(testedNode, branch) and
|
||||
SsaImpl::captureFlowIn(call, def, result) and
|
||||
def.getSourceVariable() = result.getSourceVariable() and
|
||||
this.controlsBlock(call.getBasicBlock(), branch) and
|
||||
result.getBasicBlock().getScope() = call.getExpr().(MethodCall).getBlock()
|
||||
)
|
||||
@@ -1208,7 +1220,10 @@ class LhsExprNode extends ExprNode {
|
||||
LhsExpr asLhsExprAstNode() { result = lhsExprCfgNode.getExpr() }
|
||||
|
||||
/** Gets a variable used in (or introduced by) this LHS. */
|
||||
Variable getAVariable() { result = lhsExprCfgNode.getAVariable() }
|
||||
deprecated Variable getAVariable() { result = lhsExprCfgNode.getAVariable() }
|
||||
|
||||
/** Gets the variable used in (or introduced by) this LHS. */
|
||||
Variable getVariable() { result = lhsExprCfgNode.getVariable() }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,7 +24,7 @@ class NeutralCallableBase = string;
|
||||
DataFlowCallable inject(SummarizedCallable c) { result.asLibraryCallable() = c }
|
||||
|
||||
/** Gets the parameter position representing a callback itself, if any. */
|
||||
ArgumentPosition callbackSelfParameterPosition() { none() } // disables implicit summary flow to `self` for callbacks
|
||||
ArgumentPosition callbackSelfParameterPosition() { result.isLambdaSelf() }
|
||||
|
||||
/** Gets the synthesized data-flow call for `receiver`. */
|
||||
SummaryCall summaryDataFlowCall(SummaryNode receiver) { receiver = result.getReceiver() }
|
||||
@@ -215,6 +215,9 @@ string getParameterPosition(ParameterPosition pos) {
|
||||
pos.isSelf() and
|
||||
result = "self"
|
||||
or
|
||||
pos.isLambdaSelf() and
|
||||
result = "lambda-self"
|
||||
or
|
||||
pos.isBlock() and
|
||||
result = "block"
|
||||
or
|
||||
@@ -232,6 +235,8 @@ string getParameterPosition(ParameterPosition pos) {
|
||||
string getArgumentPosition(ArgumentPosition pos) {
|
||||
pos.isSelf() and result = "self"
|
||||
or
|
||||
pos.isLambdaSelf() and result = "lambda-self"
|
||||
or
|
||||
pos.isBlock() and result = "block"
|
||||
or
|
||||
exists(int i |
|
||||
@@ -372,6 +377,9 @@ ArgumentPosition parseParamBody(string s) {
|
||||
s = "self" and
|
||||
result.isSelf()
|
||||
or
|
||||
s = "lambda-self" and
|
||||
result.isLambdaSelf()
|
||||
or
|
||||
s = "block" and
|
||||
result.isBlock()
|
||||
or
|
||||
@@ -402,6 +410,9 @@ ParameterPosition parseArgBody(string s) {
|
||||
s = "self" and
|
||||
result.isSelf()
|
||||
or
|
||||
s = "lambda-self" and
|
||||
result.isLambdaSelf()
|
||||
or
|
||||
s = "block" and
|
||||
result.isBlock()
|
||||
or
|
||||
|
||||
@@ -363,97 +363,8 @@ private module Cached {
|
||||
)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private predicate defReachesCallReadInOuterScope(
|
||||
Definition def, CallCfgNode call, LocalVariable v, Cfg::CfgScope scope
|
||||
) {
|
||||
exists(Cfg::BasicBlock bb, int i |
|
||||
Impl::ssaDefReachesRead(v, def, bb, i) and
|
||||
capturedCallRead(call, bb, i, v) and
|
||||
scope.getOuterCfgScope() = bb.getScope()
|
||||
)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private predicate hasCapturedEntryWrite(Definition entry, LocalVariable v, Cfg::CfgScope scope) {
|
||||
exists(Cfg::BasicBlock bb, int i |
|
||||
capturedEntryWrite(bb, i, v) and
|
||||
entry.definesAt(v, bb, i) and
|
||||
bb.getScope().getOuterCfgScope*() = scope
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there is flow for a captured variable from the enclosing scope into a block.
|
||||
* ```rb
|
||||
* foo = 0
|
||||
* bar {
|
||||
* puts foo
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
cached
|
||||
predicate captureFlowIn(CallCfgNode call, Definition def, Definition entry) {
|
||||
exists(LocalVariable v, Cfg::CfgScope scope |
|
||||
defReachesCallReadInOuterScope(def, call, v, scope) and
|
||||
hasCapturedEntryWrite(entry, v, scope)
|
||||
|
|
||||
// If the read happens inside a block, we restrict to the call that
|
||||
// contains the block
|
||||
not scope instanceof Block
|
||||
or
|
||||
scope = call.getExpr().(MethodCall).getBlock()
|
||||
)
|
||||
}
|
||||
|
||||
private import codeql.ruby.dataflow.SSA
|
||||
|
||||
pragma[noinline]
|
||||
private predicate defReachesExitReadInInnerScope(
|
||||
Definition def, LocalVariable v, Cfg::CfgScope scope
|
||||
) {
|
||||
exists(Cfg::BasicBlock bb, int i |
|
||||
Impl::ssaDefReachesRead(v, def, bb, i) and
|
||||
capturedExitRead(bb, i, v) and
|
||||
scope = bb.getScope().getOuterCfgScope*()
|
||||
)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private predicate hasCapturedExitRead(
|
||||
Definition exit, CallCfgNode call, LocalVariable v, Cfg::CfgScope scope
|
||||
) {
|
||||
exists(Cfg::BasicBlock bb, int i |
|
||||
capturedCallWrite(call, bb, i, v) and
|
||||
exit.definesAt(v, bb, i) and
|
||||
bb.getScope() = scope.getOuterCfgScope()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there is outgoing flow for a captured variable that is updated in a block.
|
||||
* ```rb
|
||||
* foo = 0
|
||||
* bar {
|
||||
* foo += 10
|
||||
* }
|
||||
* puts foo
|
||||
* ```
|
||||
*/
|
||||
cached
|
||||
predicate captureFlowOut(CallCfgNode call, Definition def, Definition exit) {
|
||||
exists(LocalVariable v, Cfg::CfgScope scope |
|
||||
defReachesExitReadInInnerScope(def, v, scope) and
|
||||
hasCapturedExitRead(exit, call, v, _)
|
||||
|
|
||||
// If the read happens inside a block, we restrict to the call that
|
||||
// contains the block
|
||||
not scope instanceof Block
|
||||
or
|
||||
scope = call.getExpr().(MethodCall).getBlock()
|
||||
)
|
||||
}
|
||||
|
||||
cached
|
||||
Definition phiHasInputFromBlock(PhiNode phi, Cfg::BasicBlock bb) {
|
||||
Impl::phiHasInputFromBlock(phi, result, bb)
|
||||
@@ -570,6 +481,8 @@ import Cached
|
||||
* Only intended for internal use.
|
||||
*/
|
||||
class DefinitionExt extends Impl::DefinitionExt {
|
||||
VariableReadAccessCfgNode getARead() { result = getARead(this) }
|
||||
|
||||
override string toString() { result = this.(Ssa::Definition).toString() }
|
||||
|
||||
/** Gets the location of this definition. */
|
||||
|
||||
@@ -273,39 +273,19 @@ module Sinatra {
|
||||
filter.getApp() = route.getApp() and
|
||||
// the filter applies to all routes
|
||||
not filter.hasPattern() and
|
||||
selfPostUpdate(pred, filter.getApp(), filter.getBody().asExpr().getExpr()) and
|
||||
blockCapturedSelfParameterNode(succ, route.getBody().asExpr().getExpr())
|
||||
blockPostUpdate(pred, filter.getBody()) and
|
||||
blockSelfParameterNode(succ, route.getBody().asExpr().getExpr())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `n` is a post-update node for the `self` parameter of `app` in block `b`.
|
||||
*
|
||||
* In this example, `n` is the post-update node for `@foo = 1`.
|
||||
* ```rb
|
||||
* class MyApp < Sinatra::Base
|
||||
* before do
|
||||
* @foo = 1
|
||||
* end
|
||||
* end
|
||||
* ```
|
||||
*/
|
||||
private predicate selfPostUpdate(DataFlow::PostUpdateNode n, App app, Block b) {
|
||||
n.getPreUpdateNode().asExpr().getExpr() =
|
||||
any(SelfVariableAccess self |
|
||||
pragma[only_bind_into](b) = self.getEnclosingCallable() and
|
||||
self.getVariable().getDeclaringScope() = app.getADeclaration()
|
||||
)
|
||||
/** Holds if `n` is a post-update node for the block `b`. */
|
||||
private predicate blockPostUpdate(DataFlow::PostUpdateNode n, DataFlow::BlockNode b) {
|
||||
n.getPreUpdateNode() = b
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `n` is a node representing the `self` parameter captured by block `b`.
|
||||
*/
|
||||
private predicate blockCapturedSelfParameterNode(DataFlow::Node n, Block b) {
|
||||
exists(Ssa::CapturedSelfDefinition d |
|
||||
n.(DataFlowPrivate::SsaDefinitionExtNode).getDefinitionExt() = d and
|
||||
d.getBasicBlock().getScope() = b
|
||||
)
|
||||
/** Holds if `n` is a `self` parameter belonging to block `b`. */
|
||||
private predicate blockSelfParameterNode(DataFlowPrivate::LambdaSelfParameterNode n, Block b) {
|
||||
n.getCallable() = b
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,7 +243,7 @@ predicate isExtraValidTokenArgumentInIdentifyingAccessPath(string name, string a
|
||||
or
|
||||
name = ["Argument", "Parameter"] and
|
||||
(
|
||||
argument = ["self", "block", "any", "any-named"]
|
||||
argument = ["self", "lambda-self", "block", "any", "any-named"]
|
||||
or
|
||||
argument.regexpMatch("\\w+:") // keyword argument
|
||||
)
|
||||
|
||||
@@ -6,7 +6,6 @@ private import codeql.ruby.dataflow.internal.DataFlowImplCommon as DataFlowImplC
|
||||
private import codeql.ruby.dataflow.internal.DataFlowPublic as DataFlowPublic
|
||||
private import codeql.ruby.dataflow.internal.DataFlowPrivate as DataFlowPrivate
|
||||
private import codeql.ruby.dataflow.internal.DataFlowDispatch as DataFlowDispatch
|
||||
private import codeql.ruby.dataflow.internal.SsaImpl as SsaImpl
|
||||
private import codeql.ruby.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl
|
||||
private import codeql.ruby.dataflow.internal.FlowSummaryImplSpecific as FlowSummaryImplSpecific
|
||||
private import codeql.ruby.dataflow.internal.AccessPathSyntax
|
||||
@@ -74,7 +73,7 @@ predicate simpleLocalFlowStep = DataFlowPrivate::localFlowStepTypeTracker/2;
|
||||
/**
|
||||
* Holds if data can flow from `node1` to `node2` in a way that discards call contexts.
|
||||
*/
|
||||
predicate jumpStep = DataFlowPrivate::jumpStepTypeTracker/2;
|
||||
predicate jumpStep = DataFlowPrivate::jumpStep/2;
|
||||
|
||||
/** Holds if there is direct flow from `param` to a return. */
|
||||
pragma[nomagic]
|
||||
@@ -180,6 +179,7 @@ private predicate argumentPositionMatch(
|
||||
) {
|
||||
exists(DataFlowDispatch::ArgumentPosition apos |
|
||||
arg.sourceArgumentOf(call, apos) and
|
||||
not apos.isLambdaSelf() and
|
||||
DataFlowDispatch::parameterMatch(ppos, apos)
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user