Skip to content

Commit 55b5f19

Browse files
committed
Ruby: Add def-nodes to API graphs
1 parent 9c17a5c commit 55b5f19

2 files changed

Lines changed: 155 additions & 4 deletions

File tree

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

Lines changed: 152 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ module API {
4343
*/
4444
DataFlow::LocalSourceNode getAnImmediateUse() { Impl::use(this, result) }
4545

46+
/**
47+
* Gets a data-flow node corresponding the value flowing into this API component.
48+
*/
49+
DataFlow::Node getARhs() { Impl::def(this, result) }
50+
4651
/**
4752
* Gets a call to a method on the receiver represented by this API component.
4853
*/
@@ -97,6 +102,30 @@ module API {
97102
result = this.getASubclass().getASuccessor(Label::return(method))
98103
}
99104

105+
private predicate hasParameterIndex(int n) {
106+
exists(string str |
107+
exists(this.getASuccessor(Label::parameterByStr(str))) and
108+
n = str.toInt()
109+
)
110+
}
111+
112+
/** Gets an API node representing the `n`th positional parameter. */
113+
Node getParameter(int n) {
114+
result = this.getASuccessor(Label::parameter(n)) and this.hasParameterIndex(n)
115+
}
116+
117+
private predicate hasKeywordParameter(string name) {
118+
exists(this.getASuccessor(Label::keywordParameter(name)))
119+
}
120+
121+
/** Gets an API node representing the given keyword parameter. */
122+
Node getKeywordParameter(string name) {
123+
result = this.getASuccessor(Label::keywordParameter(name)) and this.hasKeywordParameter(name)
124+
}
125+
126+
/** Gets an API node representing the block parameter. */
127+
Node getBlock() { result = this.getASuccessor(Label::blockParameter()) }
128+
100129
/**
101130
* Gets a `new` call to the function represented by this API component.
102131
*/
@@ -260,7 +289,9 @@ module API {
260289
/** The root of the API graph. */
261290
MkRoot() or
262291
/** A use of an API member at the node `nd`. */
263-
MkUse(DataFlow::Node nd) { isUse(nd) }
292+
MkUse(DataFlow::Node nd) { isUse(nd) } or
293+
/** A value that escapes into an API at the node `nd` */
294+
MkDef(DataFlow::Node nd) { isDef(nd) }
264295

265296
private string resolveTopLevel(ConstantReadAccess read) {
266297
TResolved(result) = resolveConstantReadAccess(read) and
@@ -319,6 +350,8 @@ module API {
319350
useCandFwd().flowsTo(node) and
320351
useStep(_, node, nd)
321352
)
353+
or
354+
parameterStep(_, defCand(), nd)
322355
}
323356

324357
/**
@@ -327,13 +360,21 @@ module API {
327360
cached
328361
predicate use(TApiNode nd, DataFlow::Node ref) { nd = MkUse(ref) }
329362

363+
/**
364+
* Holds if `ref` is a RHS of node `nd`.
365+
*/
366+
cached
367+
predicate def(TApiNode nd, DataFlow::Node rhs) { nd = MkDef(rhs) }
368+
369+
/** Gets a node reachable from a use-node. */
330370
private DataFlow::LocalSourceNode useCandFwd(TypeTracker t) {
331371
t.start() and
332372
isUse(result)
333373
or
334374
exists(TypeTracker t2 | result = useCandFwd(t2).track(t2, t))
335375
}
336376

377+
/** Gets a node reachable from a use-node. */
337378
private DataFlow::LocalSourceNode useCandFwd() { result = useCandFwd(TypeTracker::end()) }
338379

339380
private DataFlow::Node useCandRev(TypeBackTracker tb) {
@@ -353,6 +394,73 @@ module API {
353394
isUse(result)
354395
}
355396

397+
private predicate isDef(DataFlow::Node rhs) {
398+
exists(DataFlow::Node use |
399+
useCandFwd().flowsTo(use) and
400+
argumentStep(_, use, rhs)
401+
)
402+
}
403+
404+
/** Gets a data flow node that flows to the RHS of a def-node. */
405+
private DataFlow::LocalSourceNode defCand(TypeBackTracker t) {
406+
t.start() and
407+
exists(DataFlow::Node rhs |
408+
isDef(rhs) and
409+
result = rhs.getALocalSource()
410+
)
411+
or
412+
exists(TypeBackTracker t2 | result = defCand(t2).backtrack(t2, t))
413+
}
414+
415+
/** Gets a data flow node that flows to the RHS of a def-node. */
416+
private DataFlow::LocalSourceNode defCand() { result = defCand(TypeBackTracker::end()) }
417+
418+
/**
419+
* Holds if there should be a `lbl`-edge from the given call to an argument.
420+
*/
421+
pragma[nomagic]
422+
private predicate argumentStep(string lbl, DataFlow::CallNode call, DataFlow::Node argument) {
423+
exists(int n |
424+
argument = call.getArgument(n) and
425+
lbl = Label::parameter(n)
426+
)
427+
or
428+
exists(string name |
429+
argument = call.getKeywordArgument(name) and
430+
lbl = Label::keywordParameter(name)
431+
)
432+
or
433+
argument = call.getBlock() and
434+
lbl = Label::blockParameter()
435+
}
436+
437+
/**
438+
* Holds if there should be a `lbl`-edge from the given callable to a parameter.
439+
*/
440+
pragma[nomagic]
441+
private predicate parameterStep(string lbl, DataFlow::Node callable, DataFlow::Node paramNode) {
442+
exists(Parameter param |
443+
paramNode.asParameter() = param and
444+
callable.asExpr().getExpr().(Callable).getAParameter() = param and
445+
lbl = getLabelFromParameter(param)
446+
)
447+
}
448+
449+
private string getLabelFromParameter(Parameter param) {
450+
result = Label::keywordParameter(param.(KeywordParameter).getName())
451+
or
452+
param instanceof BlockParameter and
453+
result = Label::blockParameter()
454+
or
455+
// TODO: the index can be offset by preceding non-positional parameters; translate correctly
456+
(
457+
param instanceof SimpleParameter
458+
or
459+
param instanceof OptionalParameter
460+
) and
461+
result = Label::parameter(param.getPosition())
462+
}
463+
356464
/**
357465
* Gets a data-flow node to which `src`, which is a use of an API-graph node, flows.
358466
*
@@ -381,6 +489,21 @@ module API {
381489
result = trackUseNode(src, TypeTracker::end())
382490
}
383491

492+
/** Gets a data flow node reaching the RHS of the given def node. */
493+
private DataFlow::LocalSourceNode trackDefNode(DataFlow::Node rhs, TypeBackTracker t) {
494+
t.start() and
495+
isDef(rhs) and
496+
result = rhs.getALocalSource()
497+
or
498+
exists(TypeBackTracker t2 | result = trackDefNode(rhs, t2).backtrack(t2, t))
499+
}
500+
501+
/** Gets a data flow node reaching the RHS of the given def node. */
502+
cached
503+
DataFlow::LocalSourceNode trackDefNode(DataFlow::Node rhs) {
504+
result = trackDefNode(rhs, TypeBackTracker::end())
505+
}
506+
384507
/**
385508
* Holds if there is an edge from `pred` to `succ` in the API graph that is labeled with `lbl`.
386509
*/
@@ -408,6 +531,17 @@ module API {
408531
c.getSuperclassExpr() = a.asExpr().getExpr() and
409532
lbl = Label::subclass()
410533
)
534+
or
535+
exists(DataFlow::Node use, DataFlow::Node base, DataFlow::Node rhs |
536+
pred = MkUse(use) and
537+
trackUseNode(use).flowsTo(base) and
538+
argumentStep(lbl, base, rhs) and
539+
succ = MkDef(rhs)
540+
or
541+
pred = MkDef(rhs) and
542+
parameterStep(lbl, trackDefNode(rhs), use) and
543+
succ = MkUse(use)
544+
)
411545
}
412546

413547
/**
@@ -436,4 +570,21 @@ private module Label {
436570
string return(string m) { result = "getReturn(\"" + m + "\")" }
437571

438572
string subclass() { result = "getASubclass()" }
573+
574+
/** Gets the label representing the given keword argument/parameter. */
575+
bindingset[name]
576+
bindingset[result]
577+
string keywordParameter(string name) { result = "getKeywordParameter(\"" + name + "\")" }
578+
579+
/** Gets the label representing the `n`th positional argument/parameter. */
580+
bindingset[n]
581+
string parameter(int n) { result = parameterByStr(n.toString()) }
582+
583+
/** Gets the label representing the `n`th positional argument/parameter. */
584+
bindingset[n]
585+
bindingset[result]
586+
string parameterByStr(string n) { result = "getParameter(" + n + ")" }
587+
588+
/** Gets the label representing the block argument/parameter. */
589+
string blockParameter() { result = "getBlock()" }
439590
}

ruby/ql/test/library-tests/dataflow/api-graphs/test1.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
Const = [1, 2, 3] #$ use=getMember("Array").getReturn("[]")
1616
Const.each do |c| #$ use=getMember("Const").getReturn("each")
17-
puts c
17+
puts c #$ use=getMember("Const").getReturn("each").getBlock().getParameter(0)
1818
end
1919

2020
foo = Foo #$ use=getMember("Foo")
@@ -58,5 +58,5 @@ class C4 < C2 #$ use=getMember("C2")
5858
M1::C1.m #$ use=getMember("M1").getMember("C1").getReturn("m")
5959
M2::C3.m #$ use=getMember("M2").getMember("C3").getReturn("m") use=getMember("M1").getMember("C1").getASubclass().getReturn("m")
6060

61-
M1::C1.new.m #$ use=getMember("M1").getMember("C1").instance.getReturn("m")
62-
M2::C3.new.m #$ use=getMember("M2").getMember("C3").instance.getReturn("m")
61+
M1::C1.new.m #$ use=getMember("M1").getMember("C1").getReturn("new").getReturn("m")
62+
M2::C3.new.m #$ use=getMember("M2").getMember("C3").getReturn("new").getReturn("m")

0 commit comments

Comments
 (0)