python: dataflow for match

- also update `validTest.py`, but commented out for now
  otherwise CI will fail until we force it to run with Python 3.10
- added debug utility for dataflow (`dataflowTestPaths.ql`)
This commit is contained in:
Rasmus Lerchedahl Petersen
2022-01-18 14:55:08 +01:00
parent bb210f4172
commit 36e18d5d80
8 changed files with 527 additions and 6 deletions

View File

@@ -0,0 +1,13 @@
import python
import experimental.dataflow.TestUtil.FlowTest
import experimental.dataflow.testConfig
class DataFlowTest extends FlowTest {
DataFlowTest() { this = "DataFlowTest" }
override string flowTag() { result = "flow" }
override predicate relevantFlow(DataFlow::Node source, DataFlow::Node sink) {
exists(TestConfiguration cfg | cfg.hasFlow(source, sink))
}
}

View File

@@ -0,0 +1,152 @@
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname((__file__))))
from testlib import *
# These are defined so that we can evaluate the test code.
NONSOURCE = "not a source"
SOURCE = "source"
def is_source(x):
return x == "source" or x == b"source" or x == 42 or x == 42.0 or x == 42j
def SINK(x):
if is_source(x):
print("OK")
else:
print("Unexpected flow", x)
def SINK_F(x):
if is_source(x):
print("Unexpected flow", x)
else:
print("OK")
def test_guard():
match SOURCE:
case x if SINK(x): #$ flow="SOURCE, l:-1 -> x"
pass
@expects(2)
def test_as_pattern():
match SOURCE:
case x as y:
SINK(x) #$ flow="SOURCE, l:-2 -> x"
SINK(y) #$ flow="SOURCE, l:-3 -> y"
def test_or_pattern():
match SOURCE:
# We cannot use NONSOURCE in place of "" below, since it would be seen as a variable.
case ("" as x) | x:
SINK(x) #$ flow="SOURCE, l:-3 -> x"
# No flow for literal pattern
def test_capture_pattern():
match SOURCE:
case x:
SINK(x) #$ flow="SOURCE, l:-2 -> x"
# No flow for wildcard pattern
class Unsafe:
VALUE = SOURCE
def test_value_pattern():
match SOURCE:
case Unsafe.VALUE as x:
SINK(x) #$ flow="SOURCE, l:-2 -> x" MISSING: flow="SOURCE, l:-5 -> x"
@expects(2)
def test_sequence_pattern_tuple():
match (NONSOURCE, SOURCE):
case (x, y):
SINK_F(x)
SINK(y) #$ flow="SOURCE, l:-3 -> y"
@expects(2)
def test_sequence_pattern_list():
match [NONSOURCE, SOURCE]:
case [x, y]:
SINK_F(x) #$ SPURIOUS: flow="SOURCE, l:-2 -> x"
SINK(y) #$ flow="SOURCE, l:-3 -> y"
# Sets are excluded from sequence patterns,
# see https://www.python.org/dev/peps/pep-0635/#sequence-patterns
@expects(2)
def test_star_pattern_tuple():
match (NONSOURCE, SOURCE):
case (x, *y):
SINK_F(x)
SINK(y[0]) #$ flow="SOURCE, l:-3 -> y[0]"
@expects(2)
def test_star_pattern_tuple_exclusion():
match (SOURCE, NONSOURCE):
case (x, *y):
SINK(x) #$ flow="SOURCE, l:-2 -> x"
SINK_F(y[0])
@expects(2)
def test_star_pattern_list():
match [NONSOURCE, SOURCE]:
case [x, *y]:
SINK_F(x) #$ SPURIOUS: flow="SOURCE, l:-2 -> x"
SINK(y[0]) #$ flow="SOURCE, l:-3 -> y[0]"
@expects(2)
def test_star_pattern_list_exclusion():
match [SOURCE, NONSOURCE]:
case [x, *y]:
SINK(x) #$ flow="SOURCE, l:-2 -> x"
SINK_F(y[0]) #$ SPURIOUS: flow="SOURCE, l:-3 -> y[0]"
@expects(2)
def test_mapping_pattern():
match {"a": NONSOURCE, "b": SOURCE}:
case {"a": x, "b": y}:
SINK_F(x)
SINK(y) #$ flow="SOURCE, l:-3 -> y"
# also tests the key value pattern
@expects(2)
def test_double_star_pattern():
match {"a": NONSOURCE, "b": SOURCE}:
case {"a": x, **y}:
SINK_F(x)
SINK(y["b"]) #$ flow="SOURCE, l:-3 -> y['b']"
@expects(2)
def test_double_star_pattern_exclusion():
match {"a": SOURCE, "b": NONSOURCE}:
case {"a": x, **y}:
SINK(x) #$ flow="SOURCE, l:-2 -> x"
SINK_F(y["b"])
try:
SINK_F(y["a"])
except KeyError:
pass
class Cell:
def __init__(self, value):
self.value = value
# also tests the keyword pattern
@expects(2)
def test_class_pattern():
bad_cell = Cell(SOURCE)
good_cell = Cell(NONSOURCE)
match bad_cell:
case Cell(value = x):
SINK(x) #$ flow="SOURCE, l:-5 -> x"
match good_cell:
case Cell(value = x):
SINK_F(x)

View File

@@ -57,6 +57,10 @@ if __name__ == "__main__":
check_tests_valid("variable-capture.nonlocal")
check_tests_valid("variable-capture.dict")
check_tests_valid("module-initialization.multiphase")
# The below will fail unless we use Python 3.10 or newer.
# check_tests_valid("match.test")
# The below fails when trying to import modules
# check_tests_valid("module-initialization.test")
# check_tests_valid("module-initialization.testOnce")