mirror of
https://github.com/github/codeql.git
synced 2026-05-02 12:15:17 +02:00
Swift: add failing inline expectation test based on closure AST tests.
This commit is contained in:
committed by
Mathias Vorreiter Pedersen
parent
ba67217b44
commit
050b8e682f
117
swift/ql/test/TestUtilities/InlineFlowTest.qll
Normal file
117
swift/ql/test/TestUtilities/InlineFlowTest.qll
Normal file
@@ -0,0 +1,117 @@
|
||||
/**
|
||||
* Provides a simple base test for flow-related tests using inline expectations.
|
||||
*
|
||||
* Example for a test.ql:
|
||||
* ```ql
|
||||
* import swift
|
||||
* import TestUtilities.InlineFlowTest
|
||||
* import DefaultFlowTest
|
||||
* import PathGraph
|
||||
*
|
||||
* from PathNode source, PathNode sink
|
||||
* where flowPath(source, sink)
|
||||
* select sink, source, sink, "$@", source, source.toString()
|
||||
* ```
|
||||
*
|
||||
* To declare expectations, you can use the $hasTaintFlow or $hasValueFlow comments within the test source files.
|
||||
* Example of the corresponding test file, e.g. Test.java
|
||||
* ```swift
|
||||
* func source() -> Any { return nil }
|
||||
* func taint() -> Any { return nil }
|
||||
* func sink(_ o: Any) { }
|
||||
*
|
||||
* func test() {
|
||||
* let s = source()
|
||||
* sink(s) // $ hasValueFlow
|
||||
* let t = "foo" + taint()
|
||||
* sink(t); // $ hasTaintFlow
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* If you are only interested in value flow, then instead of importing `DefaultFlowTest`, you can import
|
||||
* `ValueFlowTest<DefaultFlowConfig>`. Similarly, if you are only interested in taint flow, then instead of
|
||||
* importing `DefaultFlowTest`, you can import `TaintFlowTest<DefaultFlowConfig>`. In both cases
|
||||
* `DefaultFlowConfig` can be replaced by another implementation of `DataFlow::ConfigSig`.
|
||||
*
|
||||
* If you need more fine-grained tuning, consider implementing a test using `InlineExpectationsTest`.
|
||||
*/
|
||||
|
||||
import codeql.swift.dataflow.DataFlow
|
||||
import codeql.swift.dataflow.ExternalFlow
|
||||
import codeql.swift.dataflow.TaintTracking
|
||||
import TestUtilities.InlineExpectationsTest
|
||||
|
||||
private predicate defaultSource(DataFlow::Node source) {
|
||||
source.asExpr().(MethodCallExpr).getStaticTarget().getName() = ["source", "taint"]
|
||||
}
|
||||
|
||||
private predicate defaultSink(DataFlow::Node sink) {
|
||||
exists(MethodCallExpr ma | ma.getStaticTarget().hasName("sink") |
|
||||
sink.asExpr() = ma.getAnArgument().getExpr()
|
||||
)
|
||||
}
|
||||
|
||||
module DefaultFlowConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { defaultSource(source) }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { defaultSink(sink) }
|
||||
|
||||
int fieldFlowBranchLimit() { result = 1000 }
|
||||
}
|
||||
|
||||
private module NoFlowConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { none() }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { none() }
|
||||
}
|
||||
|
||||
private string getSourceArgString(DataFlow::Node src) {
|
||||
defaultSource(src) and
|
||||
src.asExpr().(MethodCallExpr).getAnArgument().getExpr().(StringLiteralExpr).getValue() = result
|
||||
}
|
||||
|
||||
module FlowTest<DataFlow::ConfigSig ValueFlowConfig, DataFlow::ConfigSig TaintFlowConfig> {
|
||||
module ValueFlow = DataFlow::Global<ValueFlowConfig>;
|
||||
|
||||
module TaintFlow = TaintTracking::Global<TaintFlowConfig>;
|
||||
|
||||
private module InlineTest implements TestSig {
|
||||
string getARelevantTag() { result = ["hasValueFlow", "hasTaintFlow"] }
|
||||
|
||||
predicate hasActualResult(Location location, string element, string tag, string value) {
|
||||
tag = "hasValueFlow" and
|
||||
exists(DataFlow::Node src, DataFlow::Node sink | ValueFlow::flow(src, sink) |
|
||||
sink.getLocation() = location and
|
||||
element = sink.toString() and
|
||||
if exists(getSourceArgString(src)) then value = getSourceArgString(src) else value = ""
|
||||
)
|
||||
or
|
||||
tag = "hasTaintFlow" and
|
||||
exists(DataFlow::Node src, DataFlow::Node sink |
|
||||
TaintFlow::flow(src, sink) and not ValueFlow::flow(src, sink)
|
||||
|
|
||||
sink.getLocation() = location and
|
||||
element = sink.toString() and
|
||||
if exists(getSourceArgString(src)) then value = getSourceArgString(src) else value = ""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
import MakeTest<InlineTest>
|
||||
import DataFlow::MergePathGraph<ValueFlow::PathNode, TaintFlow::PathNode, ValueFlow::PathGraph, TaintFlow::PathGraph>
|
||||
|
||||
predicate flowPath(PathNode source, PathNode sink) {
|
||||
ValueFlow::flowPath(source.asPathNode1(), sink.asPathNode1()) or
|
||||
TaintFlow::flowPath(source.asPathNode2(), sink.asPathNode2())
|
||||
}
|
||||
}
|
||||
|
||||
module DefaultFlowTest = FlowTest<DefaultFlowConfig, DefaultFlowConfig>;
|
||||
|
||||
module ValueFlowTest<DataFlow::ConfigSig ValueFlowConfig> {
|
||||
import FlowTest<ValueFlowConfig, NoFlowConfig>
|
||||
}
|
||||
|
||||
module TaintFlowTest<DataFlow::ConfigSig TaintFlowConfig> {
|
||||
import FlowTest<NoFlowConfig, TaintFlowConfig>
|
||||
}
|
||||
155
swift/ql/test/library-tests/dataflow/capture/closures.swift
Normal file
155
swift/ql/test/library-tests/dataflow/capture/closures.swift
Normal file
@@ -0,0 +1,155 @@
|
||||
func sink<T>(_ value: T) { print("sink:", value) }
|
||||
func source<T>(_ label: String, _ value: T) -> T { return value }
|
||||
func taint<T>(_ label: String, _ value: T) -> T { return value }
|
||||
|
||||
func hello() -> String {
|
||||
let value = "Hello world!"
|
||||
return source("hello", value)
|
||||
}
|
||||
|
||||
func captureList() {
|
||||
let y: Int = source("captureList", 123);
|
||||
{ [x = hello()] () in
|
||||
sink(x) // $ MISSING: hasValueFlow=hello
|
||||
sink(y) // $ MISSING: hasValueFlow=captureList
|
||||
}()
|
||||
}
|
||||
|
||||
var escape: (() -> Int)? = nil
|
||||
|
||||
func setEscape() {
|
||||
var x = source("setEscape", 0)
|
||||
escape = {
|
||||
sink(x) // $ MISSING: hasValueFlow=setEscape
|
||||
return x + 1
|
||||
}
|
||||
}
|
||||
|
||||
func callEscape() {
|
||||
setEscape()
|
||||
sink(escape?()) // $ MISSING: hasTaintFlow=setEscape
|
||||
}
|
||||
|
||||
func logical() -> Bool {
|
||||
let f: ((Int) -> Int)? = { x in x + 1 }
|
||||
let x: Int? = source("logical", 42)
|
||||
return f != nil
|
||||
&& (x != nil
|
||||
&& f!(x!) == 43) // $ MISSING: hasValueFlow=logical
|
||||
}
|
||||
|
||||
func asyncTest() {
|
||||
func withCallback(_ callback: @escaping (Int) async -> Int) {
|
||||
@Sendable
|
||||
func wrapper(_ x: Int) async -> Int {
|
||||
return await callback(x + 1) // $ MISSING: hasValueFlow=asyncTest
|
||||
}
|
||||
Task {
|
||||
print("asyncTest():", await wrapper(source("asyncTest", 40)))
|
||||
}
|
||||
}
|
||||
withCallback { x in
|
||||
x + 1 // $ MISSING: hasTaintFlow=asyncTest
|
||||
}
|
||||
}
|
||||
|
||||
func foo() -> Int {
|
||||
var x = 1
|
||||
let f = { y in x += y }
|
||||
x = source("foo", 41)
|
||||
let r = { x }
|
||||
sink(r()) // $ MISSING: hasValueFlow=foo
|
||||
f(1)
|
||||
return r() // $ MISSING: hasTaintFlow=foo
|
||||
}
|
||||
|
||||
func bar() -> () -> Int {
|
||||
var x = 1
|
||||
let f = { y in x += y }
|
||||
x = source("bar", 41)
|
||||
let r = { x }
|
||||
f(1)
|
||||
return r // constantly 42
|
||||
}
|
||||
|
||||
var g: ((Int) -> Void)? = nil
|
||||
func baz() -> () -> Int {
|
||||
var x = 1
|
||||
g = { y in x += y }
|
||||
x = source("baz", 41)
|
||||
let r = { x }
|
||||
g!(1)
|
||||
return r
|
||||
}
|
||||
|
||||
func sharedCapture() -> Int {
|
||||
let (incrX, getX) = {
|
||||
var x = source("sharedCapture", 0)
|
||||
return ({ x += 1 }, { x })
|
||||
}()
|
||||
|
||||
let doubleIncrX = {
|
||||
incrX()
|
||||
incrX()
|
||||
}
|
||||
|
||||
sink(getX()) // $ MISSING: hasValueFlow=sharedCapture
|
||||
doubleIncrX()
|
||||
sink(getX()) // $ MISSING: hasTaintFlow=sharedCapture
|
||||
doubleIncrX()
|
||||
return getX()
|
||||
}
|
||||
|
||||
func sharedCaptureMultipleWriters() {
|
||||
var x = 123
|
||||
|
||||
let callSink1 = { sink(x) } // $ MISSING: hasValueFlow=setter1
|
||||
let callSink2 = { sink(x) } // $ MISSING: hasValueFlow=setter2
|
||||
|
||||
let makeSetter = { y in
|
||||
let setter = { x = y }
|
||||
return setter
|
||||
}
|
||||
|
||||
let setter1 = makeSetter(source("setter1", 1))
|
||||
let setter2 = makeSetter(source("setter2", 2))
|
||||
|
||||
setter1()
|
||||
callSink1()
|
||||
|
||||
setter2()
|
||||
callSink2()
|
||||
}
|
||||
|
||||
|
||||
func main() {
|
||||
print("captureList():")
|
||||
captureList() // Hello world! 123
|
||||
|
||||
print("callEscape():")
|
||||
callEscape() // 1
|
||||
|
||||
print("logical():", logical()) // true
|
||||
|
||||
print("asyncTest():")
|
||||
asyncTest() // 42
|
||||
|
||||
print("foo():", foo()) // 42
|
||||
|
||||
let a = bar()
|
||||
let b = baz()
|
||||
|
||||
print("bar():", a(), a()) // $ MISSING: hasTaintFlow=bar
|
||||
|
||||
print("baz():", b(), b()) // $ MISSING: hasTaintFlow=baz
|
||||
|
||||
g!(1)
|
||||
print("g!(1):", b(), b()) // $ MISSING: hasTaintFlow=baz
|
||||
|
||||
print("sharedCapture():", sharedCapture()) // 4
|
||||
|
||||
print("sharedCaptureMultipleWriters():")
|
||||
sharedCaptureMultipleWriters() // 42, -1
|
||||
}
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,2 @@
|
||||
failures
|
||||
testFailures
|
||||
@@ -0,0 +1,2 @@
|
||||
import TestUtilities.InlineFlowTest
|
||||
import DefaultFlowTest
|
||||
Reference in New Issue
Block a user