Skip to content

Commit 953821c

Browse files
committed
Avoid potential tuple explosion in reverse type tracking
1 parent fdf1cd3 commit 953821c

File tree

3 files changed

+62
-36
lines changed

3 files changed

+62
-36
lines changed

ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll

Lines changed: 48 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -46,18 +46,19 @@ module LocalFlow {
4646
)
4747
}
4848

49+
/** Gets the SSA definition node corresponding to parameter `p`. */
50+
SsaDefinitionNode getParameterDefNode(NamedParameter p) {
51+
exists(BasicBlock bb, int i |
52+
bb.getNode(i).getNode() = p.getDefiningAccess() and
53+
result.getDefinition().definesAt(_, bb, i)
54+
)
55+
}
56+
4957
/**
5058
* Holds if there is a local flow step from `nodeFrom` to `nodeTo` involving
5159
* SSA definition `def`.
5260
*/
5361
predicate localSsaFlowStep(Ssa::Definition def, Node nodeFrom, Node nodeTo) {
54-
// Flow from parameter into SSA definition
55-
exists(BasicBlock bb, int i |
56-
bb.getNode(i).getNode() =
57-
nodeFrom.(ParameterNode).getParameter().(NamedParameter).getDefiningAccess() and
58-
nodeTo.(SsaDefinitionNode).getDefinition().definesAt(_, bb, i)
59-
)
60-
or
6162
// Flow from assignment into SSA definition
6263
def.(Ssa::WriteDefinition).assigns(nodeFrom.asExpr()) and
6364
nodeTo.(SsaDefinitionNode).getDefinition() = def
@@ -145,19 +146,14 @@ private module Cached {
145146
class TParameterNode =
146147
TNormalParameterNode or TBlockParameterNode or TSelfParameterNode or TSummaryParameterNode;
147148

148-
/**
149-
* This is the local flow predicate that is shared between local data flow
150-
* and global data flow.
151-
*/
152-
cached
153-
predicate simpleLocalFlowStepCommon(Node nodeFrom, Node nodeTo) {
154-
LocalFlow::localSsaFlowStep(_, nodeFrom, nodeTo)
149+
private predicate defaultValueFlow(NamedParameter p, ExprNode e) {
150+
p.(OptionalParameter).getDefaultValue() = e.getExprNode().getExpr()
155151
or
156-
nodeTo.(ParameterNode).getParameter().(OptionalParameter).getDefaultValue() =
157-
nodeFrom.asExpr().getExpr()
158-
or
159-
nodeTo.(ParameterNode).getParameter().(KeywordParameter).getDefaultValue() =
160-
nodeFrom.asExpr().getExpr()
152+
p.(KeywordParameter).getDefaultValue() = e.getExprNode().getExpr()
153+
}
154+
155+
private predicate localFlowStepCommon(Node nodeFrom, Node nodeTo) {
156+
LocalFlow::localSsaFlowStep(_, nodeFrom, nodeTo)
161157
or
162158
nodeFrom.(SelfParameterNode).getMethod() = nodeTo.asExpr().getExpr().getEnclosingCallable() and
163159
nodeTo.asExpr().getExpr() instanceof Self
@@ -194,19 +190,46 @@ private module Cached {
194190

195191
/**
196192
* This is the local flow predicate that is used as a building block in global
197-
* data flow. It excludes SSA flow through instance fields, as flow through fields
198-
* is handled by the global data-flow library, but includes various other steps
199-
* that are only relevant for global flow.
193+
* data flow.
200194
*/
201195
cached
202196
predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) {
203-
simpleLocalFlowStepCommon(nodeFrom, nodeTo)
197+
localFlowStepCommon(nodeFrom, nodeTo)
198+
or
199+
defaultValueFlow(nodeTo.(ParameterNode).getParameter(), nodeFrom)
200+
or
201+
nodeTo = LocalFlow::getParameterDefNode(nodeFrom.(ParameterNode).getParameter())
204202
or
205203
nodeTo.(SynthReturnNode).getAnInput() = nodeFrom
206204
or
207205
FlowSummaryImpl::Private::Steps::summaryLocalStep(nodeFrom, nodeTo, true)
208206
}
209207

208+
/** This is the local flow predicate that is exposed. */
209+
cached
210+
predicate localFlowStepImpl(Node nodeFrom, Node nodeTo) {
211+
localFlowStepCommon(nodeFrom, nodeTo)
212+
or
213+
defaultValueFlow(nodeTo.(ParameterNode).getParameter(), nodeFrom)
214+
or
215+
nodeTo = LocalFlow::getParameterDefNode(nodeFrom.(ParameterNode).getParameter())
216+
or
217+
// Simple flow through library code is included in the exposed local
218+
// step relation, even though flow is technically inter-procedural
219+
FlowSummaryImpl::Private::Steps::summaryThroughStep(nodeFrom, nodeTo, true)
220+
}
221+
222+
/** This is the local flow predicate that is used in type tracking. */
223+
cached
224+
predicate localFlowStepTypeTracker(Node nodeFrom, Node nodeTo) {
225+
localFlowStepCommon(nodeFrom, nodeTo)
226+
or
227+
exists(NamedParameter p |
228+
defaultValueFlow(p, nodeFrom) and
229+
nodeTo = LocalFlow::getParameterDefNode(p)
230+
)
231+
}
232+
210233
cached
211234
predicate isLocalSourceNode(Node n) {
212235
n instanceof ParameterNode
@@ -217,7 +240,7 @@ private module Cached {
217240
// and we can remove this case.
218241
n instanceof SelfArgumentNode
219242
or
220-
not simpleLocalFlowStepCommon+(any(Node e |
243+
not localFlowStepTypeTracker+(any(Node e |
221244
e instanceof ExprNode
222245
or
223246
e instanceof ParameterNode
@@ -538,7 +561,7 @@ private module ReturnNodes {
538561

539562
SynthReturnNode() { this = TSynthReturnNode(scope, kind) }
540563

541-
/** Get a syntactic return node that flows into this synthetic node. */
564+
/** Gets a syntactic return node that flows into this synthetic node. */
542565
ReturningNode getAnInput() {
543566
result.(NodeImpl).getCfgScope() = scope and
544567
result.getKind() = kind

ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ predicate hasLocalSource(Node sink, Node source) {
120120
or
121121
exists(Node mid |
122122
hasLocalSource(mid, source) and
123-
simpleLocalFlowStepCommon(mid, sink)
123+
localFlowStepTypeTracker(mid, sink)
124124
)
125125
}
126126

@@ -136,13 +136,7 @@ ParameterNode parameterNode(Parameter p) { result.getParameter() = p }
136136
* Holds if data flows from `nodeFrom` to `nodeTo` in exactly one local
137137
* (intra-procedural) step.
138138
*/
139-
predicate localFlowStep(Node nodeFrom, Node nodeTo) {
140-
simpleLocalFlowStepCommon(nodeFrom, nodeTo)
141-
or
142-
// Simple flow through library code is included in the exposed local
143-
// step relation, even though flow is technically inter-procedural
144-
FlowSummaryImpl::Private::Steps::summaryThroughStep(nodeFrom, nodeTo, true)
145-
}
139+
predicate localFlowStep = localFlowStepImpl/2;
146140

147141
/**
148142
* Holds if data flows from `source` to `sink` in zero or more local

ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ class Node = DataFlowPublic::Node;
1111

1212
class TypeTrackingNode = DataFlowPublic::LocalSourceNode;
1313

14-
predicate simpleLocalFlowStep = DataFlowPrivate::simpleLocalFlowStep/2;
14+
predicate simpleLocalFlowStep = DataFlowPrivate::localFlowStepTypeTracker/2;
1515

1616
predicate jumpStep = DataFlowPrivate::jumpStep/2;
1717

@@ -30,12 +30,21 @@ string getPossibleContentName() { result = getSetterCallAttributeName(_) }
3030
* recursion (or, at best, terrible performance), since identifying calls to library
3131
* methods is done using API graphs (which uses type tracking).
3232
*/
33-
predicate callStep(DataFlowPrivate::ArgumentNode nodeFrom, DataFlowPublic::ParameterNode nodeTo) {
33+
predicate callStep(Node nodeFrom, Node nodeTo) {
3434
exists(ExprNodes::CallCfgNode call, CFG::CfgScope callable, int i |
3535
DataFlowDispatch::getTarget(call) = callable and
36-
nodeFrom.sourceArgumentOf(call, i) and
36+
nodeFrom.(DataFlowPrivate::ArgumentNode).sourceArgumentOf(call, i) and
3737
nodeTo.(DataFlowPrivate::ParameterNodeImpl).isSourceParameterOf(callable, i)
3838
)
39+
or
40+
// In normal data-flow, this will be a local flow step. But for type tracking
41+
// we model it as a call step, in order to avoid computing a potential
42+
// self-cross product of all calls to a function that returns one of its parameters
43+
// (only to later filter that flow out using `TypeTracker::append`).
44+
nodeTo =
45+
DataFlowPrivate::LocalFlow::getParameterDefNode(nodeFrom
46+
.(DataFlowPublic::ParameterNode)
47+
.getParameter())
3948
}
4049

4150
/**

0 commit comments

Comments
 (0)