Skip to content

Commit 004144b

Browse files
authored
Merge pull request #7028 from hvitved/ruby/api-graphs-prune
Ruby: Prune nodes before computing `trackUseNode`
2 parents e09c124 + 7178a98 commit 004144b

File tree

2 files changed

+86
-65
lines changed

2 files changed

+86
-65
lines changed

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

Lines changed: 78 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 = useCandFwd() and
311+
pred.flowsTo(any(DataFlow::ExprNode n | n.getExprNode() = node)) and
312+
useStep(_, node, nd)
329313
)
330314
}
331315

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

322+
private DataFlow::LocalSourceNode useCandFwd(TypeTracker t) {
323+
t.start() and
324+
isUse(result)
325+
or
326+
exists(TypeTracker t2 | result = useCandFwd(t2).track(t2, t))
327+
}
328+
329+
private DataFlow::LocalSourceNode useCandFwd() { result = useCandFwd(TypeTracker::end()) }
330+
331+
private DataFlow::Node useCandRev(TypeBackTracker tb) {
332+
result = useCandFwd() and
333+
tb.start()
334+
or
335+
exists(TypeBackTracker tb2, DataFlow::LocalSourceNode mid, TypeTracker t |
336+
mid = useCandRev(tb2) and
337+
result = mid.backtrack(tb2, tb) and
338+
pragma[only_bind_out](result) = useCandFwd(t) and
339+
pragma[only_bind_out](t) = pragma[only_bind_out](tb).getACompatibleTypeTracker()
340+
)
341+
}
342+
343+
private DataFlow::LocalSourceNode useCandRev() {
344+
result = useCandRev(TypeBackTracker::end()) and
345+
isUse(result)
346+
}
347+
338348
/**
339349
* Gets a data-flow node to which `src`, which is a use of an API-graph node, flows.
340350
*
341-
* The flow from `src` to that node may be inter-procedural.
351+
* The flow from `src` to the returned node may be inter-procedural.
342352
*/
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
353+
private DataFlow::Node trackUseNode(DataFlow::LocalSourceNode src, TypeTracker t) {
354+
result = src and
355+
result = useCandRev() and
356+
t.start()
350357
or
351-
exists(TypeTracker t2 | result = trackUseNode(src, t2).track(t2, t))
358+
exists(TypeTracker t2, DataFlow::LocalSourceNode mid, TypeBackTracker tb |
359+
mid = trackUseNode(src, t2) and
360+
result = mid.track(t2, t) and
361+
pragma[only_bind_out](result) = useCandRev(tb) and
362+
pragma[only_bind_out](t) = pragma[only_bind_out](tb).getACompatibleTypeTracker()
363+
)
352364
}
353365

354366
/**
355367
* Gets a data-flow node to which `src`, which is a use of an API-graph node, flows.
356368
*
357-
* The flow from `src` to that node may be inter-procedural.
369+
* The flow from `src` to the returned node may be inter-procedural.
358370
*/
359371
cached
360372
DataFlow::LocalSourceNode trackUseNode(DataFlow::LocalSourceNode src) {
@@ -371,9 +383,10 @@ module API {
371383
pred = MkRoot() and
372384
useRoot(lbl, ref)
373385
or
374-
exists(DataFlow::Node nd |
375-
pred = MkUse(nd) and
376-
useUse(nd, lbl, ref)
386+
exists(ExprCfgNode node, DataFlow::Node src |
387+
pred = MkUse(src) and
388+
trackUseNode(src).flowsTo(any(DataFlow::ExprNode n | n.getExprNode() = node)) and
389+
useStep(lbl, node, ref)
377390
)
378391
)
379392
}

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)