1 | #!/usr/bin/env python3
|
2 | """
|
3 | pass_state_test.py: Tests for pass_state.py
|
4 | """
|
5 | from __future__ import print_function
|
6 |
|
7 | import unittest
|
8 |
|
9 | import pass_state # module under test
|
10 |
|
11 |
|
12 | class VirtualTest(unittest.TestCase):
|
13 |
|
14 | def testVirtual(self):
|
15 | """
|
16 | Example:
|
17 |
|
18 | class Base(object):
|
19 | def method(self): # we don't know if this is virtual yet
|
20 | pass
|
21 | def x(self):
|
22 | pass
|
23 |
|
24 | class Derived(Base):
|
25 | def method(self): # now it's virtual!
|
26 | pass
|
27 | def y(self):
|
28 | pass
|
29 | """
|
30 | v = pass_state.Virtual()
|
31 | v.OnMethod(('Base', ), 'method')
|
32 | v.OnMethod(('Base', ), 'x')
|
33 | v.OnSubclass(('Base', ), ('Derived', ))
|
34 | v.OnMethod(('Derived', ), 'method')
|
35 | v.OnMethod(('Derived', ), 'y')
|
36 |
|
37 | v.Calculate()
|
38 |
|
39 | print(v.virtuals)
|
40 | self.assertEqual(
|
41 | {
|
42 | (('Base', ), 'method'): None,
|
43 | (('Derived', ), 'method'): (('Base', ), 'method')
|
44 | }, v.virtuals)
|
45 |
|
46 | self.assertEqual(True, v.IsVirtual(('Base', ), 'method'))
|
47 | self.assertEqual(True, v.IsVirtual(('Derived', ), 'method'))
|
48 | self.assertEqual(False, v.IsVirtual(('Derived', ), 'y'))
|
49 |
|
50 | self.assertEqual(False, v.IsVirtual(('Klass', ), 'z'))
|
51 |
|
52 | self.assertEqual(True, v.HasVTable(('Base', )))
|
53 | self.assertEqual(True, v.HasVTable(('Derived', )))
|
54 |
|
55 | self.assertEqual(False, v.HasVTable(('Klass', )))
|
56 |
|
57 | def testNoInit(self):
|
58 | v = pass_state.Virtual()
|
59 | v.OnMethod(('Base', ), '__init__')
|
60 | v.OnSubclass(('Base', ), ('Derived', ))
|
61 | v.OnMethod(('Derived', ), '__init__')
|
62 | v.Calculate()
|
63 | self.assertEqual(False, v.HasVTable(('Base', )))
|
64 | self.assertEqual(False, v.HasVTable(('Derived', )))
|
65 |
|
66 | def testCanReorderFields(self):
|
67 | """
|
68 | class Base(object):
|
69 | def __init__(self):
|
70 | self.s = '' # pointer
|
71 | self.i = 42
|
72 |
|
73 | class Derived(Base):
|
74 | def __init__(self):
|
75 | Base.__init__()
|
76 | self.mylist = [] # type: List[str]
|
77 |
|
78 | Note: we can't reorder these, even though there are no virtual methods.
|
79 | """
|
80 | v = pass_state.Virtual()
|
81 | v.OnSubclass(('Base2', ), ('Derived2', ))
|
82 | v.Calculate()
|
83 |
|
84 | self.assertEqual(False, v.CanReorderFields(('Base2', )))
|
85 | self.assertEqual(False, v.CanReorderFields(('Derived2', )))
|
86 |
|
87 | self.assertEqual(True, v.CanReorderFields(('Klass2', )))
|
88 |
|
89 | def testBaseCollision(self):
|
90 | v = pass_state.Virtual()
|
91 | v.OnSubclass((
|
92 | 'moduleA',
|
93 | 'Base',
|
94 | ), (
|
95 | 'foo',
|
96 | 'Derived',
|
97 | ))
|
98 | with self.assertRaises(AssertionError):
|
99 | v.OnSubclass((
|
100 | 'moduleB',
|
101 | 'Base',
|
102 | ), (
|
103 | 'bar',
|
104 | 'Derived',
|
105 | ))
|
106 |
|
107 | def testSubclassMapping(self):
|
108 | v = pass_state.Virtual()
|
109 | v.OnMethod((
|
110 | 'moduleA',
|
111 | 'Base',
|
112 | ), 'frobnicate')
|
113 | v.OnSubclass((
|
114 | 'moduleA',
|
115 | 'Base',
|
116 | ), (
|
117 | 'foo',
|
118 | 'Derived',
|
119 | ))
|
120 | v.OnMethod((
|
121 | 'foo',
|
122 | 'Derived',
|
123 | ), 'frobnicate')
|
124 | v.OnSubclass((
|
125 | 'moduleA',
|
126 | 'Base',
|
127 | ), (
|
128 | 'bar',
|
129 | 'Derived',
|
130 | ))
|
131 | v.OnMethod((
|
132 | 'bar',
|
133 | 'Derived',
|
134 | ), 'frobnicate')
|
135 | v.Calculate()
|
136 | self.assertEqual((('moduleA', 'Base'), 'frobnicate'),
|
137 | v.virtuals[(('foo', 'Derived'), 'frobnicate')])
|
138 | self.assertEqual((('moduleA', 'Base'), 'frobnicate'),
|
139 | v.virtuals[(('bar', 'Derived'), 'frobnicate')])
|
140 | self.assertEqual(None, v.virtuals[(('moduleA', 'Base'), 'frobnicate')])
|
141 |
|
142 |
|
143 | class DummyFact(pass_state.Fact):
|
144 |
|
145 | def __init__(self, n: int) -> None:
|
146 | self.n = n
|
147 |
|
148 | def name(self):
|
149 | return 'dummy'
|
150 |
|
151 | def Generate(self, func: str, statement: int) -> str:
|
152 | return '{},{},{}'.format(func, statement, self.n)
|
153 |
|
154 |
|
155 | class ControlFlowGraphTest(unittest.TestCase):
|
156 |
|
157 | def testLinear(self):
|
158 | cfg = pass_state.ControlFlowGraph()
|
159 |
|
160 | a = cfg.AddStatement()
|
161 | b = cfg.AddStatement()
|
162 | c = cfg.AddStatement()
|
163 | d = cfg.AddStatement()
|
164 |
|
165 | cfg.AddFact(b, DummyFact(1))
|
166 | cfg.AddFact(d, DummyFact(99))
|
167 | cfg.AddFact(d, DummyFact(7))
|
168 |
|
169 | expected_edges = {
|
170 | (0, a),
|
171 | (a, b),
|
172 | (b, c),
|
173 | (c, d),
|
174 | }
|
175 | self.assertEqual(expected_edges, cfg.edges)
|
176 |
|
177 | self.assertEqual(1, len(cfg.facts[b]))
|
178 | self.assertEqual('foo,1,1', cfg.facts[b][0].Generate('foo', 1))
|
179 | self.assertEqual('dummy', cfg.facts[b][0].name())
|
180 | self.assertEqual(2, len(cfg.facts[d]))
|
181 | self.assertEqual('bar,1,99', cfg.facts[d][0].Generate('bar', 1))
|
182 | self.assertEqual('bar,2,7', cfg.facts[d][1].Generate('bar', 2))
|
183 |
|
184 | def testBranches(self):
|
185 | cfg = pass_state.ControlFlowGraph()
|
186 |
|
187 | main0 = cfg.AddStatement()
|
188 |
|
189 | # branch condition facts all get attached to this statement
|
190 | branch_point = cfg.AddStatement()
|
191 |
|
192 | # first statement in if block
|
193 | with pass_state.CfgBranchContext(cfg, branch_point) as branch_ctx:
|
194 | with branch_ctx.AddBranch() as arm0:
|
195 | arm0_a = cfg.AddStatement() # block statement 2
|
196 | arm0_b = cfg.AddStatement() # block statement 2
|
197 | arm0_c = cfg.AddStatement() # block statement 3
|
198 |
|
199 | # frist statement in elif block
|
200 | with branch_ctx.AddBranch() as arm1:
|
201 | arm1_a = cfg.AddStatement()
|
202 | arm1_b = cfg.AddStatement() # block statement 2
|
203 |
|
204 | # frist statement in else block
|
205 | with branch_ctx.AddBranch() as arm2:
|
206 | arm2_a = cfg.AddStatement()
|
207 | arm2_b = cfg.AddStatement() # block statement 2
|
208 |
|
209 | self.assertEqual(arm0_c, arm0.exit)
|
210 | self.assertEqual(arm1_b, arm1.exit)
|
211 | self.assertEqual(arm2_b, arm2.exit)
|
212 |
|
213 | join = cfg.AddStatement()
|
214 | end = cfg.AddStatement()
|
215 | """
|
216 | We expecte a graph like this.
|
217 |
|
218 | begin
|
219 | |
|
220 | main0
|
221 | |
|
222 | v
|
223 | branch_point
|
224 | / | \
|
225 | arm0_a arm1_a arm2_a
|
226 | | | |
|
227 | arm0_b arm1_b arm2_b
|
228 | | | |
|
229 | arm0_c | |
|
230 | | | /
|
231 | \ | /
|
232 | \ | /
|
233 | \ | /
|
234 | \ | /
|
235 | join
|
236 | |
|
237 | end
|
238 | """
|
239 | # yapf: disable
|
240 | expected_edges = {
|
241 | (0, main0),
|
242 | (main0, branch_point),
|
243 | (branch_point, arm0_a), (branch_point, arm1_a), (branch_point, arm2_a),
|
244 | (arm0_a, arm0_b), (arm0_b, arm0_c),
|
245 | (arm1_a, arm1_b),
|
246 | (arm2_a, arm2_b),
|
247 | (arm0_c, join), (arm1_b, join), (arm2_b, join),
|
248 | (join, end),
|
249 | }
|
250 | # yapf: enable
|
251 | self.assertEqual(expected_edges, cfg.edges)
|
252 |
|
253 | def testDeadends(self):
|
254 | """
|
255 | Make sure we don't create orphans in the presence of continue, return,
|
256 | raise, etc...
|
257 | """
|
258 |
|
259 | cfg = pass_state.ControlFlowGraph()
|
260 | with pass_state.CfgBranchContext(cfg,
|
261 | cfg.AddStatement()) as branch_ctx:
|
262 | with branch_ctx.AddBranch() as branchA: # if
|
263 | ret = cfg.AddStatement() # return
|
264 | cfg.AddDeadend(ret)
|
265 | """
|
266 | while ...:
|
267 | if ...:
|
268 | continue
|
269 | else:
|
270 | print(...)
|
271 |
|
272 | print(...)
|
273 | """
|
274 | with pass_state.CfgLoopContext(cfg) as loop:
|
275 | branch_point = cfg.AddStatement()
|
276 | with pass_state.CfgBranchContext(cfg, branch_point) as branch_ctx:
|
277 | with branch_ctx.AddBranch() as branchB: # if
|
278 | cont = cfg.AddStatement() # continue
|
279 | loop.AddContinue(cont)
|
280 |
|
281 | with branch_ctx.AddBranch() as branchC: # else
|
282 | innerC = cfg.AddStatement()
|
283 |
|
284 | end = cfg.AddStatement()
|
285 | # yapf: disable
|
286 | expected_edges = {
|
287 | (0, branchA.entry),
|
288 | (branchA.entry, ret),
|
289 | (branchA.entry, loop.entry),
|
290 | (loop.entry, branchB.entry),
|
291 | (branch_point, cont),
|
292 | (cont, loop.entry),
|
293 | (branch_point, innerC),
|
294 | (innerC, end),
|
295 | (innerC, loop.entry),
|
296 | }
|
297 | # yapf: enable
|
298 | self.assertEqual(expected_edges, cfg.edges)
|
299 |
|
300 | def testNedstedIf(self):
|
301 | """
|
302 | The mypy AST represents else-if as nested if-statements inside the else arm.
|
303 | """
|
304 | cfg = pass_state.ControlFlowGraph()
|
305 |
|
306 | outer_branch_point = cfg.AddStatement()
|
307 | with pass_state.CfgBranchContext(cfg,
|
308 | outer_branch_point) as branch_ctx:
|
309 | with branch_ctx.AddBranch() as branch0: # if
|
310 | branch0_a = cfg.AddStatement()
|
311 |
|
312 | with branch_ctx.AddBranch() as branch1: # else
|
313 | with branch1.AddBranch(cfg.AddStatement()) as branch2: # if
|
314 | branch2_a = cfg.AddStatement()
|
315 |
|
316 | branch1_a = cfg.AddStatement()
|
317 |
|
318 | end = cfg.AddStatement()
|
319 | """
|
320 | We expect a graph like this.
|
321 |
|
322 | begin
|
323 | |
|
324 | outer_branch_point +------
|
325 | | | \
|
326 | branch0_a | branch2.entry
|
327 | | | |
|
328 | | | branch2_a
|
329 | | | |
|
330 | | | /
|
331 | | | /
|
332 | | | /
|
333 | | branch1_a
|
334 | | /
|
335 | | /
|
336 | | /
|
337 | | /
|
338 | end _____/
|
339 | """
|
340 | # yapf: disable
|
341 | expected_edges = {
|
342 | (0, outer_branch_point),
|
343 | (outer_branch_point, branch0_a),
|
344 | (outer_branch_point, branch2.entry),
|
345 | (branch2.entry, branch2_a),
|
346 | (branch2_a, branch1_a),
|
347 | (branch0.exit, end),
|
348 | (branch1.exit, end),
|
349 | (branch2.exit, end),
|
350 | }
|
351 | # yapf: enable
|
352 | self.assertEqual(expected_edges, cfg.edges)
|
353 |
|
354 | def testLoops(self):
|
355 | cfg = pass_state.ControlFlowGraph()
|
356 |
|
357 | with pass_state.CfgLoopContext(cfg) as loopA:
|
358 | branch_point = cfg.AddStatement()
|
359 | with pass_state.CfgBranchContext(cfg, branch_point) as branch_ctx:
|
360 | with branch_ctx.AddBranch() as arm0:
|
361 | arm0_a = cfg.AddStatement()
|
362 | arm0_b = cfg.AddStatement()
|
363 |
|
364 | with branch_ctx.AddBranch() as arm1:
|
365 | arm1_a = cfg.AddStatement()
|
366 | arm1_b = cfg.AddStatement()
|
367 |
|
368 | self.assertEqual(arm0_b, arm0.exit)
|
369 | self.assertEqual(arm1_b, arm1.exit)
|
370 |
|
371 | with pass_state.CfgLoopContext(cfg) as loopB:
|
372 | innerB = cfg.AddStatement()
|
373 |
|
374 | self.assertEqual(innerB, loopB.exit)
|
375 |
|
376 | end = cfg.AddStatement()
|
377 | """
|
378 | We expecte a graph like this:.
|
379 |
|
380 | begin
|
381 | |
|
382 | loopA <------+
|
383 | | |
|
384 | v |
|
385 | branch_point |
|
386 | / \ |
|
387 | arm0_a arm2_a |
|
388 | | | |
|
389 | arm0_b arm2_b |
|
390 | \ / |
|
391 | \ / |
|
392 | loopB <-+ |
|
393 | | | |
|
394 | innerB -+---+
|
395 | |
|
396 | end
|
397 | """
|
398 | # yapf: disable
|
399 | expected_edges = {
|
400 | (0, loopA.entry),
|
401 | (loopA.entry, branch_point),
|
402 | (branch_point, arm0_a), (branch_point, arm1_a),
|
403 | (arm0_a, arm0_b),
|
404 | (arm1_a, arm1_b),
|
405 | (arm0_b, loopB.entry), (arm1_b, loopB.entry),
|
406 | (loopB.entry, innerB),
|
407 | (innerB, loopA.entry), (innerB, loopB.entry),
|
408 | (innerB, end),
|
409 | }
|
410 | # yapf: enable
|
411 | self.assertEqual(expected_edges, cfg.edges)
|
412 |
|
413 | def testLoops2(self):
|
414 | cfg = pass_state.ControlFlowGraph()
|
415 |
|
416 | with pass_state.CfgLoopContext(cfg) as loopA:
|
417 | with pass_state.CfgLoopContext(cfg) as loopB:
|
418 | innerB = cfg.AddStatement()
|
419 |
|
420 | innerA = cfg.AddStatement()
|
421 |
|
422 | end = cfg.AddStatement()
|
423 |
|
424 | # yapf: disable
|
425 | expected_edges = {
|
426 | (0, loopA.entry),
|
427 | (loopA.entry, loopB.entry),
|
428 | (loopB.entry, innerB),
|
429 | (innerB, innerA),
|
430 | (innerB, loopB.entry),
|
431 | (innerA, loopA.entry),
|
432 | (innerA, end),
|
433 | }
|
434 | # yapf: enable
|
435 | self.assertEqual(expected_edges, cfg.edges)
|
436 |
|
437 | def testDeepTry(self):
|
438 | """
|
439 | A code snippet like the following.
|
440 |
|
441 | 1 while i < n:
|
442 | 2 for prog in cases:
|
443 | 3 try:
|
444 | 4 result = f(prog)
|
445 | except ParseError as e:
|
446 | 5 num_exceptions += 1
|
447 | 6 continue
|
448 | 7 i += 1
|
449 |
|
450 | 8 mylib.MaybeCollect() # manual GC point
|
451 |
|
452 | 9 log('num_exceptions = %d', num_exceptions)
|
453 | """
|
454 | cfg = pass_state.ControlFlowGraph()
|
455 |
|
456 | with pass_state.CfgLoopContext(cfg) as loopA:
|
457 | with pass_state.CfgLoopContext(cfg) as loopB:
|
458 | with pass_state.CfgBlockContext(cfg) as try_block:
|
459 | try_s0 = cfg.AddStatement()
|
460 |
|
461 | with pass_state.CfgBlockContext(
|
462 | cfg, try_block.exit) as except_block:
|
463 | except_s0 = cfg.AddStatement()
|
464 | cont = cfg.AddStatement()
|
465 | loopB.AddContinue(cont)
|
466 |
|
467 | a_s0 = cfg.AddStatement()
|
468 | a_s1 = cfg.AddStatement()
|
469 |
|
470 | log_stmt = cfg.AddStatement()
|
471 | end = cfg.AddStatement()
|
472 |
|
473 | # yapf: disable
|
474 | expected_edges = {
|
475 | (0, loopA.entry),
|
476 | (loopA.entry, loopB.entry),
|
477 | (loopB.entry, try_block.entry),
|
478 | (try_block.entry, try_s0),
|
479 | (try_s0, except_s0),
|
480 | (try_s0, loopB.entry),
|
481 | (except_s0, cont),
|
482 | (cont, loopB.entry),
|
483 | (try_block.exit, a_s0),
|
484 | (a_s0, a_s1),
|
485 | (a_s1, loopA.entry),
|
486 | (a_s1, log_stmt),
|
487 | (log_stmt, end),
|
488 | }
|
489 | # yapf: enable
|
490 | self.assertEqual(expected_edges, cfg.edges)
|
491 |
|
492 | def testLoopWithDanglingBlocks(self):
|
493 | """
|
494 | for i in xrange(1000000):
|
495 | try:
|
496 | with ctx_DirStack(d, 'foo') as _:
|
497 | if i % 10000 == 0:
|
498 | raise MyError()
|
499 | pass
|
500 | except MyError:
|
501 | log('exception')
|
502 | """
|
503 | cfg = pass_state.ControlFlowGraph()
|
504 |
|
505 | with pass_state.CfgLoopContext(cfg) as loop:
|
506 | with pass_state.CfgBranchContext(cfg,
|
507 | cfg.AddStatement()) as try_ctx:
|
508 | with try_ctx.AddBranch() as try_block:
|
509 | with pass_state.CfgBlockContext(
|
510 | cfg, cfg.AddStatement()) as with_block:
|
511 | with pass_state.CfgBranchContext(
|
512 | cfg, cfg.AddStatement()) as if_ctx:
|
513 | with if_ctx.AddBranch() as if_block:
|
514 | s_raise = cfg.AddStatement()
|
515 | cfg.AddDeadend(s_raise)
|
516 |
|
517 | pass_stmt = cfg.AddStatement()
|
518 |
|
519 | with try_ctx.AddBranch(try_block.exit) as except_block:
|
520 | log_stmt = cfg.AddStatement()
|
521 |
|
522 | # yapf: disable
|
523 | expected_edges = {
|
524 | (0, loop.entry),
|
525 | (loop.entry, try_block.entry),
|
526 | (try_block.entry, with_block.entry),
|
527 | (with_block.entry, if_block.entry),
|
528 | (if_block.entry, s_raise),
|
529 | (if_block.entry, pass_stmt),
|
530 | (pass_stmt, loop.entry),
|
531 | (pass_stmt, log_stmt),
|
532 | (log_stmt, loop.entry),
|
533 | }
|
534 | # yapf: enable
|
535 | self.assertEqual(expected_edges, cfg.edges)
|
536 |
|
537 | def testLoopBreak(self):
|
538 | """
|
539 | while ...:
|
540 | if ...:
|
541 | break
|
542 |
|
543 | else:
|
544 | try:
|
545 | pass
|
546 |
|
547 | except ...:
|
548 | break
|
549 |
|
550 | pass
|
551 |
|
552 | pass
|
553 | """
|
554 | cfg = pass_state.ControlFlowGraph()
|
555 |
|
556 | with pass_state.CfgLoopContext(cfg) as loop:
|
557 | with pass_state.CfgBranchContext(cfg,
|
558 | cfg.AddStatement()) as if_ctx:
|
559 | with if_ctx.AddBranch() as if_block:
|
560 | break1 = cfg.AddStatement()
|
561 | loop.AddBreak(break1)
|
562 |
|
563 | with if_ctx.AddBranch() as else_block:
|
564 | with pass_state.CfgBranchContext(
|
565 | cfg, cfg.AddStatement()) as try_ctx:
|
566 | with try_ctx.AddBranch() as try_block:
|
567 | pass1 = cfg.AddStatement()
|
568 |
|
569 | with try_ctx.AddBranch(try_block.exit) as except_block:
|
570 | break2 = cfg.AddStatement()
|
571 | loop.AddBreak(break2)
|
572 |
|
573 | pass2 = cfg.AddStatement()
|
574 |
|
575 | pass3 = cfg.AddStatement()
|
576 |
|
577 | # yapf: disable
|
578 | expected_edges = {
|
579 | (0, loop.entry),
|
580 | (loop.entry, if_block.entry),
|
581 | (if_block.entry, break1),
|
582 | (if_block.entry, try_block.entry),
|
583 | (try_block.entry, pass1),
|
584 | (pass1, break2),
|
585 | (pass1, pass2),
|
586 | (pass2, loop.entry),
|
587 | (pass2, pass3),
|
588 | (break1, pass3),
|
589 | (break2, pass3),
|
590 | }
|
591 | # yapf: enable
|
592 | self.assertEqual(expected_edges, cfg.edges)
|
593 |
|
594 |
|
595 | if __name__ == '__main__':
|
596 | unittest.main()
|