1 """Code parsing for Coverage.""" 2 3 import opcode, re, sys, token, tokenize 4 5 from coverage.backward import set, sorted, StringIO # pylint: disable=W0622 6 from coverage.backward import open_source 7 from coverage.bytecode import ByteCodes, CodeObjects 8 from coverage.misc import nice_pair, expensive, join_regex 9 from coverage.misc import CoverageException, NoSource, NotPython 10 11 12 class CodeParser(object): 13 """Parse code to find executable lines, excluded lines, etc.""" 14 15 def __init__(self, text=None, filename=None, exclude=None): 16 """ 17 Source can be provided as `text`, the text itself, or `filename`, from 18 which the text will be read. Excluded lines are those that match 19 `exclude`, a regex. 20 21 """ 22 assert text or filename, "CodeParser needs either text or filename" 23 self.filename = filename or "<code>" 24 self.text = text 25 if not self.text: 26 try: 27 sourcef = open_source(self.filename) 28 try: 29 self.text = sourcef.read() 30 finally: 31 sourcef.close() 32 except IOError: 33 _, err, _ = sys.exc_info() 34 raise NoSource( 35 "No source for code: %r: %s" % (self.filename, err) 36 ) 37 38 self.exclude = exclude 39 40 self.show_tokens = False 41 42 # The text lines of the parsed code. 43 self.lines = self.text.split('\n') 44 45 # The line numbers of excluded lines of code. 46 self.excluded = set() 47 48 # The line numbers of docstring lines. 49 self.docstrings = set() 50 51 # The line numbers of class definitions. 52 self.classdefs = set() 53 54 # A dict mapping line numbers to (lo,hi) for multi-line statements. 55 self.multiline = {} 56 57 # The line numbers that start statements. 58 self.statement_starts = set() 59 60 # Lazily-created ByteParser 61 self._byte_parser = None 62 63 def _get_byte_parser(self): 64 """Create a ByteParser on demand.""" 65 if not self._byte_parser: 66 self._byte_parser = \ 67 ByteParser(text=self.text, filename=self.filename) 68 return self._byte_parser 69 byte_parser = property(_get_byte_parser) 70 71 def lines_matching(self, *regexes): 72 """Find the lines matching one of a list of regexes. 73 74 Returns a set of line numbers, the lines that contain a match for one 75 of the regexes in `regexes`. The entire line needn't match, just a 76 part of it. 77 78 """ 79 regex_c = re.compile(join_regex(regexes)) 80 matches = set() 81 for i, ltext in enumerate(self.lines): 82 if regex_c.search(ltext): 83 matches.add(i+1) 84 return matches 85 86 def _raw_parse(self): 87 """Parse the source to find the interesting facts about its lines. 88 89 A handful of member fields are updated. 90 91 """ 92 # Find lines which match an exclusion pattern. 93 if self.exclude: 94 self.excluded = self.lines_matching(self.exclude) 95 96 # Tokenize, to find excluded suites, to find docstrings, and to find 97 # multi-line statements. 98 indent = 0 99 exclude_indent = 0 100 excluding = False 101 prev_toktype = token.INDENT 102 first_line = None 103 empty = True 104 105 tokgen = tokenize.generate_tokens(StringIO(self.text).readline) 106 for toktype, ttext, (slineno, _), (elineno, _), ltext in tokgen: 107 if self.show_tokens: # pragma: no cover 108 print("%10s %5s %-20r %r" % ( 109 tokenize.tok_name.get(toktype, toktype), 110 nice_pair((slineno, elineno)), ttext, ltext 111 )) 112 if toktype == token.INDENT: 113 indent += 1 114 elif toktype == token.DEDENT: 115 indent -= 1 116 elif toktype == token.NAME and ttext == 'class': 117 # Class definitions look like branches in the byte code, so 118 # we need to exclude them. The simplest way is to note the 119 # lines with the 'class' keyword. 120 self.classdefs.add(slineno) 121 elif toktype == token.OP and ttext == ':': 122 if not excluding and elineno in self.excluded: 123 # Start excluding a suite. We trigger off of the colon 124 # token so that the #pragma comment will be recognized on 125 # the same line as the colon. 126 exclude_indent = indent 127 excluding = True 128 elif toktype == token.STRING and prev_toktype == token.INDENT: 129 # Strings that are first on an indented line are docstrings. 130 # (a trick from trace.py in the stdlib.) This works for 131 # 99.9999% of cases. For the rest (!) see: 132 # http://stackoverflow.com/questions/1769332/x/1769794#1769794 133 for i in range(slineno, elineno+1): 134 self.docstrings.add(i) 135 elif toktype == token.NEWLINE: 136 if first_line is not None and elineno != first_line: 137 # We're at the end of a line, and we've ended on a 138 # different line than the first line of the statement, 139 # so record a multi-line range. 140 rng = (first_line, elineno) 141 for l in range(first_line, elineno+1): 142 self.multiline[l] = rng 143 first_line = None 144 145 if ttext.strip() and toktype != tokenize.COMMENT: 146 # A non-whitespace token. 147 empty = False 148 if first_line is None: 149 # The token is not whitespace, and is the first in a 150 # statement. 151 first_line = slineno 152 # Check whether to end an excluded suite. 153 if excluding and indent <= exclude_indent: 154 excluding = False 155 if excluding: 156 self.excluded.add(elineno) 157 158 prev_toktype = toktype 159 160 # Find the starts of the executable statements. 161 if not empty: 162 self.statement_starts.update(self.byte_parser._find_statements()) 163 164 def first_line(self, line): 165 """Return the first line number of the statement including `line`.""" 166 rng = self.multiline.get(line) 167 if rng: 168 first_line = rng[0] 169 else: 170 first_line = line 171 return first_line 172 173 def first_lines(self, lines, ignore=None): 174 """Map the line numbers in `lines` to the correct first line of the 175 statement. 176 177 Skip any line mentioned in `ignore`. 178 179 Returns a sorted list of the first lines. 180 181 """ 182 ignore = ignore or [] 183 lset = set() 184 for l in lines: 185 if l in ignore: 186 continue 187 new_l = self.first_line(l) 188 if new_l not in ignore: 189 lset.add(new_l) 190 return sorted(lset) 191 192 def parse_source(self): 193 """Parse source text to find executable lines, excluded lines, etc. 194 195 Return values are 1) a sorted list of executable line numbers, and 196 2) a sorted list of excluded line numbers. 197 198 Reported line numbers are normalized to the first line of multi-line 199 statements. 200 201 """ 202 self._raw_parse() 203 204 excluded_lines = self.first_lines(self.excluded) 205 ignore = excluded_lines + list(self.docstrings) 206 lines = self.first_lines(self.statement_starts, ignore) 207 208 return lines, excluded_lines 209 210 def arcs(self): 211 """Get information about the arcs available in the code. 212 213 Returns a sorted list of line number pairs. Line numbers have been 214 normalized to the first line of multiline statements. 215 216 """ 217 all_arcs = [] 218 for l1, l2 in self.byte_parser._all_arcs(): 219 fl1 = self.first_line(l1) 220 fl2 = self.first_line(l2) 221 if fl1 != fl2: 222 all_arcs.append((fl1, fl2)) 223 return sorted(all_arcs) 224 arcs = expensive(arcs) 225 226 def exit_counts(self): 227 """Get a mapping from line numbers to count of exits from that line. 228 229 Excluded lines are excluded. 230 231 """ 232 excluded_lines = self.first_lines(self.excluded) 233 exit_counts = {} 234 for l1, l2 in self.arcs(): 235 if l1 < 0: 236 # Don't ever report -1 as a line number 237 continue 238 if l1 in excluded_lines: 239 # Don't report excluded lines as line numbers. 240 continue 241 if l2 in excluded_lines: 242 # Arcs to excluded lines shouldn't count. 243 continue 244 if l1 not in exit_counts: 245 exit_counts[l1] = 0 246 exit_counts[l1] += 1 247 248 # Class definitions have one extra exit, so remove one for each: 249 for l in self.classdefs: 250 # Ensure key is there: classdefs can include excluded lines. 251 if l in exit_counts: 252 exit_counts[l] -= 1 253 254 return exit_counts 255 exit_counts = expensive(exit_counts) 256 257 258 ## Opcodes that guide the ByteParser. 259 260 def _opcode(name): 261 """Return the opcode by name from the opcode module.""" 262 return opcode.opmap[name] 263 264 def _opcode_set(*names): 265 """Return a set of opcodes by the names in `names`.""" 266 s = set() 267 for name in names: 268 try: 269 s.add(_opcode(name)) 270 except KeyError: 271 pass 272 return s 273 274 # Opcodes that leave the code object. 275 OPS_CODE_END = _opcode_set('RETURN_VALUE') 276 277 # Opcodes that unconditionally end the code chunk. 278 OPS_CHUNK_END = _opcode_set( 279 'JUMP_ABSOLUTE', 'JUMP_FORWARD', 'RETURN_VALUE', 'RAISE_VARARGS', 280 'BREAK_LOOP', 'CONTINUE_LOOP', 281 ) 282 283 # Opcodes that unconditionally begin a new code chunk. By starting new chunks 284 # with unconditional jump instructions, we neatly deal with jumps to jumps 285 # properly. 286 OPS_CHUNK_BEGIN = _opcode_set('JUMP_ABSOLUTE', 'JUMP_FORWARD') 287 288 # Opcodes that push a block on the block stack. 289 OPS_PUSH_BLOCK = _opcode_set( 290 'SETUP_LOOP', 'SETUP_EXCEPT', 'SETUP_FINALLY', 'SETUP_WITH' 291 ) 292 293 # Block types for exception handling. 294 OPS_EXCEPT_BLOCKS = _opcode_set('SETUP_EXCEPT', 'SETUP_FINALLY') 295 296 # Opcodes that pop a block from the block stack. 297 OPS_POP_BLOCK = _opcode_set('POP_BLOCK') 298 299 # Opcodes that have a jump destination, but aren't really a jump. 300 OPS_NO_JUMP = OPS_PUSH_BLOCK 301 302 # Individual opcodes we need below. 303 OP_BREAK_LOOP = _opcode('BREAK_LOOP') 304 OP_END_FINALLY = _opcode('END_FINALLY') 305 OP_COMPARE_OP = _opcode('COMPARE_OP') 306 COMPARE_EXCEPTION = 10 # just have to get this const from the code. 307 OP_LOAD_CONST = _opcode('LOAD_CONST') 308 OP_RETURN_VALUE = _opcode('RETURN_VALUE') 309 310 311 class ByteParser(object): 312 """Parse byte codes to understand the structure of code.""" 313 314 def __init__(self, code=None, text=None, filename=None): 315 if code: 316 self.code = code 317 self.text = text 318 else: 319 if not text: 320 assert filename, "If no code or text, need a filename" 321 sourcef = open_source(filename) 322 try: 323 text = sourcef.read() 324 finally: 325 sourcef.close() 326 self.text = text 327 328 try: 329 # Python 2.3 and 2.4 don't like partial last lines, so be sure 330 # the text ends nicely for them. 331 self.code = compile(text + '\n', filename, "exec") 332 except SyntaxError: 333 _, synerr, _ = sys.exc_info() 334 raise NotPython( 335 "Couldn't parse '%s' as Python source: '%s' at line %d" % 336 (filename, synerr.msg, synerr.lineno) 337 ) 338 339 # Alternative Python implementations don't always provide all the 340 # attributes on code objects that we need to do the analysis. 341 for attr in ['co_lnotab', 'co_firstlineno', 'co_consts', 'co_code']: 342 if not hasattr(self.code, attr): 343 raise CoverageException( 344 "This implementation of Python doesn't support code " 345 "analysis.\n" 346 "Run coverage.py under CPython for this command." 347 ) 348 349 def child_parsers(self): 350 """Iterate over all the code objects nested within this one. 351 352 The iteration includes `self` as its first value. 353 354 """ 355 children = CodeObjects(self.code) 356 return [ByteParser(code=c, text=self.text) for c in children] 357 358 # Getting numbers from the lnotab value changed in Py3.0. 359 if sys.version_info >= (3, 0): 360 def _lnotab_increments(self, lnotab): 361 """Return a list of ints from the lnotab bytes in 3.x""" 362 return list(lnotab) 363 else: 364 def _lnotab_increments(self, lnotab): 365 """Return a list of ints from the lnotab string in 2.x""" 366 return [ord(c) for c in lnotab] 367 368 def _bytes_lines(self): 369 """Map byte offsets to line numbers in `code`. 370 371 Uses co_lnotab described in Python/compile.c to map byte offsets to 372 line numbers. Returns a list: [(b0, l0), (b1, l1), ...] 373 374 """ 375 # Adapted from dis.py in the standard library. 376 byte_increments = self._lnotab_increments(self.code.co_lnotab[0::2]) 377 line_increments = self._lnotab_increments(self.code.co_lnotab[1::2]) 378 379 bytes_lines = [] 380 last_line_num = None 381 line_num = self.code.co_firstlineno 382 byte_num = 0 383 for byte_incr, line_incr in zip(byte_increments, line_increments): 384 if byte_incr: 385 if line_num != last_line_num: 386 bytes_lines.append((byte_num, line_num)) 387 last_line_num = line_num 388 byte_num += byte_incr 389 line_num += line_incr 390 if line_num != last_line_num: 391 bytes_lines.append((byte_num, line_num)) 392 return bytes_lines 393 394 def _find_statements(self): 395 """Find the statements in `self.code`. 396 397 Return a set of line numbers that start statements. Recurses into all 398 code objects reachable from `self.code`. 399 400 """ 401 stmts = set() 402 for bp in self.child_parsers(): 403 # Get all of the lineno information from this code. 404 for _, l in bp._bytes_lines(): 405 stmts.add(l) 406 return stmts 407 408 def _split_into_chunks(self): 409 """Split the code object into a list of `Chunk` objects. 410 411 Each chunk is only entered at its first instruction, though there can 412 be many exits from a chunk. 413 414 Returns a list of `Chunk` objects. 415 416 """ 417 418 # The list of chunks so far, and the one we're working on. 419 chunks = [] 420 chunk = None 421 bytes_lines_map = dict(self._bytes_lines()) 422 423 # The block stack: loops and try blocks get pushed here for the 424 # implicit jumps that can occur. 425 # Each entry is a tuple: (block type, destination) 426 block_stack = [] 427 428 # Some op codes are followed by branches that should be ignored. This 429 # is a count of how many ignores are left. 430 ignore_branch = 0 431 432 # We have to handle the last two bytecodes specially. 433 ult = penult = None 434 435 for bc in ByteCodes(self.code.co_code): 436 # Maybe have to start a new chunk 437 if bc.offset in bytes_lines_map: 438 # Start a new chunk for each source line number. 439 if chunk: 440 chunk.exits.add(bc.offset) 441 chunk = Chunk(bc.offset, bytes_lines_map[bc.offset]) 442 chunks.append(chunk) 443 elif bc.op in OPS_CHUNK_BEGIN: 444 # Jumps deserve their own unnumbered chunk. This fixes 445 # problems with jumps to jumps getting confused. 446 if chunk: 447 chunk.exits.add(bc.offset) 448 chunk = Chunk(bc.offset) 449 chunks.append(chunk) 450 451 if not chunk: 452 chunk = Chunk(bc.offset) 453 chunks.append(chunk) 454 455 # Look at the opcode 456 if bc.jump_to >= 0 and bc.op not in OPS_NO_JUMP: 457 if ignore_branch: 458 # Someone earlier wanted us to ignore this branch. 459 ignore_branch -= 1 460 else: 461 # The opcode has a jump, it's an exit for this chunk. 462 chunk.exits.add(bc.jump_to) 463 464 if bc.op in OPS_CODE_END: 465 # The opcode can exit the code object. 466 chunk.exits.add(-self.code.co_firstlineno) 467 if bc.op in OPS_PUSH_BLOCK: 468 # The opcode adds a block to the block_stack. 469 block_stack.append((bc.op, bc.jump_to)) 470 if bc.op in OPS_POP_BLOCK: 471 # The opcode pops a block from the block stack. 472 block_stack.pop() 473 if bc.op in OPS_CHUNK_END: 474 # This opcode forces the end of the chunk. 475 if bc.op == OP_BREAK_LOOP: 476 # A break is implicit: jump where the top of the 477 # block_stack points. 478 chunk.exits.add(block_stack[-1][1]) 479 chunk = None 480 if bc.op == OP_END_FINALLY: 481 if block_stack: 482 # A break that goes through a finally will jump to whatever 483 # block is on top of the stack. 484 chunk.exits.add(block_stack[-1][1]) 485 # For the finally clause we need to find the closest exception 486 # block, and use its jump target as an exit. 487 for iblock in range(len(block_stack)-1, -1, -1): 488 if block_stack[iblock][0] in OPS_EXCEPT_BLOCKS: 489 chunk.exits.add(block_stack[iblock][1]) 490 break 491 if bc.op == OP_COMPARE_OP and bc.arg == COMPARE_EXCEPTION: 492 # This is an except clause. We want to overlook the next 493 # branch, so that except's don't count as branches. 494 ignore_branch += 1 495 496 penult = ult 497 ult = bc 498 499 if chunks: 500 # The last two bytecodes could be a dummy "return None" that 501 # shouldn't be counted as real code. Every Python code object seems 502 # to end with a return, and a "return None" is inserted if there 503 # isn't an explicit return in the source. 504 if ult and penult: 505 if penult.op == OP_LOAD_CONST and ult.op == OP_RETURN_VALUE: 506 if self.code.co_consts[penult.arg] is None: 507 # This is "return None", but is it dummy? A real line 508 # would be a last chunk all by itself. 509 if chunks[-1].byte != penult.offset: 510 ex = -self.code.co_firstlineno 511 # Split the last chunk 512 last_chunk = chunks[-1] 513 last_chunk.exits.remove(ex) 514 last_chunk.exits.add(penult.offset) 515 chunk = Chunk(penult.offset) 516 chunk.exits.add(ex) 517 chunks.append(chunk) 518 519 # Give all the chunks a length. 520 chunks[-1].length = bc.next_offset - chunks[-1].byte 521 for i in range(len(chunks)-1): 522 chunks[i].length = chunks[i+1].byte - chunks[i].byte 523 524 return chunks 525 526 def _arcs(self): 527 """Find the executable arcs in the code. 528 529 Returns a set of pairs, (from,to). From and to are integer line 530 numbers. If from is < 0, then the arc is an entrance into the code 531 object. If to is < 0, the arc is an exit from the code object. 532 533 """ 534 chunks = self._split_into_chunks() 535 536 # A map from byte offsets to chunks jumped into. 537 byte_chunks = dict([(c.byte, c) for c in chunks]) 538 539 # Build a map from byte offsets to actual lines reached. 540 byte_lines = {} 541 bytes_to_add = set([c.byte for c in chunks]) 542 543 while bytes_to_add: 544 byte_to_add = bytes_to_add.pop() 545 if byte_to_add in byte_lines or byte_to_add < 0: 546 continue 547 548 # Which lines does this chunk lead to? 549 bytes_considered = set() 550 bytes_to_consider = [byte_to_add] 551 lines = set() 552 553 while bytes_to_consider: 554 byte = bytes_to_consider.pop() 555 bytes_considered.add(byte) 556 557 # Find chunk for byte 558 try: 559 ch = byte_chunks[byte] 560 except KeyError: 561 for ch in chunks: 562 if ch.byte <= byte < ch.byte+ch.length: 563 break 564 else: 565 # No chunk for this byte! 566 raise Exception("Couldn't find chunk @ %d" % byte) 567 byte_chunks[byte] = ch 568 569 if ch.line: 570 lines.add(ch.line) 571 else: 572 for ex in ch.exits: 573 if ex < 0: 574 lines.add(ex) 575 elif ex not in bytes_considered: 576 bytes_to_consider.append(ex) 577 578 bytes_to_add.update(ch.exits) 579 580 byte_lines[byte_to_add] = lines 581 582 # Figure out for each chunk where the exits go. 583 arcs = set() 584 for chunk in chunks: 585 if chunk.line: 586 for ex in chunk.exits: 587 if ex < 0: 588 exit_lines = [ex] 589 else: 590 exit_lines = byte_lines[ex] 591 for exit_line in exit_lines: 592 if chunk.line != exit_line: 593 arcs.add((chunk.line, exit_line)) 594 for line in byte_lines[0]: 595 arcs.add((-1, line)) 596 597 return arcs 598 599 def _all_chunks(self): 600 """Returns a list of `Chunk` objects for this code and its children. 601 602 See `_split_into_chunks` for details. 603 604 """ 605 chunks = [] 606 for bp in self.child_parsers(): 607 chunks.extend(bp._split_into_chunks()) 608 609 return chunks 610 611 def _all_arcs(self): 612 """Get the set of all arcs in this code object and its children. 613 614 See `_arcs` for details. 615 616 """ 617 arcs = set() 618 for bp in self.child_parsers(): 619 arcs.update(bp._arcs()) 620 621 return arcs 622 623 624 class Chunk(object): 625 """A sequence of bytecodes with a single entrance. 626 627 To analyze byte code, we have to divide it into chunks, sequences of byte 628 codes such that each basic block has only one entrance, the first 629 instruction in the block. 630 631 This is almost the CS concept of `basic block`_, except that we're willing 632 to have many exits from a chunk, and "basic block" is a more cumbersome 633 term. 634 635 .. _basic block: http://en.wikipedia.org/wiki/Basic_block 636 637 An exit < 0 means the chunk can leave the code (return). The exit is 638 the negative of the starting line number of the code block. 639 640 """ 641 def __init__(self, byte, line=0): 642 self.byte = byte 643 self.line = line 644 self.length = 0 645 self.exits = set() 646 647 def __repr__(self): 648 return "<%d+%d @%d %r>" % ( 649 self.byte, self.length, self.line, list(self.exits) 650 ) 651