Skip to content

Commit 5183290

Browse files
authored
Merge pull request github#7315 from hvitved/ruby/inline-flow-test
Ruby: Add `InlineFlowTest.qll`
2 parents b17a93e + 36569f9 commit 5183290

File tree

1 file changed

+121
-0
lines changed

1 file changed

+121
-0
lines changed
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/**
2+
* Provides a simple base test for flow-related tests using inline expectations.
3+
*
4+
* Example for a test.ql:
5+
* ```ql
6+
* import ruby
7+
* import TestUtilities.InlineFlowTest
8+
* import PathGraph
9+
*
10+
* from DataFlow::PathNode source, DataFlow::PathNode sink, DefaultValueFlowConf conf
11+
* where conf.hasFlowPath(source, sink)
12+
* select sink, source, sink, "$@", source, source.toString()
13+
* ```
14+
*
15+
* To declare expecations, you can use the $hasTaintFlow or $hasValueFlow comments within the test source files.
16+
* Example of the corresponding test file, e.g. test.rb
17+
* ```rb
18+
* s = source(1)
19+
* sink(s); // $ hasValueFlow=1
20+
* t = "foo" + taint(2);
21+
* sink(t); // $ hasTaintFlow=2
22+
* ```
23+
*
24+
* If you're not interested in a specific flow type, you can disable either value or taint flow expectations as follows:
25+
* ```ql
26+
* class HasFlowTest extends InlineFlowTest {
27+
* override DataFlow::Configuration getTaintFlowConfig() { none() }
28+
*
29+
* override DataFlow::Configuration getValueFlowConfig() { none() }
30+
* }
31+
* ```
32+
*
33+
* If you need more fine-grained tuning, consider implementing a test using `InlineExpectationsTest`.
34+
*/
35+
36+
import ruby
37+
import codeql.ruby.DataFlow
38+
import codeql.ruby.TaintTracking
39+
import TestUtilities.InlineExpectationsTest
40+
41+
private predicate defaultSource(DataFlow::Node src) {
42+
src.asExpr().getExpr().(MethodCall).getMethodName() = ["source", "taint"]
43+
}
44+
45+
private predicate defaultSink(DataFlow::Node sink) {
46+
exists(MethodCall mc | mc.getMethodName() = "sink" | sink.asExpr().getExpr() = mc.getAnArgument())
47+
}
48+
49+
class DefaultValueFlowConf extends DataFlow::Configuration {
50+
DefaultValueFlowConf() { this = "qltest:defaultValueFlowConf" }
51+
52+
override predicate isSource(DataFlow::Node n) { defaultSource(n) }
53+
54+
override predicate isSink(DataFlow::Node n) { defaultSink(n) }
55+
56+
override int fieldFlowBranchLimit() { result = 1000 }
57+
}
58+
59+
class DefaultTaintFlowConf extends TaintTracking::Configuration {
60+
DefaultTaintFlowConf() { this = "qltest:defaultTaintFlowConf" }
61+
62+
override predicate isSource(DataFlow::Node n) { defaultSource(n) }
63+
64+
override predicate isSink(DataFlow::Node n) { defaultSink(n) }
65+
66+
override int fieldFlowBranchLimit() { result = 1000 }
67+
}
68+
69+
private string getSourceArgString(DataFlow::Node src) {
70+
defaultSource(src) and
71+
src.asExpr().getExpr().(MethodCall).getAnArgument().getValueText() = result
72+
}
73+
74+
class InlineFlowTest extends InlineExpectationsTest {
75+
InlineFlowTest() { this = "HasFlowTest" }
76+
77+
override string getARelevantTag() { result = ["hasValueFlow", "hasTaintFlow"] }
78+
79+
override predicate hasActualResult(Location ___location, string element, string tag, string value) {
80+
tag = "hasValueFlow" and
81+
exists(DataFlow::Node src, DataFlow::Node sink | getValueFlowConfig().hasFlow(src, sink) |
82+
sink.getLocation() = ___location and
83+
element = sink.toString() and
84+
if exists(getSourceArgString(src)) then value = getSourceArgString(src) else value = ""
85+
)
86+
or
87+
tag = "hasTaintFlow" and
88+
exists(DataFlow::Node src, DataFlow::Node sink |
89+
getTaintFlowConfig().hasFlow(src, sink) and not getValueFlowConfig().hasFlow(src, sink)
90+
|
91+
sink.getLocation() = ___location and
92+
element = sink.toString() and
93+
if exists(getSourceArgString(src)) then value = getSourceArgString(src) else value = ""
94+
)
95+
}
96+
97+
DataFlow::Configuration getValueFlowConfig() { result = any(DefaultValueFlowConf config) }
98+
99+
DataFlow::Configuration getTaintFlowConfig() { result = any(DefaultTaintFlowConf config) }
100+
}
101+
102+
module PathGraph {
103+
private import DataFlow::PathGraph as PG
104+
105+
private class PathNode extends DataFlow::PathNode {
106+
PathNode() {
107+
this.getConfiguration() =
108+
[any(InlineFlowTest t).getValueFlowConfig(), any(InlineFlowTest t).getTaintFlowConfig()]
109+
}
110+
}
111+
112+
/** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */
113+
query predicate edges(PathNode a, PathNode b) { PG::edges(a, b) }
114+
115+
/** Holds if `n` is a node in the graph of data flow path explanations. */
116+
query predicate nodes(PathNode n, string key, string val) { PG::nodes(n, key, val) }
117+
118+
query predicate subpaths(PathNode arg, PathNode par, PathNode ret, PathNode out) {
119+
PG::subpaths(arg, par, ret, out)
120+
}
121+
}

0 commit comments

Comments
 (0)