mirror of
https://github.com/github/codeql.git
synced 2026-04-27 01:35:13 +02:00
Merge pull request #111 from github/hvitved/dataflow
Initial data flow library
This commit is contained in:
7
ql/src/codeql_ruby/DataFlow.qll
Normal file
7
ql/src/codeql_ruby/DataFlow.qll
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Provides classes for performing local (intra-procedural) and
|
||||
* global (inter-procedural) data flow analyses.
|
||||
*/
|
||||
module DataFlow {
|
||||
import codeql_ruby.dataflow.internal.DataFlowImpl
|
||||
}
|
||||
@@ -176,6 +176,23 @@ abstract private class ExprChildMapping extends Expr {
|
||||
/** Provides classes for control-flow nodes that wrap AST expressions. */
|
||||
module ExprNodes {
|
||||
// TODO: Add more classes
|
||||
private class AssignmentExprChildMapping extends ExprChildMapping, Assignment {
|
||||
override predicate relevantChild(Expr e) { e = this.getAnOperand() }
|
||||
}
|
||||
|
||||
/** A control-flow node that wraps an `Assignment` AST expression. */
|
||||
class AssignmentCfgNode extends ExprCfgNode {
|
||||
override AssignmentExprChildMapping e;
|
||||
|
||||
final override Assignment getExpr() { result = ExprCfgNode.super.getExpr() }
|
||||
|
||||
/** Gets the LHS of this assignment. */
|
||||
final ExprCfgNode getLhs() { e.hasCfgChild(e.getLhs(), this, result) }
|
||||
|
||||
/** Gets the RHS of this assignment. */
|
||||
final ExprCfgNode getRhs() { e.hasCfgChild(e.getRhs(), this, result) }
|
||||
}
|
||||
|
||||
private class BinaryOperationExprChildMapping extends ExprChildMapping, BinaryOperation {
|
||||
override predicate relevantChild(Expr e) { e = this.getAnOperand() }
|
||||
}
|
||||
@@ -193,6 +210,40 @@ module ExprNodes {
|
||||
final ExprCfgNode getRightOperand() { e.hasCfgChild(e.getRightOperand(), this, result) }
|
||||
}
|
||||
|
||||
private class CallExprChildMapping extends ExprChildMapping, Call {
|
||||
override predicate relevantChild(Expr e) { e = [this.getAnArgument(), this.getReceiver()] }
|
||||
}
|
||||
|
||||
/** A control-flow node that wraps a `Call` AST expression. */
|
||||
class CallCfgNode extends ExprCfgNode {
|
||||
override CallExprChildMapping e;
|
||||
|
||||
final override Call getExpr() { result = ExprCfgNode.super.getExpr() }
|
||||
|
||||
/** Gets the `n`th argument of this call. */
|
||||
final ExprCfgNode getArgument(int n) { e.hasCfgChild(e.getArgument(n), this, result) }
|
||||
|
||||
/** Gets the receiver of this call. */
|
||||
final ExprCfgNode getReceiver() { e.hasCfgChild(e.getReceiver(), this, result) }
|
||||
}
|
||||
|
||||
private class ExprSequenceChildMapping extends ExprChildMapping, ExprSequence {
|
||||
override predicate relevantChild(Expr e) { e = this.getAnExpr() }
|
||||
}
|
||||
|
||||
/** A control-flow node that wraps an `ExprSequence` AST expression. */
|
||||
class ExprSequenceCfgNode extends ExprCfgNode {
|
||||
override ExprSequenceChildMapping e;
|
||||
|
||||
final override ExprSequence getExpr() { result = ExprCfgNode.super.getExpr() }
|
||||
|
||||
/** Gets the last expression in this sequence, if any. */
|
||||
final ExprCfgNode getLastExpr() { e.hasCfgChild(e.getLastExpr(), this, result) }
|
||||
|
||||
/** Gets the 'n'th expression of this expression sequence. */
|
||||
final ExprCfgNode getExpr(int n) { e.hasCfgChild(e.getExpr(n), this, result) }
|
||||
}
|
||||
|
||||
/** A control-flow node that wraps a `VariableReadAccess` AST expression. */
|
||||
class VariableReadAccessCfgNode extends ExprCfgNode {
|
||||
override VariableReadAccess e;
|
||||
|
||||
51
ql/src/codeql_ruby/dataflow/internal/DataFlowDispatch.qll
Normal file
51
ql/src/codeql_ruby/dataflow/internal/DataFlowDispatch.qll
Normal file
@@ -0,0 +1,51 @@
|
||||
private import ruby
|
||||
private import codeql_ruby.CFG
|
||||
private import DataFlowPrivate
|
||||
|
||||
newtype TReturnKind = TNormalReturnKind()
|
||||
|
||||
/**
|
||||
* Gets a node that can read the value returned from `call` with return kind
|
||||
* `kind`.
|
||||
*/
|
||||
OutNode getAnOutNode(DataFlowCall call, ReturnKind kind) { call = result.getCall(kind) }
|
||||
|
||||
/**
|
||||
* A return kind. A return kind describes how a value can be returned
|
||||
* from a callable.
|
||||
*/
|
||||
abstract class ReturnKind extends TReturnKind {
|
||||
/** Gets a textual representation of this position. */
|
||||
abstract string toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* A value returned from a callable using a `return` statement or an expression
|
||||
* body, that is, a "normal" return.
|
||||
*/
|
||||
class NormalReturnKind extends ReturnKind, TNormalReturnKind {
|
||||
override string toString() { result = "return" }
|
||||
}
|
||||
|
||||
class DataFlowCallable = CfgScope;
|
||||
|
||||
class DataFlowCall extends CfgNodes::ExprNodes::CallCfgNode {
|
||||
DataFlowCallable getEnclosingCallable() { result = this.getScope() }
|
||||
}
|
||||
|
||||
/** Gets a viable run-time target for the call `call`. */
|
||||
DataFlowCallable viableCallable(DataFlowCall call) { none() }
|
||||
|
||||
/**
|
||||
* Holds if the set of viable implementations that can be called by `call`
|
||||
* might be improved by knowing the call context. This is the case if the
|
||||
* call is a delegate call, or if the qualifier accesses a parameter of
|
||||
* the enclosing callable `c` (including the implicit `this` parameter).
|
||||
*/
|
||||
predicate mayBenefitFromCallContext(DataFlowCall call, Callable c) { none() }
|
||||
|
||||
/**
|
||||
* Gets a viable dispatch target of `call` in the context `ctx`. This is
|
||||
* restricted to those `call`s for which a context might make a difference.
|
||||
*/
|
||||
DataFlowCallable viableImplInCallContext(DataFlowCall call, DataFlowCall ctx) { none() }
|
||||
4151
ql/src/codeql_ruby/dataflow/internal/DataFlowImpl.qll
Normal file
4151
ql/src/codeql_ruby/dataflow/internal/DataFlowImpl.qll
Normal file
File diff suppressed because it is too large
Load Diff
841
ql/src/codeql_ruby/dataflow/internal/DataFlowImplCommon.qll
Normal file
841
ql/src/codeql_ruby/dataflow/internal/DataFlowImplCommon.qll
Normal file
@@ -0,0 +1,841 @@
|
||||
private import DataFlowImplSpecific::Private
|
||||
private import DataFlowImplSpecific::Public
|
||||
import Cached
|
||||
|
||||
/**
|
||||
* The cost limits for the `AccessPathFront` to `AccessPathApprox` expansion.
|
||||
*
|
||||
* `apLimit` bounds the acceptable fan-out, and `tupleLimit` bounds the
|
||||
* estimated per-`AccessPathFront` tuple cost. Access paths exceeding both of
|
||||
* these limits are represented with lower precision during pruning.
|
||||
*/
|
||||
predicate accessPathApproxCostLimits(int apLimit, int tupleLimit) {
|
||||
apLimit = 10 and
|
||||
tupleLimit = 10000
|
||||
}
|
||||
|
||||
/**
|
||||
* The cost limits for the `AccessPathApprox` to `AccessPath` expansion.
|
||||
*
|
||||
* `apLimit` bounds the acceptable fan-out, and `tupleLimit` bounds the
|
||||
* estimated per-`AccessPathApprox` tuple cost. Access paths exceeding both of
|
||||
* these limits are represented with lower precision.
|
||||
*/
|
||||
predicate accessPathCostLimits(int apLimit, int tupleLimit) {
|
||||
apLimit = 5 and
|
||||
tupleLimit = 1000
|
||||
}
|
||||
|
||||
cached
|
||||
private module Cached {
|
||||
/**
|
||||
* Holds if `p` is the `i`th parameter of a viable dispatch target of `call`.
|
||||
* The instance parameter is considered to have index `-1`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate viableParam(DataFlowCall call, int i, ParameterNode p) {
|
||||
p.isParameterOf(viableCallable(call), i)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `arg` is a possible argument to `p` in `call`, taking virtual
|
||||
* dispatch into account.
|
||||
*/
|
||||
cached
|
||||
predicate viableParamArg(DataFlowCall call, ParameterNode p, ArgumentNode arg) {
|
||||
exists(int i |
|
||||
viableParam(call, i, p) and
|
||||
arg.argumentOf(call, i) and
|
||||
compatibleTypes(getNodeType(arg), getNodeType(p))
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private ReturnPosition viableReturnPos(DataFlowCall call, ReturnKindExt kind) {
|
||||
viableCallable(call) = result.getCallable() and
|
||||
kind = result.getKind()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if a value at return position `pos` can be returned to `out` via `call`,
|
||||
* taking virtual dispatch into account.
|
||||
*/
|
||||
cached
|
||||
predicate viableReturnPosOut(DataFlowCall call, ReturnPosition pos, Node out) {
|
||||
exists(ReturnKindExt kind |
|
||||
pos = viableReturnPos(call, kind) and
|
||||
out = kind.getAnOutNode(call)
|
||||
)
|
||||
}
|
||||
|
||||
/** Provides predicates for calculating flow-through summaries. */
|
||||
private module FlowThrough {
|
||||
/**
|
||||
* The first flow-through approximation:
|
||||
*
|
||||
* - Input access paths are abstracted with a Boolean parameter
|
||||
* that indicates (non-)emptiness.
|
||||
*/
|
||||
private module Cand {
|
||||
/**
|
||||
* Holds if `p` can flow to `node` in the same callable using only
|
||||
* value-preserving steps.
|
||||
*
|
||||
* `read` indicates whether it is contents of `p` that can flow to `node`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate parameterValueFlowCand(ParameterNode p, Node node, boolean read) {
|
||||
p = node and
|
||||
read = false
|
||||
or
|
||||
// local flow
|
||||
exists(Node mid |
|
||||
parameterValueFlowCand(p, mid, read) and
|
||||
simpleLocalFlowStep(mid, node)
|
||||
)
|
||||
or
|
||||
// read
|
||||
exists(Node mid |
|
||||
parameterValueFlowCand(p, mid, false) and
|
||||
readStep(mid, _, node) and
|
||||
read = true
|
||||
)
|
||||
or
|
||||
// flow through: no prior read
|
||||
exists(ArgumentNode arg |
|
||||
parameterValueFlowArgCand(p, arg, false) and
|
||||
argumentValueFlowsThroughCand(arg, node, read)
|
||||
)
|
||||
or
|
||||
// flow through: no read inside method
|
||||
exists(ArgumentNode arg |
|
||||
parameterValueFlowArgCand(p, arg, read) and
|
||||
argumentValueFlowsThroughCand(arg, node, false)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate parameterValueFlowArgCand(ParameterNode p, ArgumentNode arg, boolean read) {
|
||||
parameterValueFlowCand(p, arg, read)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
predicate parameterValueFlowsToPreUpdateCand(ParameterNode p, PostUpdateNode n) {
|
||||
parameterValueFlowCand(p, n.getPreUpdateNode(), false)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `p` can flow to a return node of kind `kind` in the same
|
||||
* callable using only value-preserving steps, not taking call contexts
|
||||
* into account.
|
||||
*
|
||||
* `read` indicates whether it is contents of `p` that can flow to the return
|
||||
* node.
|
||||
*/
|
||||
predicate parameterValueFlowReturnCand(ParameterNode p, ReturnKind kind, boolean read) {
|
||||
exists(ReturnNode ret |
|
||||
parameterValueFlowCand(p, ret, read) and
|
||||
kind = ret.getKind()
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate argumentValueFlowsThroughCand0(
|
||||
DataFlowCall call, ArgumentNode arg, ReturnKind kind, boolean read
|
||||
) {
|
||||
exists(ParameterNode param | viableParamArg(call, param, arg) |
|
||||
parameterValueFlowReturnCand(param, kind, read)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `arg` flows to `out` through a call using only value-preserving steps,
|
||||
* not taking call contexts into account.
|
||||
*
|
||||
* `read` indicates whether it is contents of `arg` that can flow to `out`.
|
||||
*/
|
||||
predicate argumentValueFlowsThroughCand(ArgumentNode arg, Node out, boolean read) {
|
||||
exists(DataFlowCall call, ReturnKind kind |
|
||||
argumentValueFlowsThroughCand0(call, arg, kind, read) and
|
||||
out = getAnOutNode(call, kind)
|
||||
)
|
||||
}
|
||||
|
||||
predicate cand(ParameterNode p, Node n) {
|
||||
parameterValueFlowCand(p, n, _) and
|
||||
(
|
||||
parameterValueFlowReturnCand(p, _, _)
|
||||
or
|
||||
parameterValueFlowsToPreUpdateCand(p, _)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The final flow-through calculation:
|
||||
*
|
||||
* - Calculated flow is either value-preserving (`read = TReadStepTypesNone()`)
|
||||
* or summarized as a single read step with before and after types recorded
|
||||
* in the `ReadStepTypesOption` parameter.
|
||||
* - Types are checked using the `compatibleTypes()` relation.
|
||||
*/
|
||||
private module Final {
|
||||
/**
|
||||
* Holds if `p` can flow to `node` in the same callable using only
|
||||
* value-preserving steps and possibly a single read step, not taking
|
||||
* call contexts into account.
|
||||
*
|
||||
* If a read step was taken, then `read` captures the `Content`, the
|
||||
* container type, and the content type.
|
||||
*/
|
||||
predicate parameterValueFlow(ParameterNode p, Node node, ReadStepTypesOption read) {
|
||||
parameterValueFlow0(p, node, read) and
|
||||
if node instanceof CastingNode
|
||||
then
|
||||
// normal flow through
|
||||
read = TReadStepTypesNone() and
|
||||
compatibleTypes(getNodeType(p), getNodeType(node))
|
||||
or
|
||||
// getter
|
||||
compatibleTypes(read.getContentType(), getNodeType(node))
|
||||
else any()
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate parameterValueFlow0(ParameterNode p, Node node, ReadStepTypesOption read) {
|
||||
p = node and
|
||||
Cand::cand(p, _) and
|
||||
read = TReadStepTypesNone()
|
||||
or
|
||||
// local flow
|
||||
exists(Node mid |
|
||||
parameterValueFlow(p, mid, read) and
|
||||
simpleLocalFlowStep(mid, node)
|
||||
)
|
||||
or
|
||||
// read
|
||||
exists(Node mid |
|
||||
parameterValueFlow(p, mid, TReadStepTypesNone()) and
|
||||
readStepWithTypes(mid, read.getContainerType(), read.getContent(), node,
|
||||
read.getContentType()) and
|
||||
Cand::parameterValueFlowReturnCand(p, _, true) and
|
||||
compatibleTypes(getNodeType(p), read.getContainerType())
|
||||
)
|
||||
or
|
||||
parameterValueFlow0_0(TReadStepTypesNone(), p, node, read)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate parameterValueFlow0_0(
|
||||
ReadStepTypesOption mustBeNone, ParameterNode p, Node node, ReadStepTypesOption read
|
||||
) {
|
||||
// flow through: no prior read
|
||||
exists(ArgumentNode arg |
|
||||
parameterValueFlowArg(p, arg, mustBeNone) and
|
||||
argumentValueFlowsThrough(arg, read, node)
|
||||
)
|
||||
or
|
||||
// flow through: no read inside method
|
||||
exists(ArgumentNode arg |
|
||||
parameterValueFlowArg(p, arg, read) and
|
||||
argumentValueFlowsThrough(arg, mustBeNone, node)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate parameterValueFlowArg(
|
||||
ParameterNode p, ArgumentNode arg, ReadStepTypesOption read
|
||||
) {
|
||||
parameterValueFlow(p, arg, read) and
|
||||
Cand::argumentValueFlowsThroughCand(arg, _, _)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate argumentValueFlowsThrough0(
|
||||
DataFlowCall call, ArgumentNode arg, ReturnKind kind, ReadStepTypesOption read
|
||||
) {
|
||||
exists(ParameterNode param | viableParamArg(call, param, arg) |
|
||||
parameterValueFlowReturn(param, kind, read)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `arg` flows to `out` through a call using only
|
||||
* value-preserving steps and possibly a single read step, not taking
|
||||
* call contexts into account.
|
||||
*
|
||||
* If a read step was taken, then `read` captures the `Content`, the
|
||||
* container type, and the content type.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate argumentValueFlowsThrough(ArgumentNode arg, ReadStepTypesOption read, Node out) {
|
||||
exists(DataFlowCall call, ReturnKind kind |
|
||||
argumentValueFlowsThrough0(call, arg, kind, read) and
|
||||
out = getAnOutNode(call, kind)
|
||||
|
|
||||
// normal flow through
|
||||
read = TReadStepTypesNone() and
|
||||
compatibleTypes(getNodeType(arg), getNodeType(out))
|
||||
or
|
||||
// getter
|
||||
compatibleTypes(getNodeType(arg), read.getContainerType()) and
|
||||
compatibleTypes(read.getContentType(), getNodeType(out))
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `arg` flows to `out` through a call using only
|
||||
* value-preserving steps and a single read step, not taking call
|
||||
* contexts into account, thus representing a getter-step.
|
||||
*/
|
||||
predicate getterStep(ArgumentNode arg, Content c, Node out) {
|
||||
argumentValueFlowsThrough(arg, TReadStepTypesSome(_, c, _), out)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `p` can flow to a return node of kind `kind` in the same
|
||||
* callable using only value-preserving steps and possibly a single read
|
||||
* step.
|
||||
*
|
||||
* If a read step was taken, then `read` captures the `Content`, the
|
||||
* container type, and the content type.
|
||||
*/
|
||||
private predicate parameterValueFlowReturn(
|
||||
ParameterNode p, ReturnKind kind, ReadStepTypesOption read
|
||||
) {
|
||||
exists(ReturnNode ret |
|
||||
parameterValueFlow(p, ret, read) and
|
||||
kind = ret.getKind()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
import Final
|
||||
}
|
||||
|
||||
import FlowThrough
|
||||
|
||||
cached
|
||||
private module DispatchWithCallContext {
|
||||
/**
|
||||
* Holds if the call context `ctx` reduces the set of viable run-time
|
||||
* dispatch targets of call `call` in `c`.
|
||||
*/
|
||||
cached
|
||||
predicate reducedViableImplInCallContext(DataFlowCall call, DataFlowCallable c, DataFlowCall ctx) {
|
||||
exists(int tgts, int ctxtgts |
|
||||
mayBenefitFromCallContext(call, c) and
|
||||
c = viableCallable(ctx) and
|
||||
ctxtgts = count(viableImplInCallContext(call, ctx)) and
|
||||
tgts = strictcount(viableCallable(call)) and
|
||||
ctxtgts < tgts
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a viable run-time dispatch target for the call `call` in the
|
||||
* context `ctx`. This is restricted to those calls for which a context
|
||||
* makes a difference.
|
||||
*/
|
||||
cached
|
||||
DataFlowCallable prunedViableImplInCallContext(DataFlowCall call, DataFlowCall ctx) {
|
||||
result = viableImplInCallContext(call, ctx) and
|
||||
reducedViableImplInCallContext(call, _, ctx)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if flow returning from callable `c` to call `call` might return
|
||||
* further and if this path restricts the set of call sites that can be
|
||||
* returned to.
|
||||
*/
|
||||
cached
|
||||
predicate reducedViableImplInReturn(DataFlowCallable c, DataFlowCall call) {
|
||||
exists(int tgts, int ctxtgts |
|
||||
mayBenefitFromCallContext(call, _) and
|
||||
c = viableCallable(call) and
|
||||
ctxtgts = count(DataFlowCall ctx | c = viableImplInCallContext(call, ctx)) and
|
||||
tgts = strictcount(DataFlowCall ctx | viableCallable(ctx) = call.getEnclosingCallable()) and
|
||||
ctxtgts < tgts
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a viable run-time dispatch target for the call `call` in the
|
||||
* context `ctx`. This is restricted to those calls and results for which
|
||||
* the return flow from the result to `call` restricts the possible context
|
||||
* `ctx`.
|
||||
*/
|
||||
cached
|
||||
DataFlowCallable prunedViableImplInCallContextReverse(DataFlowCall call, DataFlowCall ctx) {
|
||||
result = viableImplInCallContext(call, ctx) and
|
||||
reducedViableImplInReturn(result, call)
|
||||
}
|
||||
}
|
||||
|
||||
import DispatchWithCallContext
|
||||
|
||||
/**
|
||||
* Holds if `p` can flow to the pre-update node associated with post-update
|
||||
* node `n`, in the same callable, using only value-preserving steps.
|
||||
*/
|
||||
cached
|
||||
predicate parameterValueFlowsToPreUpdate(ParameterNode p, PostUpdateNode n) {
|
||||
parameterValueFlow(p, n.getPreUpdateNode(), TReadStepTypesNone())
|
||||
}
|
||||
|
||||
private predicate store(
|
||||
Node node1, Content c, Node node2, DataFlowType contentType, DataFlowType containerType
|
||||
) {
|
||||
storeStep(node1, c, node2) and
|
||||
readStep(_, c, _) and
|
||||
contentType = getNodeType(node1) and
|
||||
containerType = getNodeType(node2)
|
||||
or
|
||||
exists(Node n1, Node n2 |
|
||||
n1 = node1.(PostUpdateNode).getPreUpdateNode() and
|
||||
n2 = node2.(PostUpdateNode).getPreUpdateNode()
|
||||
|
|
||||
argumentValueFlowsThrough(n2, TReadStepTypesSome(containerType, c, contentType), n1)
|
||||
or
|
||||
readStep(n2, c, n1) and
|
||||
contentType = getNodeType(n1) and
|
||||
containerType = getNodeType(n2)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if data can flow from `node1` to `node2` via a direct assignment to
|
||||
* `f`.
|
||||
*
|
||||
* This includes reverse steps through reads when the result of the read has
|
||||
* been stored into, in order to handle cases like `x.f1.f2 = y`.
|
||||
*/
|
||||
cached
|
||||
predicate store(Node node1, TypedContent tc, Node node2, DataFlowType contentType) {
|
||||
store(node1, tc.getContent(), node2, contentType, tc.getContainerType())
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the call context `call` either improves virtual dispatch in
|
||||
* `callable` or if it allows us to prune unreachable nodes in `callable`.
|
||||
*/
|
||||
cached
|
||||
predicate recordDataFlowCallSite(DataFlowCall call, DataFlowCallable callable) {
|
||||
reducedViableImplInCallContext(_, callable, call)
|
||||
or
|
||||
exists(Node n | n.getEnclosingCallable() = callable | isUnreachableInCall(n, call))
|
||||
}
|
||||
|
||||
cached
|
||||
newtype TCallContext =
|
||||
TAnyCallContext() or
|
||||
TSpecificCall(DataFlowCall call) { recordDataFlowCallSite(call, _) } or
|
||||
TSomeCall() or
|
||||
TReturn(DataFlowCallable c, DataFlowCall call) { reducedViableImplInReturn(c, call) }
|
||||
|
||||
cached
|
||||
newtype TReturnPosition =
|
||||
TReturnPosition0(DataFlowCallable c, ReturnKindExt kind) {
|
||||
exists(ReturnNodeExt ret |
|
||||
c = returnNodeGetEnclosingCallable(ret) and
|
||||
kind = ret.getKind()
|
||||
)
|
||||
}
|
||||
|
||||
cached
|
||||
newtype TLocalFlowCallContext =
|
||||
TAnyLocalCall() or
|
||||
TSpecificLocalCall(DataFlowCall call) { isUnreachableInCall(_, call) }
|
||||
|
||||
cached
|
||||
newtype TReturnKindExt =
|
||||
TValueReturn(ReturnKind kind) or
|
||||
TParamUpdate(int pos) { exists(ParameterNode p | p.isParameterOf(_, pos)) }
|
||||
|
||||
cached
|
||||
newtype TBooleanOption =
|
||||
TBooleanNone() or
|
||||
TBooleanSome(boolean b) { b = true or b = false }
|
||||
|
||||
cached
|
||||
newtype TTypedContent = MkTypedContent(Content c, DataFlowType t) { store(_, c, _, _, t) }
|
||||
|
||||
cached
|
||||
newtype TAccessPathFront =
|
||||
TFrontNil(DataFlowType t) or
|
||||
TFrontHead(TypedContent tc)
|
||||
|
||||
cached
|
||||
newtype TAccessPathFrontOption =
|
||||
TAccessPathFrontNone() or
|
||||
TAccessPathFrontSome(AccessPathFront apf)
|
||||
}
|
||||
|
||||
/**
|
||||
* A `Node` at which a cast can occur such that the type should be checked.
|
||||
*/
|
||||
class CastingNode extends Node {
|
||||
CastingNode() {
|
||||
this instanceof ParameterNode or
|
||||
this instanceof CastNode or
|
||||
this instanceof OutNodeExt or
|
||||
// For reads, `x.f`, we want to check that the tracked type after the read (which
|
||||
// is obtained by popping the head of the access path stack) is compatible with
|
||||
// the type of `x.f`.
|
||||
readStep(_, _, this)
|
||||
}
|
||||
}
|
||||
|
||||
private predicate readStepWithTypes(
|
||||
Node n1, DataFlowType container, Content c, Node n2, DataFlowType content
|
||||
) {
|
||||
readStep(n1, c, n2) and
|
||||
container = getNodeType(n1) and
|
||||
content = getNodeType(n2)
|
||||
}
|
||||
|
||||
private newtype TReadStepTypesOption =
|
||||
TReadStepTypesNone() or
|
||||
TReadStepTypesSome(DataFlowType container, Content c, DataFlowType content) {
|
||||
readStepWithTypes(_, container, c, _, content)
|
||||
}
|
||||
|
||||
private class ReadStepTypesOption extends TReadStepTypesOption {
|
||||
predicate isSome() { this instanceof TReadStepTypesSome }
|
||||
|
||||
DataFlowType getContainerType() { this = TReadStepTypesSome(result, _, _) }
|
||||
|
||||
Content getContent() { this = TReadStepTypesSome(_, result, _) }
|
||||
|
||||
DataFlowType getContentType() { this = TReadStepTypesSome(_, _, result) }
|
||||
|
||||
string toString() { if this.isSome() then result = "Some(..)" else result = "None()" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call context to restrict the targets of virtual dispatch, prune local flow,
|
||||
* and match the call sites of flow into a method with flow out of a method.
|
||||
*
|
||||
* There are four cases:
|
||||
* - `TAnyCallContext()` : No restrictions on method flow.
|
||||
* - `TSpecificCall(DataFlowCall call)` : Flow entered through the
|
||||
* given `call`. This call improves the set of viable
|
||||
* dispatch targets for at least one method call in the current callable
|
||||
* or helps prune unreachable nodes in the current callable.
|
||||
* - `TSomeCall()` : Flow entered through a parameter. The
|
||||
* originating call does not improve the set of dispatch targets for any
|
||||
* method call in the current callable and was therefore not recorded.
|
||||
* - `TReturn(Callable c, DataFlowCall call)` : Flow reached `call` from `c` and
|
||||
* this dispatch target of `call` implies a reduced set of dispatch origins
|
||||
* to which data may flow if it should reach a `return` statement.
|
||||
*/
|
||||
abstract class CallContext extends TCallContext {
|
||||
abstract string toString();
|
||||
|
||||
/** Holds if this call context is relevant for `callable`. */
|
||||
abstract predicate relevantFor(DataFlowCallable callable);
|
||||
}
|
||||
|
||||
abstract class CallContextNoCall extends CallContext { }
|
||||
|
||||
class CallContextAny extends CallContextNoCall, TAnyCallContext {
|
||||
override string toString() { result = "CcAny" }
|
||||
|
||||
override predicate relevantFor(DataFlowCallable callable) { any() }
|
||||
}
|
||||
|
||||
abstract class CallContextCall extends CallContext {
|
||||
/** Holds if this call context may be `call`. */
|
||||
bindingset[call]
|
||||
abstract predicate matchesCall(DataFlowCall call);
|
||||
}
|
||||
|
||||
class CallContextSpecificCall extends CallContextCall, TSpecificCall {
|
||||
override string toString() {
|
||||
exists(DataFlowCall call | this = TSpecificCall(call) | result = "CcCall(" + call + ")")
|
||||
}
|
||||
|
||||
override predicate relevantFor(DataFlowCallable callable) {
|
||||
recordDataFlowCallSite(getCall(), callable)
|
||||
}
|
||||
|
||||
override predicate matchesCall(DataFlowCall call) { call = this.getCall() }
|
||||
|
||||
DataFlowCall getCall() { this = TSpecificCall(result) }
|
||||
}
|
||||
|
||||
class CallContextSomeCall extends CallContextCall, TSomeCall {
|
||||
override string toString() { result = "CcSomeCall" }
|
||||
|
||||
override predicate relevantFor(DataFlowCallable callable) {
|
||||
exists(ParameterNode p | p.getEnclosingCallable() = callable)
|
||||
}
|
||||
|
||||
override predicate matchesCall(DataFlowCall call) { any() }
|
||||
}
|
||||
|
||||
class CallContextReturn extends CallContextNoCall, TReturn {
|
||||
override string toString() {
|
||||
exists(DataFlowCall call | this = TReturn(_, call) | result = "CcReturn(" + call + ")")
|
||||
}
|
||||
|
||||
override predicate relevantFor(DataFlowCallable callable) {
|
||||
exists(DataFlowCall call | this = TReturn(_, call) and call.getEnclosingCallable() = callable)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call context that is relevant for pruning local flow.
|
||||
*/
|
||||
abstract class LocalCallContext extends TLocalFlowCallContext {
|
||||
abstract string toString();
|
||||
|
||||
/** Holds if this call context is relevant for `callable`. */
|
||||
abstract predicate relevantFor(DataFlowCallable callable);
|
||||
}
|
||||
|
||||
class LocalCallContextAny extends LocalCallContext, TAnyLocalCall {
|
||||
override string toString() { result = "LocalCcAny" }
|
||||
|
||||
override predicate relevantFor(DataFlowCallable callable) { any() }
|
||||
}
|
||||
|
||||
class LocalCallContextSpecificCall extends LocalCallContext, TSpecificLocalCall {
|
||||
LocalCallContextSpecificCall() { this = TSpecificLocalCall(call) }
|
||||
|
||||
DataFlowCall call;
|
||||
|
||||
DataFlowCall getCall() { result = call }
|
||||
|
||||
override string toString() { result = "LocalCcCall(" + call + ")" }
|
||||
|
||||
override predicate relevantFor(DataFlowCallable callable) { relevantLocalCCtx(call, callable) }
|
||||
}
|
||||
|
||||
private predicate relevantLocalCCtx(DataFlowCall call, DataFlowCallable callable) {
|
||||
exists(Node n | n.getEnclosingCallable() = callable and isUnreachableInCall(n, call))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the local call context given the call context and the callable that
|
||||
* the contexts apply to.
|
||||
*/
|
||||
LocalCallContext getLocalCallContext(CallContext ctx, DataFlowCallable callable) {
|
||||
ctx.relevantFor(callable) and
|
||||
if relevantLocalCCtx(ctx.(CallContextSpecificCall).getCall(), callable)
|
||||
then result.(LocalCallContextSpecificCall).getCall() = ctx.(CallContextSpecificCall).getCall()
|
||||
else result instanceof LocalCallContextAny
|
||||
}
|
||||
|
||||
/**
|
||||
* A node from which flow can return to the caller. This is either a regular
|
||||
* `ReturnNode` or a `PostUpdateNode` corresponding to the value of a parameter.
|
||||
*/
|
||||
class ReturnNodeExt extends Node {
|
||||
ReturnNodeExt() {
|
||||
this instanceof ReturnNode or
|
||||
parameterValueFlowsToPreUpdate(_, this)
|
||||
}
|
||||
|
||||
/** Gets the kind of this returned value. */
|
||||
ReturnKindExt getKind() {
|
||||
result = TValueReturn(this.(ReturnNode).getKind())
|
||||
or
|
||||
exists(ParameterNode p, int pos |
|
||||
parameterValueFlowsToPreUpdate(p, this) and
|
||||
p.isParameterOf(_, pos) and
|
||||
result = TParamUpdate(pos)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A node to which data can flow from a call. Either an ordinary out node
|
||||
* or a post-update node associated with a call argument.
|
||||
*/
|
||||
class OutNodeExt extends Node {
|
||||
OutNodeExt() {
|
||||
this instanceof OutNode
|
||||
or
|
||||
this.(PostUpdateNode).getPreUpdateNode() instanceof ArgumentNode
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An extended return kind. A return kind describes how data can be returned
|
||||
* from a callable. This can either be through a returned value or an updated
|
||||
* parameter.
|
||||
*/
|
||||
abstract class ReturnKindExt extends TReturnKindExt {
|
||||
/** Gets a textual representation of this return kind. */
|
||||
abstract string toString();
|
||||
|
||||
/** Gets a node corresponding to data flow out of `call`. */
|
||||
abstract OutNodeExt getAnOutNode(DataFlowCall call);
|
||||
}
|
||||
|
||||
class ValueReturnKind extends ReturnKindExt, TValueReturn {
|
||||
private ReturnKind kind;
|
||||
|
||||
ValueReturnKind() { this = TValueReturn(kind) }
|
||||
|
||||
ReturnKind getKind() { result = kind }
|
||||
|
||||
override string toString() { result = kind.toString() }
|
||||
|
||||
override OutNodeExt getAnOutNode(DataFlowCall call) {
|
||||
result = getAnOutNode(call, this.getKind())
|
||||
}
|
||||
}
|
||||
|
||||
class ParamUpdateReturnKind extends ReturnKindExt, TParamUpdate {
|
||||
private int pos;
|
||||
|
||||
ParamUpdateReturnKind() { this = TParamUpdate(pos) }
|
||||
|
||||
int getPosition() { result = pos }
|
||||
|
||||
override string toString() { result = "param update " + pos }
|
||||
|
||||
override OutNodeExt getAnOutNode(DataFlowCall call) {
|
||||
exists(ArgumentNode arg |
|
||||
result.(PostUpdateNode).getPreUpdateNode() = arg and
|
||||
arg.argumentOf(call, this.getPosition())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** A callable tagged with a relevant return kind. */
|
||||
class ReturnPosition extends TReturnPosition0 {
|
||||
private DataFlowCallable c;
|
||||
private ReturnKindExt kind;
|
||||
|
||||
ReturnPosition() { this = TReturnPosition0(c, kind) }
|
||||
|
||||
/** Gets the callable. */
|
||||
DataFlowCallable getCallable() { result = c }
|
||||
|
||||
/** Gets the return kind. */
|
||||
ReturnKindExt getKind() { result = kind }
|
||||
|
||||
/** Gets a textual representation of this return position. */
|
||||
string toString() { result = "[" + kind + "] " + c }
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private DataFlowCallable returnNodeGetEnclosingCallable(ReturnNodeExt ret) {
|
||||
result = ret.getEnclosingCallable()
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private ReturnPosition getReturnPosition0(ReturnNodeExt ret, ReturnKindExt kind) {
|
||||
result.getCallable() = returnNodeGetEnclosingCallable(ret) and
|
||||
kind = result.getKind()
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
ReturnPosition getReturnPosition(ReturnNodeExt ret) {
|
||||
result = getReturnPosition0(ret, ret.getKind())
|
||||
}
|
||||
|
||||
bindingset[cc, callable]
|
||||
predicate resolveReturn(CallContext cc, DataFlowCallable callable, DataFlowCall call) {
|
||||
cc instanceof CallContextAny and callable = viableCallable(call)
|
||||
or
|
||||
exists(DataFlowCallable c0, DataFlowCall call0 |
|
||||
call0.getEnclosingCallable() = callable and
|
||||
cc = TReturn(c0, call0) and
|
||||
c0 = prunedViableImplInCallContextReverse(call0, call)
|
||||
)
|
||||
}
|
||||
|
||||
bindingset[call, cc]
|
||||
DataFlowCallable resolveCall(DataFlowCall call, CallContext cc) {
|
||||
exists(DataFlowCall ctx | cc = TSpecificCall(ctx) |
|
||||
if reducedViableImplInCallContext(call, _, ctx)
|
||||
then result = prunedViableImplInCallContext(call, ctx)
|
||||
else result = viableCallable(call)
|
||||
)
|
||||
or
|
||||
result = viableCallable(call) and cc instanceof CallContextSomeCall
|
||||
or
|
||||
result = viableCallable(call) and cc instanceof CallContextAny
|
||||
or
|
||||
result = viableCallable(call) and cc instanceof CallContextReturn
|
||||
}
|
||||
|
||||
predicate read = readStep/3;
|
||||
|
||||
/** An optional Boolean value. */
|
||||
class BooleanOption extends TBooleanOption {
|
||||
string toString() {
|
||||
this = TBooleanNone() and result = "<none>"
|
||||
or
|
||||
this = TBooleanSome(any(boolean b | result = b.toString()))
|
||||
}
|
||||
}
|
||||
|
||||
/** Content tagged with the type of a containing object. */
|
||||
class TypedContent extends MkTypedContent {
|
||||
private Content c;
|
||||
private DataFlowType t;
|
||||
|
||||
TypedContent() { this = MkTypedContent(c, t) }
|
||||
|
||||
/** Gets the content. */
|
||||
Content getContent() { result = c }
|
||||
|
||||
/** Gets the container type. */
|
||||
DataFlowType getContainerType() { result = t }
|
||||
|
||||
/** Gets a textual representation of this content. */
|
||||
string toString() { result = c.toString() }
|
||||
}
|
||||
|
||||
/**
|
||||
* The front of an access path. This is either a head or a nil.
|
||||
*/
|
||||
abstract class AccessPathFront extends TAccessPathFront {
|
||||
abstract string toString();
|
||||
|
||||
abstract DataFlowType getType();
|
||||
|
||||
abstract boolean toBoolNonEmpty();
|
||||
|
||||
TypedContent getHead() { this = TFrontHead(result) }
|
||||
|
||||
predicate isClearedAt(Node n) { clearsContent(n, getHead().getContent()) }
|
||||
}
|
||||
|
||||
class AccessPathFrontNil extends AccessPathFront, TFrontNil {
|
||||
private DataFlowType t;
|
||||
|
||||
AccessPathFrontNil() { this = TFrontNil(t) }
|
||||
|
||||
override string toString() { result = ppReprType(t) }
|
||||
|
||||
override DataFlowType getType() { result = t }
|
||||
|
||||
override boolean toBoolNonEmpty() { result = false }
|
||||
}
|
||||
|
||||
class AccessPathFrontHead extends AccessPathFront, TFrontHead {
|
||||
private TypedContent tc;
|
||||
|
||||
AccessPathFrontHead() { this = TFrontHead(tc) }
|
||||
|
||||
override string toString() { result = tc.toString() }
|
||||
|
||||
override DataFlowType getType() { result = tc.getContainerType() }
|
||||
|
||||
override boolean toBoolNonEmpty() { result = true }
|
||||
}
|
||||
|
||||
/** An optional access path front. */
|
||||
class AccessPathFrontOption extends TAccessPathFrontOption {
|
||||
string toString() {
|
||||
this = TAccessPathFrontNone() and result = "<none>"
|
||||
or
|
||||
this = TAccessPathFrontSome(any(AccessPathFront apf | result = apf.toString()))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Provides Ruby-specific definitions for use in the data flow library.
|
||||
*/
|
||||
module Private {
|
||||
import DataFlowPrivate
|
||||
import DataFlowDispatch
|
||||
}
|
||||
|
||||
module Public {
|
||||
import DataFlowPublic
|
||||
}
|
||||
362
ql/src/codeql_ruby/dataflow/internal/DataFlowPrivate.qll
Normal file
362
ql/src/codeql_ruby/dataflow/internal/DataFlowPrivate.qll
Normal file
@@ -0,0 +1,362 @@
|
||||
private import ruby
|
||||
private import codeql_ruby.CFG
|
||||
private import codeql_ruby.dataflow.SSA
|
||||
private import DataFlowPublic
|
||||
private import DataFlowDispatch
|
||||
|
||||
abstract class NodeImpl extends Node {
|
||||
/** Do not call: use `getEnclosingCallable()` instead. */
|
||||
abstract CfgScope getCfgScope();
|
||||
|
||||
/** Do not call: use `getLocation()` instead. */
|
||||
abstract Location getLocationImpl();
|
||||
|
||||
/** Do not call: use `toString()` instead. */
|
||||
abstract string toStringImpl();
|
||||
}
|
||||
|
||||
private class ExprNodeImpl extends ExprNode, NodeImpl {
|
||||
override CfgScope getCfgScope() { result = this.getExprNode().getExpr().getCfgScope() }
|
||||
|
||||
override Location getLocationImpl() { result = this.getExprNode().getLocation() }
|
||||
|
||||
override string toStringImpl() { result = this.getExprNode().toString() }
|
||||
}
|
||||
|
||||
/** Provides predicates related to local data flow. */
|
||||
module LocalFlow {
|
||||
private import codeql_ruby.dataflow.internal.SsaImpl
|
||||
|
||||
/**
|
||||
* Holds if `nodeFrom` is a last node referencing SSA definition `def`, which
|
||||
* can reach `next`.
|
||||
*/
|
||||
private predicate localFlowSsaInput(Node nodeFrom, Ssa::Definition def, Ssa::Definition next) {
|
||||
exists(BasicBlock bb, int i | lastRefBeforeRedef(def, bb, i, next) |
|
||||
def = nodeFrom.(SsaDefinitionNode).getDefinition() and
|
||||
def.definesAt(_, bb, i)
|
||||
or
|
||||
exists(CfgNodes::ExprCfgNode e |
|
||||
e = nodeFrom.asExpr() and
|
||||
e = bb.getNode(i) and
|
||||
e.getExpr() instanceof VariableReadAccess
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there is a local flow step from `nodeFrom` to `nodeTo` involving
|
||||
* SSA definition `def.
|
||||
*/
|
||||
predicate localSsaFlowStep(Ssa::Definition def, Node nodeFrom, Node nodeTo) {
|
||||
// Flow from assignment into SSA definition
|
||||
exists(CfgNodes::ExprNodes::AssignmentCfgNode a, BasicBlock bb, int i |
|
||||
def.definesAt(_, bb, i) and
|
||||
a = bb.getNode(i) and
|
||||
a.getExpr() instanceof AssignExpr and
|
||||
nodeFrom.asExpr() = a.getRhs() and
|
||||
nodeTo.(SsaDefinitionNode).getDefinition() = def
|
||||
)
|
||||
or
|
||||
// Flow from SSA definition to first read
|
||||
def = nodeFrom.(SsaDefinitionNode).getDefinition() and
|
||||
nodeTo.asExpr() = def.getAFirstRead()
|
||||
or
|
||||
// Flow from read to next read
|
||||
exists(
|
||||
CfgNodes::ExprNodes::VariableReadAccessCfgNode read1,
|
||||
CfgNodes::ExprNodes::VariableReadAccessCfgNode read2
|
||||
|
|
||||
def.hasAdjacentReads(read1, read2) and
|
||||
nodeTo.asExpr() = read2
|
||||
|
|
||||
nodeFrom.asExpr() = read1
|
||||
or
|
||||
read1 = nodeFrom.(PostUpdateNode).getPreUpdateNode().asExpr()
|
||||
)
|
||||
or
|
||||
// Flow into phi node
|
||||
exists(Ssa::PhiNode phi |
|
||||
localFlowSsaInput(nodeFrom, def, phi) and
|
||||
phi = nodeTo.(SsaDefinitionNode).getDefinition() and
|
||||
def = phi.getAnInput()
|
||||
)
|
||||
// TODO
|
||||
// or
|
||||
// // Flow into uncertain SSA definition
|
||||
// exists(LocalFlow::UncertainExplicitSsaDefinition uncertain |
|
||||
// localFlowSsaInput(nodeFrom, def, uncertain) and
|
||||
// uncertain = nodeTo.(SsaDefinitionNode).getDefinition() and
|
||||
// def = uncertain.getPriorDefinition()
|
||||
// )
|
||||
}
|
||||
}
|
||||
|
||||
/** An argument of a call (including qualifier arguments). */
|
||||
private class Argument extends Expr {
|
||||
private Call call;
|
||||
private int arg;
|
||||
|
||||
Argument() { this = call.getArgument(arg) }
|
||||
|
||||
/** Holds if this expression is the `i`th argument of `c`. */
|
||||
predicate isArgumentOf(Expr c, int i) { c = call and i = arg }
|
||||
}
|
||||
|
||||
/** A collection of cached types and predicates to be evaluated in the same stage. */
|
||||
cached
|
||||
private module Cached {
|
||||
cached
|
||||
newtype TNode =
|
||||
TExprNode(CfgNodes::ExprCfgNode n) or
|
||||
TSsaDefinitionNode(Ssa::Definition def) or
|
||||
TParameterNode(Parameter p) or
|
||||
TExprPostUpdateNode(CfgNodes::ExprCfgNode n) { n.getNode() instanceof Argument }
|
||||
|
||||
/**
|
||||
* This is the local flow predicate that is used as a building block in global
|
||||
* data flow. It excludes SSA flow through instance fields, as flow through fields
|
||||
* is handled by the global data-flow library, but includes various other steps
|
||||
* that are only relevant for global flow.
|
||||
*/
|
||||
cached
|
||||
predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) {
|
||||
exists(Ssa::Definition def | LocalFlow::localSsaFlowStep(def, nodeFrom, nodeTo))
|
||||
or
|
||||
nodeFrom.asExpr() = nodeTo.asExpr().(CfgNodes::ExprNodes::AssignmentCfgNode).getRhs()
|
||||
}
|
||||
|
||||
cached
|
||||
newtype TContent = TTodoContent() // stub
|
||||
|
||||
/** Holds if `n` should be hidden from path explanations. */
|
||||
cached
|
||||
predicate nodeIsHidden(Node n) {
|
||||
exists(Ssa::Definition def | def = n.(SsaDefinitionNode).getDefinition() |
|
||||
def instanceof Ssa::PhiNode
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
import Cached
|
||||
|
||||
/** An SSA definition, viewed as a node in a data flow graph. */
|
||||
class SsaDefinitionNode extends NodeImpl, TSsaDefinitionNode {
|
||||
Ssa::Definition def;
|
||||
|
||||
SsaDefinitionNode() { this = TSsaDefinitionNode(def) }
|
||||
|
||||
/** Gets the underlying SSA definition. */
|
||||
Ssa::Definition getDefinition() { result = def }
|
||||
|
||||
override CfgScope getCfgScope() { result = def.getBasicBlock().getScope() }
|
||||
|
||||
override Location getLocationImpl() { result = def.getLocation() }
|
||||
|
||||
override string toStringImpl() { result = def.toString() }
|
||||
}
|
||||
|
||||
private module ParameterNodes {
|
||||
abstract private class ParameterNodeImpl extends ParameterNode, NodeImpl { }
|
||||
|
||||
/**
|
||||
* The value of an explicit parameter at function entry, viewed as a node in a data
|
||||
* flow graph.
|
||||
*/
|
||||
class ExplicitParameterNode extends ParameterNodeImpl, TParameterNode {
|
||||
private Parameter parameter;
|
||||
|
||||
ExplicitParameterNode() { this = TParameterNode(parameter) }
|
||||
|
||||
override Parameter getParameter() { result = parameter }
|
||||
|
||||
override predicate isParameterOf(Callable c, int i) { c.getParameter(i) = parameter }
|
||||
|
||||
override CfgScope getCfgScope() { result = parameter.getCallable() }
|
||||
|
||||
override Location getLocationImpl() { result = parameter.getLocation() }
|
||||
|
||||
override string toStringImpl() { result = parameter.toString() }
|
||||
}
|
||||
}
|
||||
|
||||
import ParameterNodes
|
||||
|
||||
/** A data-flow node that represents a call argument. */
|
||||
abstract class ArgumentNode extends Node {
|
||||
/** Holds if this argument occurs at the given position in the given call. */
|
||||
cached
|
||||
abstract predicate argumentOf(DataFlowCall call, int pos);
|
||||
|
||||
/** Gets the call in which this node is an argument. */
|
||||
final DataFlowCall getCall() { this.argumentOf(result, _) }
|
||||
}
|
||||
|
||||
private module ArgumentNodes {
|
||||
/** A data-flow node that represents an explicit call argument. */
|
||||
class ExplicitArgumentNode extends ArgumentNode {
|
||||
ExplicitArgumentNode() { this.asExpr().getExpr() instanceof Argument }
|
||||
|
||||
override predicate argumentOf(DataFlowCall call, int pos) {
|
||||
this.asExpr() = call.getReceiver() and
|
||||
pos = -1
|
||||
or
|
||||
this.asExpr() = call.getArgument(pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
import ArgumentNodes
|
||||
|
||||
/** A data-flow node that represents a value returned by a callable. */
|
||||
abstract class ReturnNode extends Node {
|
||||
/** Gets the kind of this return node. */
|
||||
abstract ReturnKind getKind();
|
||||
}
|
||||
|
||||
private module ReturnNodes {
|
||||
/**
|
||||
* A data-flow node that represents an expression returned by a callable,
|
||||
* either using a (`yield`) `return` statement or an expression body (`=>`).
|
||||
*/
|
||||
class ExprReturnNode extends ReturnNode, ExprNode {
|
||||
ExprReturnNode() {
|
||||
none() // TODO
|
||||
}
|
||||
|
||||
override ReturnKind getKind() { result instanceof NormalReturnKind }
|
||||
}
|
||||
}
|
||||
|
||||
import ReturnNodes
|
||||
|
||||
/** A data-flow node that represents the output of a call. */
|
||||
abstract class OutNode extends Node {
|
||||
/** Gets the underlying call, where this node is a corresponding output of kind `kind`. */
|
||||
cached
|
||||
abstract DataFlowCall getCall(ReturnKind kind);
|
||||
}
|
||||
|
||||
private module OutNodes {
|
||||
/**
|
||||
* A data-flow node that reads a value returned directly by a callable,
|
||||
* either via a C# call or a CIL call.
|
||||
*/
|
||||
class ExprOutNode extends OutNode, ExprNode {
|
||||
private DataFlowCall call;
|
||||
|
||||
ExprOutNode() { call = this.getExprNode() }
|
||||
|
||||
override DataFlowCall getCall(ReturnKind kind) {
|
||||
result = call and
|
||||
kind instanceof NormalReturnKind
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
import OutNodes
|
||||
|
||||
predicate jumpStep(Node pred, Node succ) { none() }
|
||||
|
||||
predicate storeStep(Node node1, Content c, Node node2) { none() }
|
||||
|
||||
predicate readStep(Node node1, Content c, Node node2) { none() }
|
||||
|
||||
/**
|
||||
* Holds if values stored inside content `c` are cleared at node `n`. For example,
|
||||
* any value stored inside `f` is cleared at the pre-update node associated with `x`
|
||||
* in `x.f = newValue`.
|
||||
*/
|
||||
predicate clearsContent(Node n, Content c) { storeStep(_, c, n) }
|
||||
|
||||
private newtype TDataFlowType = TTodoDataFlowType()
|
||||
|
||||
class DataFlowType extends TDataFlowType {
|
||||
string toString() { result = "" }
|
||||
}
|
||||
|
||||
/** Gets the type of `n` used for type pruning. */
|
||||
DataFlowType getNodeType(NodeImpl n) { any() }
|
||||
|
||||
/** Gets a string representation of a `DataFlowType`. */
|
||||
string ppReprType(DataFlowType t) { result = t.toString() }
|
||||
|
||||
/**
|
||||
* 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() }
|
||||
|
||||
/**
|
||||
* A node associated with an object after an operation that might have
|
||||
* changed its state.
|
||||
*
|
||||
* This can be either the argument to a callable after the callable returns
|
||||
* (which might have mutated the argument), or the qualifier of a field after
|
||||
* an update to the field.
|
||||
*
|
||||
* Nodes corresponding to AST elements, for example `ExprNode`, usually refer
|
||||
* to the value before the update with the exception of `ObjectCreation`,
|
||||
* which represents the value after the constructor has run.
|
||||
*/
|
||||
abstract class PostUpdateNode extends Node {
|
||||
/** Gets the node before the state update. */
|
||||
abstract Node getPreUpdateNode();
|
||||
}
|
||||
|
||||
private module PostUpdateNodes {
|
||||
class ExprPostUpdateNode extends PostUpdateNode, NodeImpl, TExprPostUpdateNode {
|
||||
private CfgNodes::ExprCfgNode e;
|
||||
|
||||
ExprPostUpdateNode() { this = TExprPostUpdateNode(e) }
|
||||
|
||||
override ExprNode getPreUpdateNode() { e = result.getExprNode() }
|
||||
|
||||
override CfgScope getCfgScope() { result = e.getExpr().getCfgScope() }
|
||||
|
||||
override Location getLocationImpl() { result = e.getLocation() }
|
||||
|
||||
override string toStringImpl() { result = "[post] " + e.toString() }
|
||||
}
|
||||
}
|
||||
|
||||
private import PostUpdateNodes
|
||||
|
||||
/** A node that performs a type cast. */
|
||||
class CastNode extends Node {
|
||||
CastNode() { none() }
|
||||
}
|
||||
|
||||
class DataFlowExpr = CfgNodes::ExprCfgNode;
|
||||
|
||||
int accessPathLimit() { result = 5 }
|
||||
|
||||
/** The unit type. */
|
||||
private newtype TUnit = TMkUnit()
|
||||
|
||||
/** The trivial type with a single element. */
|
||||
class Unit extends TUnit {
|
||||
/** Gets a textual representation of this element. */
|
||||
string toString() { result = "unit" }
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `n` does not require a `PostUpdateNode` as it either cannot be
|
||||
* modified or its modification cannot be observed, for example if it is a
|
||||
* freshly created object that is not saved in a variable.
|
||||
*
|
||||
* This predicate is only used for consistency checks.
|
||||
*/
|
||||
predicate isImmutableOrUnobservable(Node n) { none() }
|
||||
|
||||
/**
|
||||
* Holds if the node `n` is unreachable when the call context is `call`.
|
||||
*/
|
||||
predicate isUnreachableInCall(Node n, DataFlowCall call) { none() }
|
||||
|
||||
class BarrierGuard extends AstNode {
|
||||
BarrierGuard() { none() }
|
||||
|
||||
Node getAGuardedNode() { none() }
|
||||
}
|
||||
110
ql/src/codeql_ruby/dataflow/internal/DataFlowPublic.qll
Normal file
110
ql/src/codeql_ruby/dataflow/internal/DataFlowPublic.qll
Normal file
@@ -0,0 +1,110 @@
|
||||
private import ruby
|
||||
private import DataFlowDispatch
|
||||
private import DataFlowPrivate
|
||||
private import codeql_ruby.CFG
|
||||
|
||||
/**
|
||||
* An element, viewed as a node in a data flow graph. Either an expression
|
||||
* (`ExprNode`) or a parameter (`ParameterNode`).
|
||||
*/
|
||||
class Node extends TNode {
|
||||
/** Gets the expression corresponding to this node, if any. */
|
||||
CfgNodes::ExprCfgNode asExpr() { result = this.(ExprNode).getExprNode() }
|
||||
|
||||
/** Gets the parameter corresponding to this node, if any. */
|
||||
Parameter asParameter() { result = this.(ParameterNode).getParameter() }
|
||||
|
||||
/** Gets a textual representation of this node. */
|
||||
// TODO: cache
|
||||
final string toString() { result = this.(NodeImpl).toStringImpl() }
|
||||
|
||||
/** Gets the location of this node. */
|
||||
// TODO: cache
|
||||
final Location getLocation() { result = this.(NodeImpl).getLocationImpl() }
|
||||
|
||||
final DataFlowCallable getEnclosingCallable() { result = this.(NodeImpl).getCfgScope() }
|
||||
|
||||
/**
|
||||
* Holds if this element is at the specified location.
|
||||
* The location spans column `startcolumn` of line `startline` to
|
||||
* column `endcolumn` of line `endline` in file `filepath`.
|
||||
* For more information, see
|
||||
* [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
|
||||
*/
|
||||
predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
) {
|
||||
getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An expression, viewed as a node in a data flow graph.
|
||||
*
|
||||
* Note that because of control-flow splitting, one `Expr` may correspond
|
||||
* to multiple `ExprNode`s, just like it may correspond to multiple
|
||||
* `ControlFlow::Node`s.
|
||||
*/
|
||||
class ExprNode extends Node, TExprNode {
|
||||
private CfgNodes::ExprCfgNode n;
|
||||
|
||||
ExprNode() { this = TExprNode(n) }
|
||||
|
||||
/** Gets the expression corresponding to this node. */
|
||||
CfgNodes::ExprCfgNode getExprNode() { result = n }
|
||||
}
|
||||
|
||||
/**
|
||||
* The value of a parameter at function entry, viewed as a node in a data
|
||||
* flow graph.
|
||||
*/
|
||||
class ParameterNode extends Node, TParameterNode {
|
||||
private Parameter p;
|
||||
|
||||
ParameterNode() { this = TParameterNode(p) }
|
||||
|
||||
/** Gets the parameter corresponding to this node, if any. */
|
||||
Parameter getParameter() { result = p }
|
||||
|
||||
/**
|
||||
* Holds if this node is the parameter of callable `c` at the specified
|
||||
* (zero-based) position.
|
||||
*/
|
||||
predicate isParameterOf(Callable c, int i) { p = c.getParameter(i) }
|
||||
}
|
||||
|
||||
/** Gets a node corresponding to expression `e`. */
|
||||
ExprNode exprNode(CfgNodes::ExprCfgNode e) { result.getExprNode() = e }
|
||||
|
||||
/**
|
||||
* Gets the node corresponding to the value of parameter `p` at function entry.
|
||||
*/
|
||||
ParameterNode parameterNode(Parameter p) { result.getParameter() = p }
|
||||
|
||||
predicate localFlowStep = simpleLocalFlowStep/2;
|
||||
|
||||
/**
|
||||
* Holds if data flows from `source` to `sink` in zero or more local
|
||||
* (intra-procedural) steps.
|
||||
*/
|
||||
predicate localFlow(Node source, Node sink) { localFlowStep*(source, sink) }
|
||||
|
||||
/**
|
||||
* Holds if data can flow from `e1` to `e2` in zero or more
|
||||
* local (intra-procedural) steps.
|
||||
*/
|
||||
predicate localExprFlow(CfgNodes::ExprCfgNode e1, CfgNodes::ExprCfgNode e2) {
|
||||
localFlow(exprNode(e1), exprNode(e2))
|
||||
}
|
||||
|
||||
/**
|
||||
* A reference contained in an object. This is either a field, a property,
|
||||
* or an element in a collection.
|
||||
*/
|
||||
class Content extends TContent {
|
||||
/** Gets a textual representation of this content. */
|
||||
string toString() { none() }
|
||||
|
||||
/** Gets the location of this content. */
|
||||
Location getLocation() { none() }
|
||||
}
|
||||
@@ -2,5 +2,13 @@
|
||||
"SSA": [
|
||||
"codeql/csharp/ql/src/semmle/code/csharp/dataflow/internal/SsaImplCommon.qll",
|
||||
"ql/src/codeql_ruby/dataflow/internal/SsaImplCommon.qll"
|
||||
],
|
||||
"DataFlow Common": [
|
||||
"codeql/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImplCommon.qll",
|
||||
"ql/src/codeql_ruby/dataflow/internal/DataFlowImplCommon.qll"
|
||||
],
|
||||
"DataFlow": [
|
||||
"codeql/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll",
|
||||
"ql/src/codeql_ruby/dataflow/internal/DataFlowImpl.qll"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user