Skip to content

Commit 050b8e6

Browse files
d10cMathiasVP
authored andcommitted
Swift: add failing inline expectation test based on closure AST tests.
1 parent ba67217 commit 050b8e6

File tree

4 files changed

+276
-0
lines changed

4 files changed

+276
-0
lines changed
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
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 swift
7+
* import TestUtilities.InlineFlowTest
8+
* import DefaultFlowTest
9+
* import PathGraph
10+
*
11+
* from PathNode source, PathNode sink
12+
* where flowPath(source, sink)
13+
* select sink, source, sink, "$@", source, source.toString()
14+
* ```
15+
*
16+
* To declare expectations, you can use the $hasTaintFlow or $hasValueFlow comments within the test source files.
17+
* Example of the corresponding test file, e.g. Test.java
18+
* ```swift
19+
* func source() -> Any { return nil }
20+
* func taint() -> Any { return nil }
21+
* func sink(_ o: Any) { }
22+
*
23+
* func test() {
24+
* let s = source()
25+
* sink(s) // $ hasValueFlow
26+
* let t = "foo" + taint()
27+
* sink(t); // $ hasTaintFlow
28+
* }
29+
* ```
30+
*
31+
* If you are only interested in value flow, then instead of importing `DefaultFlowTest`, you can import
32+
* `ValueFlowTest<DefaultFlowConfig>`. Similarly, if you are only interested in taint flow, then instead of
33+
* importing `DefaultFlowTest`, you can import `TaintFlowTest<DefaultFlowConfig>`. In both cases
34+
* `DefaultFlowConfig` can be replaced by another implementation of `DataFlow::ConfigSig`.
35+
*
36+
* If you need more fine-grained tuning, consider implementing a test using `InlineExpectationsTest`.
37+
*/
38+
39+
import codeql.swift.dataflow.DataFlow
40+
import codeql.swift.dataflow.ExternalFlow
41+
import codeql.swift.dataflow.TaintTracking
42+
import TestUtilities.InlineExpectationsTest
43+
44+
private predicate defaultSource(DataFlow::Node source) {
45+
source.asExpr().(MethodCallExpr).getStaticTarget().getName() = ["source", "taint"]
46+
}
47+
48+
private predicate defaultSink(DataFlow::Node sink) {
49+
exists(MethodCallExpr ma | ma.getStaticTarget().hasName("sink") |
50+
sink.asExpr() = ma.getAnArgument().getExpr()
51+
)
52+
}
53+
54+
module DefaultFlowConfig implements DataFlow::ConfigSig {
55+
predicate isSource(DataFlow::Node source) { defaultSource(source) }
56+
57+
predicate isSink(DataFlow::Node sink) { defaultSink(sink) }
58+
59+
int fieldFlowBranchLimit() { result = 1000 }
60+
}
61+
62+
private module NoFlowConfig implements DataFlow::ConfigSig {
63+
predicate isSource(DataFlow::Node source) { none() }
64+
65+
predicate isSink(DataFlow::Node sink) { none() }
66+
}
67+
68+
private string getSourceArgString(DataFlow::Node src) {
69+
defaultSource(src) and
70+
src.asExpr().(MethodCallExpr).getAnArgument().getExpr().(StringLiteralExpr).getValue() = result
71+
}
72+
73+
module FlowTest<DataFlow::ConfigSig ValueFlowConfig, DataFlow::ConfigSig TaintFlowConfig> {
74+
module ValueFlow = DataFlow::Global<ValueFlowConfig>;
75+
76+
module TaintFlow = TaintTracking::Global<TaintFlowConfig>;
77+
78+
private module InlineTest implements TestSig {
79+
string getARelevantTag() { result = ["hasValueFlow", "hasTaintFlow"] }
80+
81+
predicate hasActualResult(Location location, string element, string tag, string value) {
82+
tag = "hasValueFlow" and
83+
exists(DataFlow::Node src, DataFlow::Node sink | ValueFlow::flow(src, sink) |
84+
sink.getLocation() = location and
85+
element = sink.toString() and
86+
if exists(getSourceArgString(src)) then value = getSourceArgString(src) else value = ""
87+
)
88+
or
89+
tag = "hasTaintFlow" and
90+
exists(DataFlow::Node src, DataFlow::Node sink |
91+
TaintFlow::flow(src, sink) and not ValueFlow::flow(src, sink)
92+
|
93+
sink.getLocation() = location and
94+
element = sink.toString() and
95+
if exists(getSourceArgString(src)) then value = getSourceArgString(src) else value = ""
96+
)
97+
}
98+
}
99+
100+
import MakeTest<InlineTest>
101+
import DataFlow::MergePathGraph<ValueFlow::PathNode, TaintFlow::PathNode, ValueFlow::PathGraph, TaintFlow::PathGraph>
102+
103+
predicate flowPath(PathNode source, PathNode sink) {
104+
ValueFlow::flowPath(source.asPathNode1(), sink.asPathNode1()) or
105+
TaintFlow::flowPath(source.asPathNode2(), sink.asPathNode2())
106+
}
107+
}
108+
109+
module DefaultFlowTest = FlowTest<DefaultFlowConfig, DefaultFlowConfig>;
110+
111+
module ValueFlowTest<DataFlow::ConfigSig ValueFlowConfig> {
112+
import FlowTest<ValueFlowConfig, NoFlowConfig>
113+
}
114+
115+
module TaintFlowTest<DataFlow::ConfigSig TaintFlowConfig> {
116+
import FlowTest<NoFlowConfig, TaintFlowConfig>
117+
}
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
func sink<T>(_ value: T) { print("sink:", value) }
2+
func source<T>(_ label: String, _ value: T) -> T { return value }
3+
func taint<T>(_ label: String, _ value: T) -> T { return value }
4+
5+
func hello() -> String {
6+
let value = "Hello world!"
7+
return source("hello", value)
8+
}
9+
10+
func captureList() {
11+
let y: Int = source("captureList", 123);
12+
{ [x = hello()] () in
13+
sink(x) // $ MISSING: hasValueFlow=hello
14+
sink(y) // $ MISSING: hasValueFlow=captureList
15+
}()
16+
}
17+
18+
var escape: (() -> Int)? = nil
19+
20+
func setEscape() {
21+
var x = source("setEscape", 0)
22+
escape = {
23+
sink(x) // $ MISSING: hasValueFlow=setEscape
24+
return x + 1
25+
}
26+
}
27+
28+
func callEscape() {
29+
setEscape()
30+
sink(escape?()) // $ MISSING: hasTaintFlow=setEscape
31+
}
32+
33+
func logical() -> Bool {
34+
let f: ((Int) -> Int)? = { x in x + 1 }
35+
let x: Int? = source("logical", 42)
36+
return f != nil
37+
&& (x != nil
38+
&& f!(x!) == 43) // $ MISSING: hasValueFlow=logical
39+
}
40+
41+
func asyncTest() {
42+
func withCallback(_ callback: @escaping (Int) async -> Int) {
43+
@Sendable
44+
func wrapper(_ x: Int) async -> Int {
45+
return await callback(x + 1) // $ MISSING: hasValueFlow=asyncTest
46+
}
47+
Task {
48+
print("asyncTest():", await wrapper(source("asyncTest", 40)))
49+
}
50+
}
51+
withCallback { x in
52+
x + 1 // $ MISSING: hasTaintFlow=asyncTest
53+
}
54+
}
55+
56+
func foo() -> Int {
57+
var x = 1
58+
let f = { y in x += y }
59+
x = source("foo", 41)
60+
let r = { x }
61+
sink(r()) // $ MISSING: hasValueFlow=foo
62+
f(1)
63+
return r() // $ MISSING: hasTaintFlow=foo
64+
}
65+
66+
func bar() -> () -> Int {
67+
var x = 1
68+
let f = { y in x += y }
69+
x = source("bar", 41)
70+
let r = { x }
71+
f(1)
72+
return r // constantly 42
73+
}
74+
75+
var g: ((Int) -> Void)? = nil
76+
func baz() -> () -> Int {
77+
var x = 1
78+
g = { y in x += y }
79+
x = source("baz", 41)
80+
let r = { x }
81+
g!(1)
82+
return r
83+
}
84+
85+
func sharedCapture() -> Int {
86+
let (incrX, getX) = {
87+
var x = source("sharedCapture", 0)
88+
return ({ x += 1 }, { x })
89+
}()
90+
91+
let doubleIncrX = {
92+
incrX()
93+
incrX()
94+
}
95+
96+
sink(getX()) // $ MISSING: hasValueFlow=sharedCapture
97+
doubleIncrX()
98+
sink(getX()) // $ MISSING: hasTaintFlow=sharedCapture
99+
doubleIncrX()
100+
return getX()
101+
}
102+
103+
func sharedCaptureMultipleWriters() {
104+
var x = 123
105+
106+
let callSink1 = { sink(x) } // $ MISSING: hasValueFlow=setter1
107+
let callSink2 = { sink(x) } // $ MISSING: hasValueFlow=setter2
108+
109+
let makeSetter = { y in
110+
let setter = { x = y }
111+
return setter
112+
}
113+
114+
let setter1 = makeSetter(source("setter1", 1))
115+
let setter2 = makeSetter(source("setter2", 2))
116+
117+
setter1()
118+
callSink1()
119+
120+
setter2()
121+
callSink2()
122+
}
123+
124+
125+
func main() {
126+
print("captureList():")
127+
captureList() // Hello world! 123
128+
129+
print("callEscape():")
130+
callEscape() // 1
131+
132+
print("logical():", logical()) // true
133+
134+
print("asyncTest():")
135+
asyncTest() // 42
136+
137+
print("foo():", foo()) // 42
138+
139+
let a = bar()
140+
let b = baz()
141+
142+
print("bar():", a(), a()) // $ MISSING: hasTaintFlow=bar
143+
144+
print("baz():", b(), b()) // $ MISSING: hasTaintFlow=baz
145+
146+
g!(1)
147+
print("g!(1):", b(), b()) // $ MISSING: hasTaintFlow=baz
148+
149+
print("sharedCapture():", sharedCapture()) // 4
150+
151+
print("sharedCaptureMultipleWriters():")
152+
sharedCaptureMultipleWriters() // 42, -1
153+
}
154+
155+
main()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
failures
2+
testFailures
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import TestUtilities.InlineFlowTest
2+
import DefaultFlowTest

0 commit comments

Comments
 (0)