DebugClients/Python/coverage/parser.py

changeset 6649
f1b3a73831c9
parent 6219
d6c795b5ce33
diff -r c09e6c6006eb -r f1b3a73831c9 DebugClients/Python/coverage/parser.py
--- a/DebugClients/Python/coverage/parser.py	Thu Jan 10 18:01:19 2019 +0100
+++ b/DebugClients/Python/coverage/parser.py	Sat Jan 12 11:26:32 2019 +0100
@@ -409,6 +409,8 @@
                     yield (byte_num, line_num)
                     last_line_num = line_num
                 byte_num += byte_incr
+            if env.PYVERSION >= (3, 6) and line_incr >= 0x80:
+                line_incr -= 0x100
             line_num += line_incr
         if line_num != last_line_num:
             yield (byte_num, line_num)
@@ -503,6 +505,10 @@
         self.lineno = body[0].lineno
 
 
+# TODO: some add_arcs methods here don't add arcs, they return them. Rename them.
+# TODO: the cause messages have too many commas.
+# TODO: Shouldn't the cause messages join with "and" instead of "or"?
+
 class AstArcAnalyzer(object):
     """Analyze source text with an AST to find executable code paths."""
 
@@ -544,6 +550,7 @@
             if code_object_handler is not None:
                 code_object_handler(node)
 
+    @contract(start=int, end=int)
     def add_arc(self, start, end, smsg=None, emsg=None):
         """Add an arc, including message fragments to use if it is missing."""
         if self.debug:                      # pragma: debugging
@@ -572,9 +579,19 @@
         else:
             return node.lineno
 
+    def _line_decorated(self, node):
+        """Compute first line number for things that can be decorated (classes and functions)."""
+        lineno = node.lineno
+        if env.PYBEHAVIOR.trace_decorated_def:
+            if node.decorator_list:
+                lineno = node.decorator_list[0].lineno
+        return lineno
+
     def _line__Assign(self, node):
         return self.line_for_node(node.value)
 
+    _line__ClassDef = _line_decorated
+
     def _line__Dict(self, node):
         # Python 3.5 changed how dict literals are made.
         if env.PYVERSION >= (3, 5) and node.keys:
@@ -587,6 +604,8 @@
         else:
             return node.lineno
 
+    _line__FunctionDef = _line_decorated
+
     def _line__List(self, node):
         if node.elts:
             return self.line_for_node(node.elts[0])
@@ -690,6 +709,13 @@
             node = None
         return node
 
+    # Missing nodes: _missing__*
+    #
+    # Entire statements can be optimized away by Python. They will appear in
+    # the AST, but not the bytecode.  These functions are called (by
+    # find_non_missing_node) to find a node to use instead of the missing
+    # node.  They can return None if the node should truly be gone.
+
     def _missing__If(self, node):
         # If the if-node is missing, then one of its children might still be
         # here, but not both. So return the first of the two that isn't missing.
@@ -717,10 +743,24 @@
             return non_missing_children[0]
         return NodeList(non_missing_children)
 
+    def _missing__While(self, node):
+        body_nodes = self.find_non_missing_node(NodeList(node.body))
+        if not body_nodes:
+            return None
+        # Make a synthetic While-true node.
+        new_while = ast.While()
+        new_while.lineno = body_nodes.lineno
+        new_while.test = ast.Name()
+        new_while.test.lineno = body_nodes.lineno
+        new_while.test.id = "True"
+        new_while.body = body_nodes.body
+        new_while.orelse = None
+        return new_while
+
     def is_constant_expr(self, node):
         """Is this a compile-time constant?"""
         node_name = node.__class__.__name__
-        if node_name in ["NameConstant", "Num"]:
+        if node_name in ["Constant", "NameConstant", "Num"]:
             return "Num"
         elif node_name == "Name":
             if node.id in ["True", "False", "None", "__debug__"]:
