@@ -237,6 +237,104 @@ def set_attr(obj):
237237 SINK (y .attr ) # $ MISSING: flow
238238 SINK_F (z .attr ) # $ MISSING: flow
239239
240+ # ------------------------------------------------------------------------------
241+ # Content in class attribute
242+ # ------------------------------------------------------------------------------
243+
244+ class WithTuple :
245+ my_tuple = (SOURCE , NONSOURCE )
246+
247+ def test_inst (self ):
248+ SINK (self .my_tuple [0 ]) # $ MISSING: flow
249+ SINK_F (self .my_tuple [1 ])
250+
251+ @classmethod
252+ def test_cm (cls ):
253+ SINK (cls .my_tuple [0 ]) # $ MISSING: flow
254+ SINK_F (cls .my_tuple [1 ])
255+
256+
257+ @expects (2 * 4 ) # $ unresolved_call=expects(..) unresolved_call=expects(..)(..)
258+ def test_WithTuple ():
259+ SINK (WithTuple .my_tuple [0 ]) # $ MISSING: flow="SOURCE, l:-7 -> WithTuple.my_tuple[0]"
260+ SINK_F (WithTuple .my_tuple [1 ])
261+
262+ WithTuple .test_cm ()
263+
264+ inst = WithTuple ()
265+ inst .test_inst ()
266+
267+ SINK (inst .my_tuple [0 ]) # $ MISSING: flow="SOURCE, l:-18 -> inst.my_tuple[0]"
268+ SINK_F (inst .my_tuple [1 ])
269+
270+
271+ @expects (4 ) # $ unresolved_call=expects(..) unresolved_call=expects(..)(..)
272+ def test_inst_override ():
273+ inst = WithTuple ()
274+
275+ # setting attribute on instance does not override class attribute, it's only on the
276+ # instance!
277+ inst .my_tuple = (NONSOURCE , SOURCE )
278+
279+ SINK_F (inst .my_tuple [0 ])
280+ SINK (inst .my_tuple [1 ]) # $ flow="SOURCE, l:-3 -> inst.my_tuple[1]"
281+
282+ SINK (WithTuple .my_tuple [0 ]) # $ MISSING: flow="SOURCE, l:-27 -> WithTuple.my_tuple[0]"
283+ SINK_F (WithTuple .my_tuple [1 ])
284+
285+
286+ class WithTuple2 :
287+ my_tuple = (NONSOURCE ,)
288+
289+ def set_to_source ():
290+ WithTuple2 .my_tuple = (SOURCE ,)
291+
292+ @expects (4 ) # $ unresolved_call=expects(..) unresolved_call=expects(..)(..)
293+ def test_class_override ():
294+ inst = WithTuple2 ()
295+ SINK_F (WithTuple2 .my_tuple [0 ])
296+ SINK_F (inst .my_tuple [0 ])
297+
298+ set_to_source ()
299+
300+ SINK (WithTuple2 .my_tuple [0 ]) # $ MISSING: flow="SOURCE, l:-10 -> WithTuple2.my_tuple[0]"
301+ SINK (inst .my_tuple [0 ]) # $ MISSING: flow="SOURCE, l:-11 -> inst.my_tuple[0]"
302+
303+ # --------------------------------------
304+ # unique classes from functions
305+ # --------------------------------------
306+ def make_class ():
307+ # a fresh class is returned each time this function is called
308+ class C :
309+ my_tuple = (NONSOURCE ,)
310+ return C
311+
312+ @expects (8 ) # $ unresolved_call=expects(..) unresolved_call=expects(..)(..)
313+ def test_unique_class ():
314+ # This test highlights that if we use the _ClassExpr_ itself as the target/source
315+ # for jumpsteps, we will end up with spurious flow (that is, we will think that
316+ # x_cls and y_cls are the same, so by updating .my_tuple on x_cls we might propagate
317+ # that to y_cls as well -- it might not matter too much in reality, but certainly an
318+ # interesting corner case)
319+ x_cls = make_class ()
320+ y_cls = make_class ()
321+
322+ assert x_cls != y_cls
323+
324+ x_inst = x_cls ()
325+ y_inst = y_cls ()
326+
327+ SINK_F (x_cls .my_tuple [0 ])
328+ SINK_F (x_inst .my_tuple [0 ])
329+ SINK_F (y_cls .my_tuple [0 ])
330+ SINK_F (y_inst .my_tuple [0 ])
331+
332+ x_cls .my_tuple = (SOURCE ,)
333+ SINK (x_cls .my_tuple [0 ]) # $ flow="SOURCE, l:-1 -> x_cls.my_tuple[0]"
334+ SINK (x_inst .my_tuple [0 ]) # $ MISSING: flow="SOURCE, l:-2 -> x_inst.my_tuple[0]"
335+ SINK_F (y_cls .my_tuple [0 ])
336+ SINK_F (y_inst .my_tuple [0 ])
337+
240338# ------------------------------------------------------------------------------
241339# Crosstalk test -- using different function based on conditional
242340# ------------------------------------------------------------------------------
0 commit comments