Skip to content

Commit 3025174

Browse files
committed
Ruby: Prune nodes before computing trackUseNode
1 parent 8195ebf commit 3025174

File tree

2 files changed

+88
-65
lines changed

2 files changed

+88
-65
lines changed

ruby/ql/lib/codeql/ruby/ApiGraphs.qll

Lines changed: 80 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -252,8 +252,8 @@ module API {
252252
* Holds if `ref` is a use of a node that should have an incoming edge from the root
253253
* node labeled `lbl` in the API graph.
254254
*/
255-
cached
256-
predicate useRoot(string lbl, DataFlow::Node ref) {
255+
pragma[nomagic]
256+
private predicate useRoot(string lbl, DataFlow::Node ref) {
257257
exists(string name, ExprNodes::ConstantAccessCfgNode access, ConstantReadAccess read |
258258
access = ref.asExpr() and
259259
lbl = Label::member(read.getName()) and
@@ -268,64 +268,48 @@ module API {
268268
}
269269

270270
/**
271-
* Holds if `ref` is a use of a node that should have an incoming edge from use node
272-
* `base` labeled `lbl` in the API graph.
271+
* Holds if `ref` is a use of a node that should have an incoming edge labeled `lbl`,
272+
* from a use node that flows to `node`.
273273
*/
274-
cached
275-
predicate useUse(DataFlow::LocalSourceNode base, string lbl, DataFlow::Node ref) {
276-
exists(ExprCfgNode node |
277-
// First, we find a predecessor of the node `ref` that we want to determine. The predecessor
278-
// is any node that is a type-tracked use of a data flow node (`src`), which is itself a
279-
// reference to the API node `base`. Thus, `pred` and `src` both represent uses of `base`.
280-
//
281-
// Once we have identified the predecessor, we define its relation to the successor `ref` as
282-
// well as the label on the edge from `pred` to `ref`. This label describes the nature of
283-
// the relationship between `pred` and `ref`.
284-
useExpr(node, base)
285-
|
286-
// // Referring to an attribute on a node that is a use of `base`:
287-
// pred = `Rails` part of `Rails::Whatever`
288-
// lbl = `Whatever`
289-
// ref = `Rails::Whatever`
290-
exists(ExprNodes::ConstantAccessCfgNode c, ConstantReadAccess read |
291-
not exists(resolveTopLevel(read)) and
292-
node = c.getScopeExpr() and
293-
lbl = Label::member(read.getName()) and
294-
ref.asExpr() = c and
295-
read = c.getExpr()
296-
)
297-
or
298-
// Calling a method on a node that is a use of `base`
299-
exists(ExprNodes::MethodCallCfgNode call, string name |
300-
node = call.getReceiver() and
301-
name = call.getExpr().getMethodName() and
302-
lbl = Label::return(name) and
303-
name != "new" and
304-
ref.asExpr() = call
305-
)
306-
or
307-
// Calling the `new` method on a node that is a use of `base`, which creates a new instance
308-
exists(ExprNodes::MethodCallCfgNode call |
309-
node = call.getReceiver() and
310-
lbl = Label::instance() and
311-
call.getExpr().getMethodName() = "new" and
312-
ref.asExpr() = call
313-
)
274+
private predicate useStep(string lbl, ExprCfgNode node, DataFlow::Node ref) {
275+
// // Referring to an attribute on a node that is a use of `base`:
276+
// pred = `Rails` part of `Rails::Whatever`
277+
// lbl = `Whatever`
278+
// ref = `Rails::Whatever`
279+
exists(ExprNodes::ConstantAccessCfgNode c, ConstantReadAccess read |
280+
not exists(resolveTopLevel(read)) and
281+
node = c.getScopeExpr() and
282+
lbl = Label::member(read.getName()) and
283+
ref.asExpr() = c and
284+
read = c.getExpr()
285+
)
286+
or
287+
// Calling a method on a node that is a use of `base`
288+
exists(ExprNodes::MethodCallCfgNode call, string name |
289+
node = call.getReceiver() and
290+
name = call.getExpr().getMethodName() and
291+
lbl = Label::return(name) and
292+
name != "new" and
293+
ref.asExpr() = call
294+
)
295+
or
296+
// Calling the `new` method on a node that is a use of `base`, which creates a new instance
297+
exists(ExprNodes::MethodCallCfgNode call |
298+
node = call.getReceiver() and
299+
lbl = Label::instance() and
300+
call.getExpr().getMethodName() = "new" and
301+
ref.asExpr() = call
314302
)
315303
}
316304

317305
pragma[nomagic]
318306
private predicate isUse(DataFlow::Node nd) {
319307
useRoot(_, nd)
320308
or
321-
useUse(_, _, nd)
322-
}
323-
324-
pragma[nomagic]
325-
private predicate useExpr(ExprCfgNode node, DataFlow::LocalSourceNode src) {
326-
exists(DataFlow::LocalSourceNode pred |
327-
pred = trackUseNode(src) and
328-
pred.flowsTo(any(DataFlow::ExprNode n | n.getExprNode() = node))
309+
exists(ExprCfgNode node, DataFlow::LocalSourceNode pred |
310+
pred = pruneUseNodeFwd() and
311+
pred.flowsTo(any(DataFlow::ExprNode n | n.getExprNode() = node)) and
312+
useStep(_, node, nd)
329313
)
330314
}
331315

@@ -335,26 +319,56 @@ module API {
335319
cached
336320
predicate use(TApiNode nd, DataFlow::Node ref) { nd = MkUse(ref) }
337321

322+
private DataFlow::LocalSourceNode pruneUseNodeFwd(TypeTracker t) {
323+
t.start() and
324+
isUse(result)
325+
or
326+
exists(TypeTracker t2 | result = pruneUseNodeFwd(t2).track(t2, t))
327+
}
328+
329+
private DataFlow::LocalSourceNode pruneUseNodeFwd() {
330+
result = pruneUseNodeFwd(TypeTracker::end())
331+
}
332+
333+
private DataFlow::Node pruneUseNodeRev(TypeBackTracker tb) {
334+
result = pruneUseNodeFwd() and
335+
tb.start()
336+
or
337+
exists(TypeBackTracker tb2, DataFlow::LocalSourceNode mid, TypeTracker t |
338+
mid = pruneUseNodeRev(tb2) and
339+
result = mid.backtrack(tb2, tb) and
340+
pragma[only_bind_out](result) = pruneUseNodeFwd(t) and
341+
pragma[only_bind_out](t) = pragma[only_bind_out](tb).getACompatibleTypeTracker()
342+
)
343+
}
344+
345+
private DataFlow::LocalSourceNode pruneUseNodeRev() {
346+
result = pruneUseNodeRev(TypeBackTracker::end()) and
347+
isUse(result)
348+
}
349+
338350
/**
339351
* Gets a data-flow node to which `src`, which is a use of an API-graph node, flows.
340352
*
341-
* The flow from `src` to that node may be inter-procedural.
353+
* The flow from `src` to the returned node may be inter-procedural.
342354
*/
343-
private DataFlow::LocalSourceNode trackUseNode(DataFlow::Node src, TypeTracker t) {
344-
// Declaring `src` to be a `LocalSourceNode` currently causes a redundant check in the
345-
// recursive case, so instead we check it explicitly here.
346-
src instanceof DataFlow::LocalSourceNode and
347-
t.start() and
348-
isUse(src) and
349-
result = src
355+
private DataFlow::Node trackUseNode(DataFlow::LocalSourceNode src, TypeTracker t) {
356+
result = src and
357+
result = pruneUseNodeRev() and
358+
t.start()
350359
or
351-
exists(TypeTracker t2 | result = trackUseNode(src, t2).track(t2, t))
360+
exists(TypeTracker t2, DataFlow::LocalSourceNode mid, TypeBackTracker tb |
361+
mid = trackUseNode(src, t2) and
362+
result = mid.track(t2, t) and
363+
pragma[only_bind_out](result) = pruneUseNodeRev(tb) and
364+
pragma[only_bind_out](t) = pragma[only_bind_out](tb).getACompatibleTypeTracker()
365+
)
352366
}
353367

354368
/**
355369
* Gets a data-flow node to which `src`, which is a use of an API-graph node, flows.
356370
*
357-
* The flow from `src` to that node may be inter-procedural.
371+
* The flow from `src` to the returned node may be inter-procedural.
358372
*/
359373
cached
360374
DataFlow::LocalSourceNode trackUseNode(DataFlow::LocalSourceNode src) {
@@ -371,9 +385,10 @@ module API {
371385
pred = MkRoot() and
372386
useRoot(lbl, ref)
373387
or
374-
exists(DataFlow::Node nd |
375-
pred = MkUse(nd) and
376-
useUse(nd, lbl, ref)
388+
exists(ExprCfgNode node, DataFlow::Node src |
389+
pred = MkUse(src) and
390+
trackUseNode(src).flowsTo(any(DataFlow::ExprNode n | n.getExprNode() = node)) and
391+
useStep(lbl, node, ref)
377392
)
378393
)
379394
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,14 @@ class LocalSourceNode extends Node {
102102
*/
103103
pragma[inline]
104104
LocalSourceNode track(TypeTracker t2, TypeTracker t) { t = t2.step(this, result) }
105+
106+
/**
107+
* Gets a node that may flow into this one using one heap and/or interprocedural step.
108+
*
109+
* See `TypeBackTracker` for more details about how to use this.
110+
*/
111+
pragma[inline]
112+
LocalSourceNode backtrack(TypeBackTracker t2, TypeBackTracker t) { t2 = t.step(result, this) }
105113
}
106114

107115
predicate hasLocalSource(Node sink, Node source) {

0 commit comments

Comments
 (0)