@@ -805,10 +845,10 @@
     # Handlers: _handle__*
     #
     # Each handler deals with a specific AST node type, dispatched from
-    # add_arcs.  Each deals with a particular kind of node type, and returns
-    # the set of exits from that node. These functions mirror the Python
-    # semantics of each syntactic construct.  See the docstring for add_arcs to
-    # understand the concept of exits from a node.
+    # add_arcs.  Handlers return the set of exits from that node, and can
+    # also call self.add_arc to record arcs they find.  These functions mirror
+    # the Python semantics of each syntactic construct.  See the docstring
+    # for add_arcs to understand the concept of exits from a node.
 
     @contract(returns='ArcStarts')
     def _handle__Break(self, node):
@@ -820,13 +860,18 @@
     @contract(returns='ArcStarts')
     def _handle_decorated(self, node):
         """Add arcs for things that can be decorated (classes and functions)."""
-        last = self.line_for_node(node)
+        main_line = last = node.lineno
         if node.decorator_list:
+            if env.PYBEHAVIOR.trace_decorated_def:
+                last = None
             for dec_node in node.decorator_list:
                 dec_start = self.line_for_node(dec_node)
-                if dec_start != last:
+                if last is not None and dec_start != last:
                     self.add_arc(last, dec_start)
-                    last = dec_start
+                last = dec_start
+            if env.PYBEHAVIOR.trace_decorated_def:
+                self.add_arc(last, main_line)
+                last = main_line
             # The definition line may have been missed, but we should have it
             # in `self.statements`.  For some constructs, `line_for_node` is
             # not what we'd think of as the first line in the statement, so map
@@ -968,21 +1013,45 @@
             final_exits = self.add_body_arcs(node.finalbody, prev_starts=final_from)
 
             if try_block.break_from:
-                self.process_break_exits(
-                    self._combine_finally_starts(try_block.break_from, final_exits)
-                )
+                if env.PYBEHAVIOR.finally_jumps_back:
+                    for break_line in try_block.break_from:
+                        lineno = break_line.lineno
+                        cause = break_line.cause.format(lineno=lineno)
+                        for final_exit in final_exits:
+                            self.add_arc(final_exit.lineno, lineno, cause)
+                    breaks = try_block.break_from
+                else:
+                    breaks = self._combine_finally_starts(try_block.break_from, final_exits)
+                self.process_break_exits(breaks)
+
             if try_block.continue_from:
-                self.process_continue_exits(
-                    self._combine_finally_starts(try_block.continue_from, final_exits)
-                )
+                if env.PYBEHAVIOR.finally_jumps_back:
+                    for continue_line in try_block.continue_from:
+                        lineno = continue_line.lineno
+                        cause = continue_line.cause.format(lineno=lineno)
+                        for final_exit in final_exits:
+                            self.add_arc(final_exit.lineno, lineno, cause)
+                    continues = try_block.continue_from
+                else:
+                    continues = self._combine_finally_starts(try_block.continue_from, final_exits)
+                self.process_continue_exits(continues)
+
             if try_block.raise_from:
                 self.process_raise_exits(
                     self._combine_finally_starts(try_block.raise_from, final_exits)
                 )
+
             if try_block.return_from:
-                self.process_return_exits(
-                    self._combine_finally_starts(try_block.return_from, final_exits)
-                )
+                if env.PYBEHAVIOR.finally_jumps_back:
+                    for return_line in try_block.return_from:
+                        lineno = return_line.lineno
+                        cause = return_line.cause.format(lineno=lineno)
+                        for final_exit in final_exits:
+                            self.add_arc(final_exit.lineno, lineno, cause)
+                    returns = try_block.return_from
+                else:
+                    returns = self._combine_finally_starts(try_block.return_from, final_exits)
+                self.process_return_exits(returns)
 
             if exits:
                 # The finally clause's exits are only exits for the try block

eric ide

mercurial