Skip to content

Commit 609a4cf

Browse files
committed
Python: validate tests in datamodel.py
And adopt argument passing tests as well. turns out that `C.staticmethod.__func__` doesn't actually work :O
1 parent 39081e9 commit 609a4cf

File tree

2 files changed

+62
-27
lines changed

2 files changed

+62
-27
lines changed

python/ql/test/experimental/dataflow/coverage/datamodel.py

Lines changed: 61 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,30 @@
88
# Intended sources should be the variable `SOURCE` and intended sinks should be
99
# arguments to the function `SINK` (see python/ql/test/experimental/dataflow/testConfig.qll).
1010

11+
import sys
12+
import os
13+
import functools
14+
15+
sys.path.append(os.path.dirname(os.path.dirname((__file__))))
16+
from testlib import expects
17+
1118
# These are defined so that we can evaluate the test code.
1219
NONSOURCE = "not a source"
1320
SOURCE = "source"
1421

22+
arg1 = "source1"
23+
arg2 = "source2"
24+
arg3 = "source3"
25+
arg4 = "source4"
26+
arg5 = "source5"
27+
arg6 = "source6"
28+
arg7 = "source7"
29+
1530
def is_source(x):
1631
return x == "source" or x == b"source" or x == 42 or x == 42.0 or x == 42j
1732

18-
def SINK(x):
19-
if is_source(x):
33+
def SINK(x, expected=SOURCE):
34+
if is_source(x) or x == expected:
2035
print("OK")
2136
else:
2237
print("Unexpected flow", x)
@@ -27,6 +42,14 @@ def SINK_F(x):
2742
else:
2843
print("OK")
2944

45+
SINK1 = functools.partial(SINK, expected=arg1)
46+
SINK2 = functools.partial(SINK, expected=arg2)
47+
SINK3 = functools.partial(SINK, expected=arg3)
48+
SINK4 = functools.partial(SINK, expected=arg4)
49+
SINK5 = functools.partial(SINK, expected=arg5)
50+
SINK6 = functools.partial(SINK, expected=arg6)
51+
SINK7 = functools.partial(SINK, expected=arg7)
52+
3053
# Callable types
3154
# These are the types to which the function call operation (see section Calls) can be applied:
3255

@@ -41,17 +64,19 @@ def f(a, b):
4164
# An instance method object combines a class, a class instance and any callable object (normally a user-defined function).
4265
class C(object):
4366

44-
def method(self, x, cls):
45-
assert cls is self.__class__
46-
return x
67+
def method(self, x, y):
68+
SINK1(x)
69+
SINK2(y)
4770

4871
@classmethod
49-
def classmethod(cls, x):
50-
return x
72+
def classmethod(cls, x, y):
73+
SINK1(x)
74+
SINK2(y)
5175

5276
@staticmethod
53-
def staticmethod(x):
54-
return x
77+
def staticmethod(x, y):
78+
SINK1(x)
79+
SINK2(y)
5580

5681
def gen(self, x, count):
5782
n = count
@@ -64,30 +89,39 @@ async def coro(self, x):
6489

6590
c = C()
6691

67-
# When an instance method object is created by retrieving a user-defined function object from a class via one of its instances, its __self__ attribute is the instance, and the method object is said to be bound. The new method’s __func__ attribute is the original function object.
68-
func_obj = c.method.__func__
92+
@expects(6)
93+
def test_method_call():
94+
# When an instance method object is created by retrieving a user-defined function object from a class via one of its instances, its __self__ attribute is the instance, and the method object is said to be bound. The new method’s __func__ attribute is the original function object.
95+
func_obj = c.method.__func__
96+
97+
# When an instance method object is called, the underlying function (__func__) is called, inserting the class instance (__self__) in front of the argument list. For instance, when C is a class which contains a definition for a function f(), and x is an instance of C, calling x.f(1) is equivalent to calling C.f(x, 1).
98+
c.method(arg1, arg2) # $ func=C.method arg1 arg2
99+
C.method(c, arg1, arg2) # $ func=C.method arg1 arg2
100+
func_obj(c, arg1, arg2) # $ MISSING: func=C.method arg1 arg2
69101

