13 from coverage import env |
13 from coverage import env |
14 from coverage.backward import range # pylint: disable=redefined-builtin |
14 from coverage.backward import range # pylint: disable=redefined-builtin |
15 from coverage.backward import bytes_to_ints, string_class |
15 from coverage.backward import bytes_to_ints, string_class |
16 from coverage.bytecode import CodeObjects |
16 from coverage.bytecode import CodeObjects |
17 from coverage.debug import short_stack |
17 from coverage.debug import short_stack |
18 from coverage.misc import contract, new_contract, nice_pair, join_regex |
18 from coverage.misc import contract, join_regex, new_contract, nice_pair, one_of |
19 from coverage.misc import CoverageException, NoSource, NotPython |
19 from coverage.misc import NoSource, NotPython, StopEverything |
20 from coverage.phystokens import compile_unicode, generate_tokens, neuter_encoding_declaration |
20 from coverage.phystokens import compile_unicode, generate_tokens, neuter_encoding_declaration |
21 |
21 |
22 |
22 |
23 class PythonParser(object): |
23 class PythonParser(object): |
24 """Parse code to find executable lines, excluded lines, etc. |
24 """Parse code to find executable lines, excluded lines, etc. |
135 empty = True |
135 empty = True |
136 first_on_line = True |
136 first_on_line = True |
137 |
137 |
138 tokgen = generate_tokens(self.text) |
138 tokgen = generate_tokens(self.text) |
139 for toktype, ttext, (slineno, _), (elineno, _), ltext in tokgen: |
139 for toktype, ttext, (slineno, _), (elineno, _), ltext in tokgen: |
140 if self.show_tokens: # pragma: not covered |
140 if self.show_tokens: # pragma: debugging |
141 print("%10s %5s %-20r %r" % ( |
141 print("%10s %5s %-20r %r" % ( |
142 tokenize.tok_name.get(toktype, toktype), |
142 tokenize.tok_name.get(toktype, toktype), |
143 nice_pair((slineno, elineno)), ttext, ltext |
143 nice_pair((slineno, elineno)), ttext, ltext |
144 )) |
144 )) |
145 if toktype == token.INDENT: |
145 if toktype == token.INDENT: |
368 ) |
368 ) |
369 ) |
369 ) |
370 |
370 |
371 # Alternative Python implementations don't always provide all the |
371 # Alternative Python implementations don't always provide all the |
372 # attributes on code objects that we need to do the analysis. |
372 # attributes on code objects that we need to do the analysis. |
373 for attr in ['co_lnotab', 'co_firstlineno', 'co_consts']: |
373 for attr in ['co_lnotab', 'co_firstlineno']: |
374 if not hasattr(self.code, attr): |
374 if not hasattr(self.code, attr): |
375 raise CoverageException( |
375 raise StopEverything( # pragma: only jython |
376 "This implementation of Python doesn't support code analysis.\n" |
376 "This implementation of Python doesn't support code analysis.\n" |
377 "Run coverage.py under CPython for this command." |
377 "Run coverage.py under another Python for this command." |
378 ) |
378 ) |
379 |
379 |
380 def child_parsers(self): |
380 def child_parsers(self): |
381 """Iterate over all the code objects nested within this one. |
381 """Iterate over all the code objects nested within this one. |
382 |
382 |
430 # AST analysis |
430 # AST analysis |
431 # |
431 # |
432 |
432 |
433 class LoopBlock(object): |
433 class LoopBlock(object): |
434 """A block on the block stack representing a `for` or `while` loop.""" |
434 """A block on the block stack representing a `for` or `while` loop.""" |
|
435 @contract(start=int) |
435 def __init__(self, start): |
436 def __init__(self, start): |
|
437 # The line number where the loop starts. |
436 self.start = start |
438 self.start = start |
|
439 # A set of ArcStarts, the arcs from break statements exiting this loop. |
437 self.break_exits = set() |
440 self.break_exits = set() |
438 |
441 |
439 |
442 |
440 class FunctionBlock(object): |
443 class FunctionBlock(object): |
441 """A block on the block stack representing a function definition.""" |
444 """A block on the block stack representing a function definition.""" |
|
445 @contract(start=int, name=str) |
442 def __init__(self, start, name): |
446 def __init__(self, start, name): |
|
447 # The line number where the function starts. |
443 self.start = start |
448 self.start = start |
|
449 # The name of the function. |
444 self.name = name |
450 self.name = name |
445 |
451 |
446 |
452 |
447 class TryBlock(object): |
453 class TryBlock(object): |
448 """A block on the block stack representing a `try` block.""" |
454 """A block on the block stack representing a `try` block.""" |
449 def __init__(self, handler_start=None, final_start=None): |
455 @contract(handler_start='int|None', final_start='int|None') |
|
456 def __init__(self, handler_start, final_start): |
|
457 # The line number of the first "except" handler, if any. |
450 self.handler_start = handler_start |
458 self.handler_start = handler_start |
|
459 # The line number of the "finally:" clause, if any. |
451 self.final_start = final_start |
460 self.final_start = final_start |
|
461 |
|
462 # The ArcStarts for breaks/continues/returns/raises inside the "try:" |
|
463 # that need to route through the "finally:" clause. |
452 self.break_from = set() |
464 self.break_from = set() |
453 self.continue_from = set() |
465 self.continue_from = set() |
454 self.return_from = set() |
466 self.return_from = set() |
455 self.raise_from = set() |
467 self.raise_from = set() |
456 |
468 |
457 |
469 |
458 class ArcStart(collections.namedtuple("Arc", "lineno, cause")): |
470 class ArcStart(collections.namedtuple("Arc", "lineno, cause")): |
459 """The information needed to start an arc. |
471 """The information needed to start an arc. |
460 |
472 |
461 `lineno` is the line number the arc starts from. `cause` is a fragment |
473 `lineno` is the line number the arc starts from. |
462 used as the startmsg for AstArcAnalyzer.missing_arc_fragments. |
474 |
|
475 `cause` is an English text fragment used as the `startmsg` for |
|
476 AstArcAnalyzer.missing_arc_fragments. It will be used to describe why an |
|
477 arc wasn't executed, so should fit well into a sentence of the form, |
|
478 "Line 17 didn't run because {cause}." The fragment can include "{lineno}" |
|
479 to have `lineno` interpolated into it. |
463 |
480 |
464 """ |
481 """ |
465 def __new__(cls, lineno, cause=None): |
482 def __new__(cls, lineno, cause=None): |
466 return super(ArcStart, cls).__new__(cls, lineno, cause) |
483 return super(ArcStart, cls).__new__(cls, lineno, cause) |
467 |
484 |
468 |
485 |
469 # Define contract words that PyContract doesn't have. |
486 # Define contract words that PyContract doesn't have. |
470 # ArcStarts is for a list or set of ArcStart's. |
487 # ArcStarts is for a list or set of ArcStart's. |
471 new_contract('ArcStarts', lambda seq: all(isinstance(x, ArcStart) for x in seq)) |
488 new_contract('ArcStarts', lambda seq: all(isinstance(x, ArcStart) for x in seq)) |
|
489 |
|
490 |
|
491 # Turn on AST dumps with an environment variable. |
|
492 AST_DUMP = bool(int(os.environ.get("COVERAGE_AST_DUMP", 0))) |
|
493 |
|
494 class NodeList(object): |
|
495 """A synthetic fictitious node, containing a sequence of nodes. |
|
496 |
|
497 This is used when collapsing optimized if-statements, to represent the |
|
498 unconditional execution of one of the clauses. |
|
499 |
|
500 """ |
|
501 def __init__(self, body): |
|
502 self.body = body |
|
503 self.lineno = body[0].lineno |
472 |
504 |
473 |
505 |
474 class AstArcAnalyzer(object): |
506 class AstArcAnalyzer(object): |
475 """Analyze source text with an AST to find executable code paths.""" |
507 """Analyze source text with an AST to find executable code paths.""" |
476 |
508 |
479 self.root_node = ast.parse(neuter_encoding_declaration(text)) |
511 self.root_node = ast.parse(neuter_encoding_declaration(text)) |
480 # TODO: I think this is happening in too many places. |
512 # TODO: I think this is happening in too many places. |
481 self.statements = set(multiline.get(l, l) for l in statements) |
513 self.statements = set(multiline.get(l, l) for l in statements) |
482 self.multiline = multiline |
514 self.multiline = multiline |
483 |
515 |
484 if int(os.environ.get("COVERAGE_ASTDUMP", 0)): # pragma: debugging |
516 if AST_DUMP: # pragma: debugging |
485 # Dump the AST so that failing tests have helpful output. |
517 # Dump the AST so that failing tests have helpful output. |
486 print("Statements: {}".format(self.statements)) |
518 print("Statements: {0}".format(self.statements)) |
487 print("Multiline map: {}".format(self.multiline)) |
519 print("Multiline map: {0}".format(self.multiline)) |
488 ast_dump(self.root_node) |
520 ast_dump(self.root_node) |
489 |
521 |
490 self.arcs = set() |
522 self.arcs = set() |
491 |
523 |
492 # A map from arc pairs to a pair of sentence fragments: (startmsg, endmsg). |
524 # A map from arc pairs to a list of pairs of sentence fragments: |
|
525 # { (start, end): [(startmsg, endmsg), ...], } |
|
526 # |
493 # For an arc from line 17, they should be usable like: |
527 # For an arc from line 17, they should be usable like: |
494 # "Line 17 {endmsg}, because {startmsg}" |
528 # "Line 17 {endmsg}, because {startmsg}" |
495 self.missing_arc_fragments = collections.defaultdict(list) |
529 self.missing_arc_fragments = collections.defaultdict(list) |
496 self.block_stack = [] |
530 self.block_stack = [] |
497 |
531 |
510 if code_object_handler is not None: |
544 if code_object_handler is not None: |
511 code_object_handler(node) |
545 code_object_handler(node) |
512 |
546 |
513 def add_arc(self, start, end, smsg=None, emsg=None): |
547 def add_arc(self, start, end, smsg=None, emsg=None): |
514 """Add an arc, including message fragments to use if it is missing.""" |
548 """Add an arc, including message fragments to use if it is missing.""" |
515 if self.debug: |
549 if self.debug: # pragma: debugging |
516 print("\nAdding arc: ({}, {}): {!r}, {!r}".format(start, end, smsg, emsg)) |
550 print("\nAdding arc: ({}, {}): {!r}, {!r}".format(start, end, smsg, emsg)) |
517 print(short_stack(limit=6)) |
551 print(short_stack(limit=6)) |
518 self.arcs.add((start, end)) |
552 self.arcs.add((start, end)) |
519 |
553 |
520 if smsg is not None or emsg is not None: |
554 if smsg is not None or emsg is not None: |
561 |
595 |
562 def _line__Module(self, node): |
596 def _line__Module(self, node): |
563 if node.body: |
597 if node.body: |
564 return self.line_for_node(node.body[0]) |
598 return self.line_for_node(node.body[0]) |
565 else: |
599 else: |
566 # Modules have no line number, they always start at 1. |
600 # Empty modules have no line number, they always start at 1. |
567 return 1 |
601 return 1 |
568 |
602 |
|
603 # The node types that just flow to the next node with no complications. |
569 OK_TO_DEFAULT = set([ |
604 OK_TO_DEFAULT = set([ |
570 "Assign", "Assert", "AugAssign", "Delete", "Exec", "Expr", "Global", |
605 "Assign", "Assert", "AugAssign", "Delete", "Exec", "Expr", "Global", |
571 "Import", "ImportFrom", "Nonlocal", "Pass", "Print", |
606 "Import", "ImportFrom", "Nonlocal", "Pass", "Print", |
572 ]) |
607 ]) |
573 |
608 |
574 @contract(returns='ArcStarts') |
609 @contract(returns='ArcStarts') |
575 def add_arcs(self, node): |
610 def add_arcs(self, node): |
576 """Add the arcs for `node`. |
611 """Add the arcs for `node`. |
577 |
612 |
578 Return a set of ArcStarts, exits from this node to the next. |
613 Return a set of ArcStarts, exits from this node to the next. Because a |
|
614 node represents an entire sub-tree (including its children), the exits |
|
615 from a node can be arbitrarily complex:: |
|
616 |
|
617 if something(1): |
|
618 if other(2): |
|
619 doit(3) |
|
620 else: |
|
621 doit(5) |
|
622 |
|
623 There are two exits from line 1: they start at line 3 and line 5. |
579 |
624 |
580 """ |
625 """ |
581 node_name = node.__class__.__name__ |
626 node_name = node.__class__.__name__ |
582 handler = getattr(self, "_handle__" + node_name, None) |
627 handler = getattr(self, "_handle__" + node_name, None) |
583 if handler is not None: |
628 if handler is not None: |
584 return handler(node) |
629 return handler(node) |
585 |
630 else: |
586 if 0: |
631 # No handler: either it's something that's ok to default (a simple |
587 node_name = node.__class__.__name__ |
632 # statement), or it's something we overlooked. Change this 0 to 1 |
588 if node_name not in self.OK_TO_DEFAULT: |
633 # to see if it's overlooked. |
589 print("*** Unhandled: {0}".format(node)) |
634 if 0: |
590 return set([ArcStart(self.line_for_node(node), cause=None)]) |
635 if node_name not in self.OK_TO_DEFAULT: |
591 |
636 print("*** Unhandled: {0}".format(node)) |
|
637 |
|
638 # Default for simple statements: one exit from this node. |
|
639 return set([ArcStart(self.line_for_node(node))]) |
|
640 |
|
641 @one_of("from_start, prev_starts") |
592 @contract(returns='ArcStarts') |
642 @contract(returns='ArcStarts') |
593 def add_body_arcs(self, body, from_start=None, prev_starts=None): |
643 def add_body_arcs(self, body, from_start=None, prev_starts=None): |
594 """Add arcs for the body of a compound statement. |
644 """Add arcs for the body of a compound statement. |
595 |
645 |
596 `body` is the body node. `from_start` is a single `ArcStart` that can |
646 `body` is the body node. `from_start` is a single `ArcStart` that can |
605 prev_starts = set([from_start]) |
655 prev_starts = set([from_start]) |
606 for body_node in body: |
656 for body_node in body: |
607 lineno = self.line_for_node(body_node) |
657 lineno = self.line_for_node(body_node) |
608 first_line = self.multiline.get(lineno, lineno) |
658 first_line = self.multiline.get(lineno, lineno) |
609 if first_line not in self.statements: |
659 if first_line not in self.statements: |
610 continue |
660 body_node = self.find_non_missing_node(body_node) |
|
661 if body_node is None: |
|
662 continue |
|
663 lineno = self.line_for_node(body_node) |
611 for prev_start in prev_starts: |
664 for prev_start in prev_starts: |
612 self.add_arc(prev_start.lineno, lineno, prev_start.cause) |
665 self.add_arc(prev_start.lineno, lineno, prev_start.cause) |
613 prev_starts = self.add_arcs(body_node) |
666 prev_starts = self.add_arcs(body_node) |
614 return prev_starts |
667 return prev_starts |
615 |
668 |
|
669 def find_non_missing_node(self, node): |
|
670 """Search `node` looking for a child that has not been optimized away. |
|
671 |
|
672 This might return the node you started with, or it will work recursively |
|
673 to find a child node in self.statements. |
|
674 |
|
675 Returns a node, or None if none of the node remains. |
|
676 |
|
677 """ |
|
678 # This repeats work just done in add_body_arcs, but this duplication |
|
679 # means we can avoid a function call in the 99.9999% case of not |
|
680 # optimizing away statements. |
|
681 lineno = self.line_for_node(node) |
|
682 first_line = self.multiline.get(lineno, lineno) |
|
683 if first_line in self.statements: |
|
684 return node |
|
685 |
|
686 missing_fn = getattr(self, "_missing__" + node.__class__.__name__, None) |
|
687 if missing_fn: |
|
688 node = missing_fn(node) |
|
689 else: |
|
690 node = None |
|
691 return node |
|
692 |
|
693 def _missing__If(self, node): |
|
694 # If the if-node is missing, then one of its children might still be |
|
695 # here, but not both. So return the first of the two that isn't missing. |
|
696 # Use a NodeList to hold the clauses as a single node. |
|
697 non_missing = self.find_non_missing_node(NodeList(node.body)) |
|
698 if non_missing: |
|
699 return non_missing |
|
700 if node.orelse: |
|
701 return self.find_non_missing_node(NodeList(node.orelse)) |
|
702 return None |
|
703 |
|
704 def _missing__NodeList(self, node): |
|
705 # A NodeList might be a mixture of missing and present nodes. Find the |
|
706 # ones that are present. |
|
707 non_missing_children = [] |
|
708 for child in node.body: |
|
709 child = self.find_non_missing_node(child) |
|
710 if child is not None: |
|
711 non_missing_children.append(child) |
|
712 |
|
713 # Return the simplest representation of the present children. |
|
714 if not non_missing_children: |
|
715 return None |
|
716 if len(non_missing_children) == 1: |
|
717 return non_missing_children[0] |
|
718 return NodeList(non_missing_children) |
|
719 |
616 def is_constant_expr(self, node): |
720 def is_constant_expr(self, node): |
617 """Is this a compile-time constant?""" |
721 """Is this a compile-time constant?""" |
618 node_name = node.__class__.__name__ |
722 node_name = node.__class__.__name__ |
619 if node_name in ["NameConstant", "Num"]: |
723 if node_name in ["NameConstant", "Num"]: |
620 return True |
724 return "Num" |
621 elif node_name == "Name": |
725 elif node_name == "Name": |
622 if env.PY3 and node.id in ["True", "False", "None"]: |
726 if node.id in ["True", "False", "None", "__debug__"]: |
623 return True |
727 return "Name" |
624 return False |
728 return None |
625 |
729 |
626 # tests to write: |
730 # In the fullness of time, these might be good tests to write: |
627 # TODO: while EXPR: |
731 # while EXPR: |
628 # TODO: while False: |
732 # while False: |
629 # TODO: listcomps hidden deep in other expressions |
733 # listcomps hidden deep in other expressions |
630 # TODO: listcomps hidden in lists: x = [[i for i in range(10)]] |
734 # listcomps hidden in lists: x = [[i for i in range(10)]] |
631 # TODO: nested function definitions |
735 # nested function definitions |
|
736 |
|
737 |
|
738 # Exit processing: process_*_exits |
|
739 # |
|
740 # These functions process the four kinds of jump exits: break, continue, |
|
741 # raise, and return. To figure out where an exit goes, we have to look at |
|
742 # the block stack context. For example, a break will jump to the nearest |
|
743 # enclosing loop block, or the nearest enclosing finally block, whichever |
|
744 # is nearer. |
632 |
745 |
633 @contract(exits='ArcStarts') |
746 @contract(exits='ArcStarts') |
634 def process_break_exits(self, exits): |
747 def process_break_exits(self, exits): |
635 """Add arcs due to jumps from `exits` being breaks.""" |
748 """Add arcs due to jumps from `exits` being breaks.""" |
636 for block in self.nearest_blocks(): |
749 for block in self.nearest_blocks(): |
686 xit.lineno, -block.start, xit.cause, |
799 xit.lineno, -block.start, xit.cause, |
687 "didn't return from function '{0}'".format(block.name), |
800 "didn't return from function '{0}'".format(block.name), |
688 ) |
801 ) |
689 break |
802 break |
690 |
803 |
691 ## Handlers |
804 |
|
805 # Handlers: _handle__* |
|
806 # |
|
807 # Each handler deals with a specific AST node type, dispatched from |
|
808 # add_arcs. Each deals with a particular kind of node type, and returns |
|
809 # the set of exits from that node. These functions mirror the Python |
|
810 # semantics of each syntactic construct. See the docstring for add_arcs to |
|
811 # understand the concept of exits from a node. |
692 |
812 |
693 @contract(returns='ArcStarts') |
813 @contract(returns='ArcStarts') |
694 def _handle__Break(self, node): |
814 def _handle__Break(self, node): |
695 here = self.line_for_node(node) |
815 here = self.line_for_node(node) |
696 break_start = ArcStart(here, cause="the break on line {lineno} wasn't executed") |
816 break_start = ArcStart(here, cause="the break on line {lineno} wasn't executed") |
709 last = dec_start |
829 last = dec_start |
710 # The definition line may have been missed, but we should have it |
830 # The definition line may have been missed, but we should have it |
711 # in `self.statements`. For some constructs, `line_for_node` is |
831 # in `self.statements`. For some constructs, `line_for_node` is |
712 # not what we'd think of as the first line in the statement, so map |
832 # not what we'd think of as the first line in the statement, so map |
713 # it to the first one. |
833 # it to the first one. |
714 body_start = self.line_for_node(node.body[0]) |
834 if node.body: |
715 body_start = self.multiline.get(body_start, body_start) |
835 body_start = self.line_for_node(node.body[0]) |
716 for lineno in range(last+1, body_start): |
836 body_start = self.multiline.get(body_start, body_start) |
717 if lineno in self.statements: |
837 for lineno in range(last+1, body_start): |
718 self.add_arc(last, lineno) |
838 if lineno in self.statements: |
719 last = lineno |
839 self.add_arc(last, lineno) |
|
840 last = lineno |
720 # The body is handled in collect_arcs. |
841 # The body is handled in collect_arcs. |
721 return set([ArcStart(last, cause=None)]) |
842 return set([ArcStart(last)]) |
722 |
843 |
723 _handle__ClassDef = _handle_decorated |
844 _handle__ClassDef = _handle_decorated |
724 |
845 |
725 @contract(returns='ArcStarts') |
846 @contract(returns='ArcStarts') |
726 def _handle__Continue(self, node): |
847 def _handle__Continue(self, node): |
762 from_start = ArcStart(start, cause="the condition on line {lineno} was never false") |
883 from_start = ArcStart(start, cause="the condition on line {lineno} was never false") |
763 exits |= self.add_body_arcs(node.orelse, from_start=from_start) |
884 exits |= self.add_body_arcs(node.orelse, from_start=from_start) |
764 return exits |
885 return exits |
765 |
886 |
766 @contract(returns='ArcStarts') |
887 @contract(returns='ArcStarts') |
|
888 def _handle__NodeList(self, node): |
|
889 start = self.line_for_node(node) |
|
890 exits = self.add_body_arcs(node.body, from_start=ArcStart(start)) |
|
891 return exits |
|
892 |
|
893 @contract(returns='ArcStarts') |
767 def _handle__Raise(self, node): |
894 def _handle__Raise(self, node): |
768 here = self.line_for_node(node) |
895 here = self.line_for_node(node) |
769 raise_start = ArcStart(here, cause="the raise on line {lineno} wasn't executed") |
896 raise_start = ArcStart(here, cause="the raise on line {lineno} wasn't executed") |
770 self.process_raise_exits([raise_start]) |
897 self.process_raise_exits([raise_start]) |
771 # `raise` statement jumps away, no exits from here. |
898 # `raise` statement jumps away, no exits from here. |
789 if node.finalbody: |
916 if node.finalbody: |
790 final_start = self.line_for_node(node.finalbody[0]) |
917 final_start = self.line_for_node(node.finalbody[0]) |
791 else: |
918 else: |
792 final_start = None |
919 final_start = None |
793 |
920 |
794 try_block = TryBlock(handler_start=handler_start, final_start=final_start) |
921 try_block = TryBlock(handler_start, final_start) |
795 self.block_stack.append(try_block) |
922 self.block_stack.append(try_block) |
796 |
923 |
797 start = self.line_for_node(node) |
924 start = self.line_for_node(node) |
798 exits = self.add_body_arcs(node.body, from_start=ArcStart(start, cause=None)) |
925 exits = self.add_body_arcs(node.body, from_start=ArcStart(start)) |
799 |
926 |
800 # We're done with the `try` body, so this block no longer handles |
927 # We're done with the `try` body, so this block no longer handles |
801 # exceptions. We keep the block so the `finally` clause can pick up |
928 # exceptions. We keep the block so the `finally` clause can pick up |
802 # flows from the handlers and `else` clause. |
929 # flows from the handlers and `else` clause. |
803 if node.finalbody: |
930 if node.finalbody: |
836 try_block.continue_from | # or a `continue`, |
963 try_block.continue_from | # or a `continue`, |
837 try_block.raise_from | # or a `raise`, |
964 try_block.raise_from | # or a `raise`, |
838 try_block.return_from # or a `return`. |
965 try_block.return_from # or a `return`. |
839 ) |
966 ) |
840 |
967 |
841 exits = self.add_body_arcs(node.finalbody, prev_starts=final_from) |
968 final_exits = self.add_body_arcs(node.finalbody, prev_starts=final_from) |
|
969 |
842 if try_block.break_from: |
970 if try_block.break_from: |
843 break_exits = self._combine_finally_starts(try_block.break_from, exits) |
971 self.process_break_exits( |
844 self.process_break_exits(break_exits) |
972 self._combine_finally_starts(try_block.break_from, final_exits) |
|
973 ) |
845 if try_block.continue_from: |
974 if try_block.continue_from: |
846 continue_exits = self._combine_finally_starts(try_block.continue_from, exits) |
975 self.process_continue_exits( |
847 self.process_continue_exits(continue_exits) |
976 self._combine_finally_starts(try_block.continue_from, final_exits) |
|
977 ) |
848 if try_block.raise_from: |
978 if try_block.raise_from: |
849 raise_exits = self._combine_finally_starts(try_block.raise_from, exits) |
979 self.process_raise_exits( |
850 self.process_raise_exits(raise_exits) |
980 self._combine_finally_starts(try_block.raise_from, final_exits) |
|
981 ) |
851 if try_block.return_from: |
982 if try_block.return_from: |
852 return_exits = self._combine_finally_starts(try_block.return_from, exits) |
983 self.process_return_exits( |
853 self.process_return_exits(return_exits) |
984 self._combine_finally_starts(try_block.return_from, final_exits) |
|
985 ) |
|
986 |
|
987 if exits: |
|
988 # The finally clause's exits are only exits for the try block |
|
989 # as a whole if the try block had some exits to begin with. |
|
990 exits = final_exits |
854 |
991 |
855 return exits |
992 return exits |
856 |
993 |
|
994 @contract(starts='ArcStarts', exits='ArcStarts', returns='ArcStarts') |
857 def _combine_finally_starts(self, starts, exits): |
995 def _combine_finally_starts(self, starts, exits): |
858 """Helper for building the cause of `finally` branches.""" |
996 """Helper for building the cause of `finally` branches. |
|
997 |
|
998 "finally" clauses might not execute their exits, and the causes could |
|
999 be due to a failure to execute any of the exits in the try block. So |
|
1000 we use the causes from `starts` as the causes for `exits`. |
|
1001 """ |
859 causes = [] |
1002 causes = [] |
860 for lineno, cause in sorted(starts): |
1003 for start in sorted(starts): |
861 if cause is not None: |
1004 if start.cause is not None: |
862 causes.append(cause.format(lineno=lineno)) |
1005 causes.append(start.cause.format(lineno=start.lineno)) |
863 cause = " or ".join(causes) |
1006 cause = " or ".join(causes) |
864 exits = set(ArcStart(ex.lineno, cause) for ex in exits) |
1007 exits = set(ArcStart(xit.lineno, cause) for xit in exits) |
865 return exits |
1008 return exits |
866 |
1009 |
867 @contract(returns='ArcStarts') |
1010 @contract(returns='ArcStarts') |
868 def _handle__TryExcept(self, node): |
1011 def _handle__TryExcept(self, node): |
869 # Python 2.7 uses separate TryExcept and TryFinally nodes. If we get |
1012 # Python 2.7 uses separate TryExcept and TryFinally nodes. If we get |
891 |
1034 |
892 @contract(returns='ArcStarts') |
1035 @contract(returns='ArcStarts') |
893 def _handle__While(self, node): |
1036 def _handle__While(self, node): |
894 constant_test = self.is_constant_expr(node.test) |
1037 constant_test = self.is_constant_expr(node.test) |
895 start = to_top = self.line_for_node(node.test) |
1038 start = to_top = self.line_for_node(node.test) |
896 if constant_test: |
1039 if constant_test and (env.PY3 or constant_test == "Num"): |
897 to_top = self.line_for_node(node.body[0]) |
1040 to_top = self.line_for_node(node.body[0]) |
898 self.block_stack.append(LoopBlock(start=start)) |
1041 self.block_stack.append(LoopBlock(start=to_top)) |
899 from_start = ArcStart(start, cause="the condition on line {lineno} was never true") |
1042 from_start = ArcStart(start, cause="the condition on line {lineno} was never true") |
900 exits = self.add_body_arcs(node.body, from_start=from_start) |
1043 exits = self.add_body_arcs(node.body, from_start=from_start) |
901 for xit in exits: |
1044 for xit in exits: |
902 self.add_arc(xit.lineno, to_top, xit.cause) |
1045 self.add_arc(xit.lineno, to_top, xit.cause) |
903 exits = set() |
1046 exits = set() |
968 _code_object__SetComp = _make_oneline_code_method("set comprehension") |
1111 _code_object__SetComp = _make_oneline_code_method("set comprehension") |
969 if env.PY3: |
1112 if env.PY3: |
970 _code_object__ListComp = _make_oneline_code_method("list comprehension") |
1113 _code_object__ListComp = _make_oneline_code_method("list comprehension") |
971 |
1114 |
972 |
1115 |
973 SKIP_DUMP_FIELDS = ["ctx"] |
1116 if AST_DUMP: # pragma: debugging |
974 |
1117 # Code only used when dumping the AST for debugging. |
975 def _is_simple_value(value): |
1118 |
976 """Is `value` simple enough to be displayed on a single line?""" |
1119 SKIP_DUMP_FIELDS = ["ctx"] |
977 return ( |
1120 |
978 value in [None, [], (), {}, set()] or |
1121 def _is_simple_value(value): |
979 isinstance(value, (string_class, int, float)) |
1122 """Is `value` simple enough to be displayed on a single line?""" |
980 ) |
1123 return ( |
981 |
1124 value in [None, [], (), {}, set()] or |
982 # TODO: a test of ast_dump? |
1125 isinstance(value, (string_class, int, float)) |
983 def ast_dump(node, depth=0): |
1126 ) |
984 """Dump the AST for `node`. |
1127 |
985 |
1128 def ast_dump(node, depth=0): |
986 This recursively walks the AST, printing a readable version. |
1129 """Dump the AST for `node`. |
987 |
1130 |
988 """ |
1131 This recursively walks the AST, printing a readable version. |
989 indent = " " * depth |
1132 |
990 if not isinstance(node, ast.AST): |
1133 """ |
991 print("{0}<{1} {2!r}>".format(indent, node.__class__.__name__, node)) |
1134 indent = " " * depth |
992 return |
1135 if not isinstance(node, ast.AST): |
993 |
1136 print("{0}<{1} {2!r}>".format(indent, node.__class__.__name__, node)) |
994 lineno = getattr(node, "lineno", None) |
1137 return |
995 if lineno is not None: |
1138 |
996 linemark = " @ {0}".format(node.lineno) |
1139 lineno = getattr(node, "lineno", None) |
997 else: |
1140 if lineno is not None: |
998 linemark = "" |
1141 linemark = " @ {0}".format(node.lineno) |
999 head = "{0}<{1}{2}".format(indent, node.__class__.__name__, linemark) |
1142 else: |
1000 |
1143 linemark = "" |
1001 named_fields = [ |
1144 head = "{0}<{1}{2}".format(indent, node.__class__.__name__, linemark) |
1002 (name, value) |
1145 |
1003 for name, value in ast.iter_fields(node) |
1146 named_fields = [ |
1004 if name not in SKIP_DUMP_FIELDS |
1147 (name, value) |
1005 ] |
1148 for name, value in ast.iter_fields(node) |
1006 if not named_fields: |
1149 if name not in SKIP_DUMP_FIELDS |
1007 print("{0}>".format(head)) |
1150 ] |
1008 elif len(named_fields) == 1 and _is_simple_value(named_fields[0][1]): |
1151 if not named_fields: |
1009 field_name, value = named_fields[0] |
1152 print("{0}>".format(head)) |
1010 print("{0} {1}: {2!r}>".format(head, field_name, value)) |
1153 elif len(named_fields) == 1 and _is_simple_value(named_fields[0][1]): |
1011 else: |
1154 field_name, value = named_fields[0] |
1012 print(head) |
1155 print("{0} {1}: {2!r}>".format(head, field_name, value)) |
1013 if 0: |
1156 else: |
1014 print("{0}# mro: {1}".format( |
1157 print(head) |
1015 indent, ", ".join(c.__name__ for c in node.__class__.__mro__[1:]), |
1158 if 0: |
1016 )) |
1159 print("{0}# mro: {1}".format( |
1017 next_indent = indent + " " |
1160 indent, ", ".join(c.__name__ for c in node.__class__.__mro__[1:]), |
1018 for field_name, value in named_fields: |
1161 )) |
1019 prefix = "{0}{1}:".format(next_indent, field_name) |
1162 next_indent = indent + " " |
1020 if _is_simple_value(value): |
1163 for field_name, value in named_fields: |
1021 print("{0} {1!r}".format(prefix, value)) |
1164 prefix = "{0}{1}:".format(next_indent, field_name) |
1022 elif isinstance(value, list): |
1165 if _is_simple_value(value): |
1023 print("{0} [".format(prefix)) |
1166 print("{0} {1!r}".format(prefix, value)) |
1024 for n in value: |
1167 elif isinstance(value, list): |
1025 ast_dump(n, depth + 8) |
1168 print("{0} [".format(prefix)) |
1026 print("{0}]".format(next_indent)) |
1169 for n in value: |
1027 else: |
1170 ast_dump(n, depth + 8) |
1028 print(prefix) |
1171 print("{0}]".format(next_indent)) |
1029 ast_dump(value, depth + 8) |
1172 else: |
1030 |
1173 print(prefix) |
1031 print("{0}>".format(indent)) |
1174 ast_dump(value, depth + 8) |
|
1175 |
|
1176 print("{0}>".format(indent)) |