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