70-
# When an instance method object is called, the underlying function (__func__) is called, inserting the class instance (__self__) in front of the argument list. For instance, when C is a class which contains a definition for a function f(), and x is an instance of C, calling x.f(1) is equivalent to calling C.f(x, 1).
71-
SINK(c.method(SOURCE, C)) #$ flow="SOURCE -> c.method(..)"
72-
SINK(C.method(c, SOURCE, C)) #$ flow="SOURCE -> C.method(..)"
73-
SINK(func_obj(c, SOURCE, C)) #$ MISSING: flow="SOURCE -> func_obj(..)"
74102

103+
@expects(6)
104+
def test_classmethod_call():
105+
# When an instance method object is created by retrieving a class method object from a class or instance, its __self__ attribute is the class itself, and its __func__ attribute is the function object underlying the class method.
106+
c_func_obj = C.classmethod.__func__
75107

76-
# When an instance method object is created by retrieving a class method object from a class or instance, its __self__ attribute is the class itself, and its __func__ attribute is the function object underlying the class method.
77-
c_func_obj = C.classmethod.__func__
108+
# When an instance method object is derived from a class method object, the “class instance” stored in __self__ will actually be the class itself, so that calling either x.f(1) or C.f(1) is equivalent to calling f(C,1) where f is the underlying function.
109+
c.classmethod(arg1, arg2) # $ func=C.classmethod arg1 arg2
110+
C.classmethod(arg1, arg2) # $ func=C.classmethod arg1 arg2
111+
c_func_obj(C, arg1, arg2) # $ MISSING: func=C.classmethod arg1 arg2
78112

79-
# When an instance method object is derived from a class method object, the “class instance” stored in __self__ will actually be the class itself, so that calling either x.f(1) or C.f(1) is equivalent to calling f(C,1) where f is the underlying function.
80-
SINK(c.classmethod(SOURCE)) #$ flow="SOURCE -> c.classmethod(..)"
81-
SINK(C.classmethod(SOURCE)) #$ flow="SOURCE -> C.classmethod(..)"
82-
SINK(c_func_obj(C, SOURCE)) #$ MISSING: flow="SOURCE -> c_func_obj(..)"
83113

84-
# When an instance method object is created by retrieving a class method object from a class or instance, its __self__ attribute is the class itself, and its __func__ attribute is the function object underlying the class method.
85-
s_func_obj = C.staticmethod.__func__
114+
@expects(5)
115+
def test_staticmethod_call():
116+
# staticmethods does not have a __func__ attribute
117+
try:
118+
C.staticmethod.__func__
119+
except AttributeError:
120+
print("OK")
86121

87-
# When an instance method object is derived from a class method object, the “class instance” stored in __self__ will actually be the class itself, so that calling either x.f(1) or C.f(1) is equivalent to calling f(C,1) where f is the underlying function.
88-
SINK(c.staticmethod(SOURCE)) #$ flow="SOURCE -> c.staticmethod(..)"
89-
SINK(C.staticmethod(SOURCE)) #$ flow="SOURCE -> C.staticmethod(..)"
90-
SINK(s_func_obj(SOURCE)) #$ MISSING: flow="SOURCE -> s_func_obj(..)"
122+
# When an instance method object is derived from a class method object, the “class instance” stored in __self__ will actually be the class itself, so that calling either x.f(1) or C.f(1) is equivalent to calling f(C,1) where f is the underlying function.
123+
c.staticmethod(arg1, arg2) # $ func=C.staticmethod arg1 arg2
124+
C.staticmethod(arg1, arg2) # $ func=C.staticmethod arg1 arg2
91125

92126

93127
# Generator functions

python/ql/test/experimental/dataflow/validTest.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ def check_tests_valid(testFile):
5555
check_tests_valid("coverage.classes")
5656
check_tests_valid("coverage.test")
5757
check_tests_valid("coverage.argumentPassing")
58+
check_tests_valid("coverage.datamodel")
5859
check_tests_valid("variable-capture.in")
5960
check_tests_valid("variable-capture.nonlocal")
6061
check_tests_valid("variable-capture.dict")

0 commit comments

Comments
 (0)