New feature: show line number correspondences

Option --linemap on uncompile show how original source-code line numbers
map to uncompiled source lines
This commit is contained in:
rocky
2016-11-18 09:02:00 -05:00
parent 0f719d41fd
commit d7f898b4fb
4 changed files with 45 additions and 14 deletions

View File

@@ -24,6 +24,6 @@ setup(
py_modules = py_modules,
test_suite = 'nose.collector',
url = web,
test_requires = ['nose>=1.0'],
tests_require = ['nose>=1.0'],
version = VERSION,
zip_safe = zip_safe)

View File

@@ -5,7 +5,7 @@
# Copyright (c) 2000-2002 by hartmut Goebel <h.goebel@crazy-compilers.com>
#
from __future__ import print_function
import sys, os, getopt, tempfile, time
import sys, os, getopt, time
program, ext = os.path.splitext(os.path.basename(__file__))
@@ -35,7 +35,8 @@ Options:
-p <integer> use <integer> number of processes
-r recurse directories looking for .pyc and .pyo files
--verify compare generated source with input byte-code
(requires -o)
--linemaps generated line number correspondencies between byte-code
and generated source output
--help show this message
Debugging Options:
@@ -81,8 +82,8 @@ def main_bin():
try:
opts, files = getopt.getopt(sys.argv[1:], 'hagtdrVo:c:p:',
'help asm grammar recurse timestamp tree verify version '
'showgrammar'.split(' '))
'help asm grammar linemaps recurse timestamp tree '
'verify version showgrammar'.split(' '))
except getopt.GetoptError as e:
print('%s: %s' % (os.path.basename(sys.argv[0]), e), file=sys.stderr)
sys.exit(-1)
@@ -97,6 +98,8 @@ def main_bin():
sys.exit(0)
elif opt == '--verify':
options['do_verify'] = True
elif opt == '--linemaps':
options['do_linemaps'] = True
elif opt in ('--asm', '-a'):
options['showasm'] = 'after'
options['do_verify'] = False
@@ -146,11 +149,7 @@ def main_bin():
usage()
if outfile == '-':
if 'do_verify' in options and options['do_verify'] and len(files) == 1:
junk, outfile = tempfile.mkstemp(suffix=".pyc",
prefix=files[0][0:-4]+'-')
else:
outfile = None # use stdout
outfile = None # use stdout
elif outfile and os.path.isdir(outfile):
out_base = outfile; outfile = None
elif outfile and len(files) > 1:

14
uncompyle6/linenumbers.py Normal file
View File

@@ -0,0 +1,14 @@
from xdis.load import load_file, load_module
from xdis.bytecode import findlinestarts, offset2line
def line_number_mapping(pyc_filename, src_filename):
(version, timestamp, magic_int, code_obj1, is_pypy,
source_size) = load_module(pyc_filename)
try:
code_obj2 = load_file(src_filename)
except SyntaxError as e:
return str(e)
linestarts_orig = findlinestarts(code_obj1)
linestarts_uncompiled = list(findlinestarts(code_obj2))
return [[line, offset2line(offset, linestarts_uncompiled)] for offset, line in linestarts_orig]

View File

@@ -1,5 +1,5 @@
from __future__ import print_function
import datetime, os, sys
import datetime, os, subprocess, sys, tempfile
from uncompyle6 import verify, IS_PYPY
from xdis.code import iscode
@@ -7,6 +7,7 @@ from uncompyle6.disas import check_object_path
from uncompyle6.semantics import pysource
from uncompyle6.parser import ParserError
from uncompyle6.version import VERSION
from uncompyle6.linenumbers import line_number_mapping
from xdis.load import load_module
@@ -76,7 +77,8 @@ def uncompyle_file(filename, outstream=None, showasm=None, showast=False,
# FIXME: combine into an options parameter
def main(in_base, out_base, files, codes, outfile=None,
showasm=None, showast=False, do_verify=False,
showgrammar=False, raise_on_error=False):
showgrammar=False, raise_on_error=False,
do_linemaps=False):
"""
in_base base directory for input files
out_base base directory for output files (ignored when
@@ -99,7 +101,6 @@ def main(in_base, out_base, files, codes, outfile=None,
pass
return open(outfile, 'w')
of = outfile
tot_files = okay_files = failed_files = verify_failed_files = 0
# for code in codes:
@@ -117,10 +118,21 @@ def main(in_base, out_base, files, codes, outfile=None,
# print (infile, file=sys.stderr)
if of: # outfile was given as parameter
if outfile: # outfile was given as parameter
outstream = _get_outstream(outfile)
elif out_base is None:
outstream = sys.stdout
if do_linemaps or do_verify:
prefix = os.path.basename(filename)
if prefix.endswith('.py'):
prefix = prefix[:-len('.py')]
junk, outfile = tempfile.mkstemp(suffix=".pyc",
prefix=prefix)
# Unbuffer output
sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)
tee = subprocess.Popen(["tee", outfile], stdin=subprocess.PIPE)
os.dup2(tee.stdin.fileno(), sys.stdout.fileno())
os.dup2(tee.stdin.fileno(), sys.stderr.fileno())
else:
if filename.endswith('.pyc'):
outfile = os.path.join(out_base, filename[0:-1])
@@ -152,7 +164,13 @@ def main(in_base, out_base, files, codes, outfile=None,
# sys.stderr.write("\n# Can't uncompile %s\n" % infile)
else: # uncompile successful
if outfile:
if do_linemaps:
mapping = line_number_mapping(infile, outfile)
print("\n\n## Line number correspondences", infile)
for m in mapping:
print("## %s" % m, infile)
outstream.close()
if do_verify:
weak_verify = do_verify == 'weak'
try: