def _split_into_chunks(self): """Split the code object into a list of `Chunk` objects. Each chunk is only entered at its first instruction, though there can be many exits from a chunk. Returns a list of `Chunk` objects. """ # The list of chunks so far, and the one we're working on. chunks = [] chunk = None bytes_lines_map = dict(self._bytes_lines()) # The block stack: loops and try blocks get pushed here for the # implicit jumps that can occur. # Each entry is a tuple: (block type, destination) block_stack = [] # Some op codes are followed by branches that should be ignored. This # is a count of how many ignores are left. ignore_branch = 0 # We have to handle the last two bytecodes specially. ult = penult = None for bc in ByteCodes(self.code.co_code): # Maybe have to start a new chunk if bc.offset in bytes_lines_map: # Start a new chunk for each source line number. if chunk: chunk.exits.add(bc.offset) chunk = Chunk(bc.offset, bytes_lines_map[bc.offset]) chunks.append(chunk) elif bc.op in OPS_CHUNK_BEGIN: # Jumps deserve their own unnumbered chunk. This fixes # problems with jumps to jumps getting confused. if chunk: chunk.exits.add(bc.offset) chunk = Chunk(bc.offset) chunks.append(chunk) if not chunk: chunk = Chunk(bc.offset) chunks.append(chunk) # Look at the opcode if bc.jump_to >= 0 and bc.op not in OPS_NO_JUMP: if ignore_branch: # Someone earlier wanted us to ignore this branch. ignore_branch -= 1 else: # The opcode has a jump, it's an exit for this chunk. chunk.exits.add(bc.jump_to) if bc.op in OPS_CODE_END: # The opcode can exit the code object. chunk.exits.add(-self.code.co_firstlineno) if bc.op in OPS_PUSH_BLOCK: # The opcode adds a block to the block_stack. block_stack.append((bc.op, bc.jump_to)) if bc.op in OPS_POP_BLOCK: # The opcode pops a block from the block stack. block_stack.pop() if bc.op in OPS_CHUNK_END: # This opcode forces the end of the chunk. if bc.op == OP_BREAK_LOOP: # A break is implicit: jump where the top of the # block_stack points. chunk.exits.add(block_stack[-1][1]) chunk = None if bc.op == OP_END_FINALLY: if block_stack: # A break that goes through a finally will jump to whatever # block is on top of the stack. chunk.exits.add(block_stack[-1][1]) # For the finally clause we need to find the closest exception # block, and use its jump target as an exit. for iblock in range(len(block_stack)-1, -1, -1): if block_stack[iblock][0] in OPS_EXCEPT_BLOCKS: chunk.exits.add(block_stack[iblock][1]) break if bc.op == OP_COMPARE_OP and bc.arg == COMPARE_EXCEPTION: # This is an except clause. We want to overlook the next # branch, so that except's don't count as branches. ignore_branch += 1 penult = ult ult = bc if chunks: # The last two bytecodes could be a dummy "return None" that # shouldn't be counted as real code. Every Python code object seems # to end with a return, and a "return None" is inserted if there # isn't an explicit return in the source. if ult and penult: if penult.op == OP_LOAD_CONST and ult.op == OP_RETURN_VALUE: if self.code.co_consts[penult.arg] is None: # This is "return None", but is it dummy? A real line # would be a last chunk all by itself. if chunks[-1].byte != penult.offset: ex = -self.code.co_firstlineno # Split the last chunk last_chunk = chunks[-1] last_chunk.exits.remove(ex) last_chunk.exits.add(penult.offset) chunk = Chunk(penult.offset) chunk.exits.add(ex) chunks.append(chunk) # Give all the chunks a length. chunks[-1].length = bc.next_offset - chunks[-1].byte # pylint: disable=W0631,C0301 for i in range(len(chunks)-1): chunks[i].length = chunks[i+1].byte - chunks[i].byte return chunks
def _split_into_chunks(self): chunks = [] chunk = None bytes_lines_map = dict(self._bytes_lines()) block_stack = [] ignore_branch = 0 ult = penult = None jump_to = set() for bc in ByteCodes(self.code.co_code): if bc.jump_to >= 0: jump_to.add(bc.jump_to) chunk_lineno = 0 for bc in ByteCodes(self.code.co_code): start_new_chunk = False first_chunk = False if bc.offset in bytes_lines_map: start_new_chunk = True chunk_lineno = bytes_lines_map[bc.offset] first_chunk = True elif bc.offset in jump_to: start_new_chunk = True elif bc.op in OPS_CHUNK_BEGIN: start_new_chunk = True if not chunk or start_new_chunk: if chunk: chunk.exits.add(bc.offset) chunk = Chunk(bc.offset, chunk_lineno, first_chunk) chunks.append(chunk) if bc.jump_to >= 0 and bc.op not in OPS_NO_JUMP: if ignore_branch: ignore_branch -= 1 else: chunk.exits.add(bc.jump_to) if bc.op in OPS_CODE_END: chunk.exits.add(-self.code.co_firstlineno) if bc.op in OPS_PUSH_BLOCK: block_stack.append((bc.op, bc.jump_to)) if bc.op in OPS_POP_BLOCK: block_stack.pop() if bc.op in OPS_CHUNK_END: if bc.op == OP_BREAK_LOOP: chunk.exits.add(block_stack[-1][1]) chunk = None if bc.op == OP_END_FINALLY: for block in reversed(block_stack): if block[0] in OPS_EXCEPT_BLOCKS: chunk.exits.add(block[1]) break if bc.op == OP_COMPARE_OP and bc.arg == COMPARE_EXCEPTION: ignore_branch += 1 penult = ult ult = bc if chunks: if ult and penult: if penult.op == OP_LOAD_CONST and ult.op == OP_RETURN_VALUE: if self.code.co_consts[penult.arg] is None: if chunks[-1].byte != penult.offset: ex = -self.code.co_firstlineno last_chunk = chunks[-1] last_chunk.exits.remove(ex) last_chunk.exits.add(penult.offset) chunk = Chunk(penult.offset, last_chunk.line, False) chunk.exits.add(ex) chunks.append(chunk) chunks[-1].length = bc.next_offset - chunks[-1].byte for i in range(len(chunks) - 1): chunks[i].length = chunks[i + 1].byte - chunks[i].byte return chunks