OLD | NEW |
(Empty) | |
| 1 # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 |
| 2 # For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt |
| 3 |
| 4 """ Wicked hack to get .pyc files to do bytecode tracing instead of |
| 5 line tracing. |
| 6 """ |
| 7 |
| 8 import marshal, new, opcode, sys, types |
| 9 |
| 10 from lnotab import lnotab_numbers, lnotab_string |
| 11 |
| 12 class PycFile: |
| 13 def read(self, f): |
| 14 if isinstance(f, basestring): |
| 15 f = open(f, "rb") |
| 16 self.magic = f.read(4) |
| 17 self.modtime = f.read(4) |
| 18 self.code = marshal.load(f) |
| 19 |
| 20 def write(self, f): |
| 21 if isinstance(f, basestring): |
| 22 f = open(f, "wb") |
| 23 f.write(self.magic) |
| 24 f.write(self.modtime) |
| 25 marshal.dump(self.code, f) |
| 26 |
| 27 def hack_line_numbers(self): |
| 28 self.code = hack_line_numbers(self.code) |
| 29 |
| 30 def hack_line_numbers(code): |
| 31 """ Replace a code object's line number information to claim that every |
| 32 byte of the bytecode is a new source line. Returns a new code |
| 33 object. Also recurses to hack the line numbers in nested code objects. |
| 34 """ |
| 35 |
| 36 # Create a new lnotab table. Each opcode is claimed to be at |
| 37 # 1000*lineno + (opcode number within line), so for example, the opcodes on |
| 38 # source line 12 will be given new line numbers 12000, 12001, 12002, etc. |
| 39 old_num = list(lnotab_numbers(code.co_lnotab, code.co_firstlineno)) |
| 40 n_bytes = len(code.co_code) |
| 41 new_num = [] |
| 42 line = 0 |
| 43 opnum_in_line = 0 |
| 44 i_byte = 0 |
| 45 while i_byte < n_bytes: |
| 46 if old_num and i_byte == old_num[0][0]: |
| 47 line = old_num.pop(0)[1] |
| 48 opnum_in_line = 0 |
| 49 new_num.append((i_byte, 100000000 + 1000*line + opnum_in_line)) |
| 50 if ord(code.co_code[i_byte]) >= opcode.HAVE_ARGUMENT: |
| 51 i_byte += 3 |
| 52 else: |
| 53 i_byte += 1 |
| 54 opnum_in_line += 1 |
| 55 |
| 56 # new_num is a list of pairs, (byteoff, lineoff). Turn it into an lnotab. |
| 57 new_firstlineno = new_num[0][1]-1 |
| 58 new_lnotab = lnotab_string(new_num, new_firstlineno) |
| 59 |
| 60 # Recurse into code constants in this code object. |
| 61 new_consts = [] |
| 62 for const in code.co_consts: |
| 63 if type(const) == types.CodeType: |
| 64 new_consts.append(hack_line_numbers(const)) |
| 65 else: |
| 66 new_consts.append(const) |
| 67 |
| 68 # Create a new code object, just like the old one, except with new |
| 69 # line numbers. |
| 70 new_code = new.code( |
| 71 code.co_argcount, code.co_nlocals, code.co_stacksize, code.co_flags, |
| 72 code.co_code, tuple(new_consts), code.co_names, code.co_varnames, |
| 73 code.co_filename, code.co_name, new_firstlineno, new_lnotab |
| 74 ) |
| 75 |
| 76 return new_code |
| 77 |
| 78 def hack_file(f): |
| 79 pyc = PycFile() |
| 80 pyc.read(f) |
| 81 pyc.hack_line_numbers() |
| 82 pyc.write(f) |
| 83 |
| 84 if __name__ == '__main__': |
| 85 hack_file(sys.argv[1]) |
OLD | NEW |