203 |
203 |
204 # Find the starts of the executable statements. |
204 # Find the starts of the executable statements. |
205 if not empty: |
205 if not empty: |
206 self.raw_statements.update(self.byte_parser._find_statements()) |
206 self.raw_statements.update(self.byte_parser._find_statements()) |
207 |
207 |
|
208 # The first line of modules can lie and say 1 always, even if the first |
|
209 # line of code is later. If so, map 1 to the actual first line of the |
|
210 # module. |
|
211 if env.PYBEHAVIOR.module_firstline_1 and self._multiline: |
|
212 self._multiline[1] = min(self.raw_statements) |
|
213 |
208 def first_line(self, line): |
214 def first_line(self, line): |
209 """Return the first line number of the statement including `line`.""" |
215 """Return the first line number of the statement including `line`.""" |
210 if line < 0: |
216 if line < 0: |
211 line = -self._multiline.get(-line, -line) |
217 line = -self._multiline.get(-line, -line) |
212 else: |
218 else: |
330 start, end = end, start |
336 start, end = end, start |
331 |
337 |
332 fragment_pairs = self._missing_arc_fragments.get((start, end), [(None, None)]) |
338 fragment_pairs = self._missing_arc_fragments.get((start, end), [(None, None)]) |
333 |
339 |
334 msgs = [] |
340 msgs = [] |
335 for fragment_pair in fragment_pairs: |
341 for smsg, emsg in fragment_pairs: |
336 smsg, emsg = fragment_pair |
|
337 |
|
338 if emsg is None: |
342 if emsg is None: |
339 if end < 0: |
343 if end < 0: |
340 # Hmm, maybe we have a one-line callable, let's check. |
344 # Hmm, maybe we have a one-line callable, let's check. |
341 if (-end, end) in self._missing_arc_fragments: |
345 if (-end, end) in self._missing_arc_fragments: |
342 return self.missing_arc_description(-end, end) |
346 return self.missing_arc_description(-end, end) |
387 The iteration includes `self` as its first value. |
391 The iteration includes `self` as its first value. |
388 |
392 |
389 """ |
393 """ |
390 return (ByteParser(self.text, code=c) for c in code_objects(self.code)) |
394 return (ByteParser(self.text, code=c) for c in code_objects(self.code)) |
391 |
395 |
392 def _bytes_lines(self): |
396 def _line_numbers(self): |
393 """Map byte offsets to line numbers in `code`. |
397 """Yield the line numbers possible in this code object. |
394 |
398 |
395 Uses co_lnotab described in Python/compile.c to map byte offsets to |
399 Uses co_lnotab described in Python/compile.c to find the |
396 line numbers. Produces a sequence: (b0, l0), (b1, l1), ... |
400 line numbers. Produces a sequence: l0, l1, ... |
397 |
401 """ |
398 Only byte offsets that correspond to line numbers are included in the |
402 if hasattr(self.code, "co_lines"): |
399 results. |
403 for _, _, line in self.code.co_lines(): |
400 |
404 if line is not None: |
401 """ |
405 yield line |
402 # Adapted from dis.py in the standard library. |
406 else: |
403 byte_increments = bytes_to_ints(self.code.co_lnotab[0::2]) |
407 # Adapted from dis.py in the standard library. |
404 line_increments = bytes_to_ints(self.code.co_lnotab[1::2]) |
408 byte_increments = bytes_to_ints(self.code.co_lnotab[0::2]) |
405 |
409 line_increments = bytes_to_ints(self.code.co_lnotab[1::2]) |
406 last_line_num = None |
410 |
407 line_num = self.code.co_firstlineno |
411 last_line_num = None |
408 byte_num = 0 |
412 line_num = self.code.co_firstlineno |
409 for byte_incr, line_incr in zip(byte_increments, line_increments): |
413 byte_num = 0 |
410 if byte_incr: |
414 for byte_incr, line_incr in zip(byte_increments, line_increments): |
411 if line_num != last_line_num: |
415 if byte_incr: |
412 yield (byte_num, line_num) |
416 if line_num != last_line_num: |
413 last_line_num = line_num |
417 yield line_num |
414 byte_num += byte_incr |
418 last_line_num = line_num |
415 if env.PYBEHAVIOR.negative_lnotab and line_incr >= 0x80: |
419 byte_num += byte_incr |
416 line_incr -= 0x100 |
420 if env.PYBEHAVIOR.negative_lnotab and line_incr >= 0x80: |
417 line_num += line_incr |
421 line_incr -= 0x100 |
418 if line_num != last_line_num: |
422 line_num += line_incr |
419 yield (byte_num, line_num) |
423 if line_num != last_line_num: |
|
424 yield line_num |
420 |
425 |
421 def _find_statements(self): |
426 def _find_statements(self): |
422 """Find the statements in `self.code`. |
427 """Find the statements in `self.code`. |
423 |
428 |
424 Produce a sequence of line numbers that start statements. Recurses |
429 Produce a sequence of line numbers that start statements. Recurses |
425 into all code objects reachable from `self.code`. |
430 into all code objects reachable from `self.code`. |
426 |
431 |
427 """ |
432 """ |
428 for bp in self.child_parsers(): |
433 for bp in self.child_parsers(): |
429 # Get all of the lineno information from this code. |
434 # Get all of the lineno information from this code. |
430 for _, l in bp._bytes_lines(): |
435 for l in bp._line_numbers(): |
431 yield l |
436 yield l |
432 |
437 |
433 |
438 |
434 # |
439 # |
435 # AST analysis |
440 # AST analysis |
518 |
523 |
519 @contract(text='unicode', statements=set) |
524 @contract(text='unicode', statements=set) |
520 def __init__(self, text, statements, multiline): |
525 def __init__(self, text, statements, multiline): |
521 self.root_node = ast.parse(neuter_encoding_declaration(text)) |
526 self.root_node = ast.parse(neuter_encoding_declaration(text)) |
522 # TODO: I think this is happening in too many places. |
527 # TODO: I think this is happening in too many places. |
523 self.statements = set(multiline.get(l, l) for l in statements) |
528 self.statements = {multiline.get(l, l) for l in statements} |
524 self.multiline = multiline |
529 self.multiline = multiline |
525 |
530 |
526 if AST_DUMP: # pragma: debugging |
531 if AST_DUMP: # pragma: debugging |
527 # Dump the AST so that failing tests have helpful output. |
532 # Dump the AST so that failing tests have helpful output. |
528 print("Statements: {}".format(self.statements)) |
533 print("Statements: {}".format(self.statements)) |
617 return self.line_for_node(node.elts[0]) |
622 return self.line_for_node(node.elts[0]) |
618 else: |
623 else: |
619 return node.lineno |
624 return node.lineno |
620 |
625 |
621 def _line__Module(self, node): |
626 def _line__Module(self, node): |
622 if node.body: |
627 if env.PYBEHAVIOR.module_firstline_1: |
|
628 return 1 |
|
629 elif node.body: |
623 return self.line_for_node(node.body[0]) |
630 return self.line_for_node(node.body[0]) |
624 else: |
631 else: |
625 # Empty modules have no line number, they always start at 1. |
632 # Empty modules have no line number, they always start at 1. |
626 return 1 |
633 return 1 |
627 |
634 |
628 # The node types that just flow to the next node with no complications. |
635 # The node types that just flow to the next node with no complications. |
629 OK_TO_DEFAULT = set([ |
636 OK_TO_DEFAULT = { |
630 "Assign", "Assert", "AugAssign", "Delete", "Exec", "Expr", "Global", |
637 "Assign", "Assert", "AugAssign", "Delete", "Exec", "Expr", "Global", |
631 "Import", "ImportFrom", "Nonlocal", "Pass", "Print", |
638 "Import", "ImportFrom", "Nonlocal", "Pass", "Print", |
632 ]) |
639 } |
633 |
640 |
634 @contract(returns='ArcStarts') |
641 @contract(returns='ArcStarts') |
635 def add_arcs(self, node): |
642 def add_arcs(self, node): |
636 """Add the arcs for `node`. |
643 """Add the arcs for `node`. |
637 |
644 |
659 if 0: |
666 if 0: |
660 if node_name not in self.OK_TO_DEFAULT: |
667 if node_name not in self.OK_TO_DEFAULT: |
661 print("*** Unhandled: {}".format(node)) |
668 print("*** Unhandled: {}".format(node)) |
662 |
669 |
663 # Default for simple statements: one exit from this node. |
670 # Default for simple statements: one exit from this node. |
664 return set([ArcStart(self.line_for_node(node))]) |
671 return {ArcStart(self.line_for_node(node))} |
665 |
672 |
666 @one_of("from_start, prev_starts") |
673 @one_of("from_start, prev_starts") |
667 @contract(returns='ArcStarts') |
674 @contract(returns='ArcStarts') |
668 def add_body_arcs(self, body, from_start=None, prev_starts=None): |
675 def add_body_arcs(self, body, from_start=None, prev_starts=None): |
669 """Add arcs for the body of a compound statement. |
676 """Add arcs for the body of a compound statement. |
675 |
682 |
676 Returns a set of ArcStarts, the exits from this body. |
683 Returns a set of ArcStarts, the exits from this body. |
677 |
684 |
678 """ |
685 """ |
679 if prev_starts is None: |
686 if prev_starts is None: |
680 prev_starts = set([from_start]) |
687 prev_starts = {from_start} |
681 for body_node in body: |
688 for body_node in body: |
682 lineno = self.line_for_node(body_node) |
689 lineno = self.line_for_node(body_node) |
683 first_line = self.multiline.get(lineno, lineno) |
690 first_line = self.multiline.get(lineno, lineno) |
684 if first_line not in self.statements: |
691 if first_line not in self.statements: |
685 body_node = self.find_non_missing_node(body_node) |
692 body_node = self.find_non_missing_node(body_node) |
888 for lineno in range(last+1, body_start): |
895 for lineno in range(last+1, body_start): |
889 if lineno in self.statements: |
896 if lineno in self.statements: |
890 self.add_arc(last, lineno) |
897 self.add_arc(last, lineno) |
891 last = lineno |
898 last = lineno |
892 # The body is handled in collect_arcs. |
899 # The body is handled in collect_arcs. |
893 return set([ArcStart(last)]) |
900 return {ArcStart(last)} |
894 |
901 |
895 _handle__ClassDef = _handle_decorated |
902 _handle__ClassDef = _handle_decorated |
896 |
903 |
897 @contract(returns='ArcStarts') |
904 @contract(returns='ArcStarts') |
898 def _handle__Continue(self, node): |
905 def _handle__Continue(self, node): |
982 try_block.handler_start = None |
989 try_block.handler_start = None |
983 if node.handlers: |
990 if node.handlers: |
984 # If there are `except` clauses, then raises in the try body |
991 # If there are `except` clauses, then raises in the try body |
985 # will already jump to them. Start this set over for raises in |
992 # will already jump to them. Start this set over for raises in |
986 # `except` and `else`. |
993 # `except` and `else`. |
987 try_block.raise_from = set([]) |
994 try_block.raise_from = set() |
988 else: |
995 else: |
989 self.block_stack.pop() |
996 self.block_stack.pop() |
990 |
997 |
991 handler_exits = set() |
998 handler_exits = set() |
992 |
999 |
1077 causes = [] |
1084 causes = [] |
1078 for start in sorted(starts): |
1085 for start in sorted(starts): |
1079 if start.cause is not None: |
1086 if start.cause is not None: |
1080 causes.append(start.cause.format(lineno=start.lineno)) |
1087 causes.append(start.cause.format(lineno=start.lineno)) |
1081 cause = " or ".join(causes) |
1088 cause = " or ".join(causes) |
1082 exits = set(ArcStart(xit.lineno, cause) for xit in exits) |
1089 exits = {ArcStart(xit.lineno, cause) for xit in exits} |
1083 return exits |
1090 return exits |
1084 |
1091 |
1085 @contract(returns='ArcStarts') |
1092 @contract(returns='ArcStarts') |
1086 def _handle__TryExcept(self, node): |
1093 def _handle__TryExcept(self, node): |
1087 # Python 2.7 uses separate TryExcept and TryFinally nodes. If we get |
1094 # Python 2.7 uses separate TryExcept and TryFinally nodes. If we get |
1107 |
1114 |
1108 return self._handle__Try(node) |
1115 return self._handle__Try(node) |
1109 |
1116 |
1110 @contract(returns='ArcStarts') |
1117 @contract(returns='ArcStarts') |
1111 def _handle__While(self, node): |
1118 def _handle__While(self, node): |
|
1119 start = to_top = self.line_for_node(node.test) |
1112 constant_test = self.is_constant_expr(node.test) |
1120 constant_test = self.is_constant_expr(node.test) |
1113 start = to_top = self.line_for_node(node.test) |
1121 top_is_body0 = False |
1114 if constant_test and (env.PY3 or constant_test == "Num"): |
1122 if constant_test and (env.PY3 or constant_test == "Num"): |
|
1123 top_is_body0 = True |
|
1124 if env.PYBEHAVIOR.keep_constant_test: |
|
1125 top_is_body0 = False |
|
1126 if top_is_body0: |
1115 to_top = self.line_for_node(node.body[0]) |
1127 to_top = self.line_for_node(node.body[0]) |
1116 self.block_stack.append(LoopBlock(start=to_top)) |
1128 self.block_stack.append(LoopBlock(start=to_top)) |
1117 from_start = ArcStart(start, cause="the condition on line {lineno} was never true") |
1129 from_start = ArcStart(start, cause="the condition on line {lineno} was never true") |
1118 exits = self.add_body_arcs(node.body, from_start=from_start) |
1130 exits = self.add_body_arcs(node.body, from_start=from_start) |
1119 for xit in exits: |
1131 for xit in exits: |