Skip to content

Commit

Permalink
Showing 5 changed files with 119 additions and 92 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@
*.pyo
*_dis
*~
.mypy_cache
/.cache
/.eggs
/.hypothesis
@@ -10,7 +11,6 @@
/.pytest_cache
/.python-version
/.tox
.mypy_cache
/.venv*
/README
/__pkginfo__.pyc
@@ -20,6 +20,7 @@
/tmp
/uncompyle6.egg-info
/unpyc
/venv
ChangeLog
__pycache__
build
2 changes: 1 addition & 1 deletion test/test_pythonlib.py
Original file line number Diff line number Diff line change
@@ -216,7 +216,7 @@ def file_matches(files, root, basenames, patterns):
print("Output directory: ", target_dir)
try:
_, _, failed_files, failed_verify = main(
src_dir, target_dir, files, [], do_verify=opts["do_verify"]
src_dir, target_dir, files, []
)
if failed_files != 0:
sys.exit(2)
44 changes: 21 additions & 23 deletions uncompyle6/main.py
Original file line number Diff line number Diff line change
@@ -90,7 +90,7 @@ def write(s):
run_pypy_str = "PyPy " if IS_PYPY else ""
sys_version_lines = sys.version.split("\n")
if source_encoding:
write("# -*- coding: %s -*-" % source_encoding)
write(f"# -*- coding: {source_encoding} -*-")
write(
"# uncompyle6 version %s\n"
"# %sPython bytecode version base %s%s\n# Decompiled from: %sPython %s"
@@ -104,9 +104,9 @@ def write(s):
)
)
if co.co_filename:
write("# Embedded file name: %s" % co.co_filename)
write(f"# Embedded file name: {co.co_filename}")
if timestamp:
write("# Compiled at: %s" % datetime.datetime.fromtimestamp(timestamp))
write(f"# Compiled at: {datetime.datetime.fromtimestamp(timestamp)}")
if source_size:
write("# Size of source mod 2**32: %d bytes" % source_size)

@@ -129,13 +129,14 @@ def write(s):
version=bytecode_version,
code_objects=code_objects,
is_pypy=is_pypy,
debug_opts=debug_opts,
)
header_count = 3 + len(sys_version_lines)
linemap = [
(line_no, deparsed.source_linemap[line_no] + header_count)
for line_no in sorted(deparsed.source_linemap.keys())
]
mapstream.write("\n\n# %s\n" % linemap)
mapstream.write(f"\n\n# {linemap}\n")
else:
if do_fragments:
deparse_fn = code_deparse_fragments
@@ -163,11 +164,11 @@ def compile_file(source_path: str) -> str:
basename = source_path

if hasattr(sys, "pypy_version_info"):
bytecode_path = "%s-pypy%s.pyc" % (basename, version_tuple_to_str())
bytecode_path = f"{basename}-pypy{version_tuple_to_str()}.pyc"
else:
bytecode_path = "%s-%s.pyc" % (basename, version_tuple_to_str())
bytecode_path = f"{basename}-{version_tuple_to_str()}.pyc"

print("compiling %s to %s" % (source_path, bytecode_path))
print(f"compiling {source_path} to {bytecode_path}")
py_compile.compile(source_path, bytecode_path, "exec")
return bytecode_path

@@ -232,7 +233,6 @@ def decompile_file(
compile_mode="exec",
)
]
co = None
return deparsed


@@ -245,7 +245,6 @@ def main(
outfile=None,
showasm: Optional[str] = None,
showast={},
do_verify=False,
showgrammar=False,
source_encoding=None,
raise_on_error=False,
@@ -274,7 +273,7 @@ def main(
infile = os.path.join(in_base, filename)
# print("XXX", infile)
if not os.path.exists(infile):
sys.stderr.write("File '%s' doesn't exist. Skipped\n" % infile)
sys.stderr.write(f"File '{infile}' doesn't exist. Skipped\n")
continue

if do_linemaps:
@@ -322,13 +321,13 @@ def main(
):
if e[0] != last_mod:
line = "=" * len(e[0])
outstream.write("%s\n%s\n%s\n" % (line, e[0], line))
outstream.write(f"{line}\n{e[0]}\n{line}\n")
last_mod = e[0]
info = offsets[e]
extractInfo = d.extract_node_info(info)
outstream.write("%s" % info.node.format().strip() + "\n")
outstream.write(extractInfo.selectedLine + "\n")
outstream.write(extractInfo.markerLine + "\n\n")
extract_info = d.extract_node_info(info)
outstream.write(f"{info.node.format().strip()}" + "\n")
outstream.write(extract_info.selectedLine + "\n")
outstream.write(extract_info.markerLine + "\n\n")
pass
pass
tot_files += 1
@@ -349,14 +348,14 @@ def main(
if str(e).startswith("Unsupported Python"):
sys.stdout.write("\n")
sys.stderr.write(
"\n# Unsupported bytecode in file %s\n# %s\n" % (infile, e)
f"\n# Unsupported bytecode in file {infile}\n# {e}\n"
)
else:
if outfile:
outstream.close()
os.remove(outfile)
sys.stdout.write("\n")
sys.stderr.write("\nLast file: %s " % (infile))
sys.stderr.write(f"\nLast file: {infile} ")
raise

# except:
@@ -376,15 +375,14 @@ def main(
okay_files += 1
if not current_outfile:
mess = "\n# okay decompiling"
# mem_usage = __memUsage()
# mem_usage = __mem_usage()
print(mess, infile)
if current_outfile:
sys.stdout.write(
"%s -- %s\r"
% (
infile,
status_msg(
do_verify,
tot_files,
okay_files,
failed_files,
@@ -405,26 +403,26 @@ def main(
except Exception:
pass
pass
return (tot_files, okay_files, failed_files, verify_failed_files)
return tot_files, okay_files, failed_files, verify_failed_files


# ---- main ----

if sys.platform.startswith("linux") and os.uname()[2][:2] in ["2.", "3.", "4."]:

def __memUsage():
def __mem_sage():
mi = open("/proc/self/stat", "r")
mu = mi.readline().split()[22]
mi.close()
return int(mu) / 1000000

else:

def __memUsage():
def __mem_usage():
return ""


def status_msg(do_verify, tot_files, okay_files, failed_files, verify_failed_files):
def status_msg(tot_files, okay_files, failed_files, verify_failed_files):
if tot_files == 1:
if failed_files:
return "\n# decompile failed"
14 changes: 9 additions & 5 deletions uncompyle6/semantics/linemap.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Copyright (c) 2018, 2024 by Rocky Bernstein
#

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
@@ -13,7 +14,10 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

from uncompyle6.semantics.fragments import FragmentsWalker, code_deparse as fragments_code_deparse
from uncompyle6.semantics.fragments import (
FragmentsWalker,
code_deparse as fragments_code_deparse,
)
from uncompyle6.semantics.pysource import SourceWalker, code_deparse


@@ -25,9 +29,9 @@ def __init__(self, *args, **kwargs):
self.current_line_number = 1

def write(self, *data):
"""Augment write routine to keep track of current line"""
"""Augment write routine to keep track of current line."""
for line in data:
## print("XXX write: '%s'" % l)
# print(f"XXX write: '{line}'")
for i in str(line):
if i == "\n":
self.current_line_number += 1
@@ -39,7 +43,7 @@ def write(self, *data):
# Note n_expr needs treatment too

def default(self, node):
"""Augment write default routine to record line number changes"""
"""Augment default-write routine to record line number changes."""
if hasattr(node, "linestart"):
if node.linestart:
self.source_linemap[self.current_line_number] = node.linestart
@@ -85,7 +89,7 @@ def code_deparse_with_fragments_and_map(*args, **kwargs):
if __name__ == "__main__":

def deparse_test(co):
"This is a docstring"
"""This is a docstring"""
deparsed = code_deparse_with_map(co)
a = 1
b = 2
148 changes: 86 additions & 62 deletions uncompyle6/semantics/pysource.py
Original file line number Diff line number Diff line change
@@ -141,17 +141,25 @@
from uncompyle6.scanner import Code, get_scanner
from uncompyle6.scanners.tok import Token
from uncompyle6.semantics.check_ast import checker
from uncompyle6.semantics.consts import (ASSIGN_TUPLE_PARAM,
INDENT_PER_LEVEL, LINE_LENGTH, MAP,
MAP_DIRECT, NAME_MODULE, NONE, PASS,
PRECEDENCE, RETURN_LOCALS,
RETURN_NONE, TAB, TABLE_R, escape)
from uncompyle6.semantics.consts import (
ASSIGN_TUPLE_PARAM,
INDENT_PER_LEVEL,
LINE_LENGTH,
MAP,
MAP_DIRECT,
NAME_MODULE,
NONE,
PASS,
PRECEDENCE,
RETURN_LOCALS,
RETURN_NONE,
TAB,
TABLE_R,
escape,
)
from uncompyle6.semantics.customize import customize_for_version
from uncompyle6.semantics.gencomp import ComprehensionMixin
from uncompyle6.semantics.helper import (
find_globals_and_nonlocals,
print_docstring
)
from uncompyle6.semantics.helper import find_globals_and_nonlocals, print_docstring
from uncompyle6.semantics.make_function1 import make_function1
from uncompyle6.semantics.make_function2 import make_function2
from uncompyle6.semantics.make_function3 import make_function3
@@ -162,9 +170,11 @@
from uncompyle6.show import maybe_show_tree
from uncompyle6.util import better_repr

DEFAULT_DEBUG_OPTS = {"asm": False, "tree": False, "grammar": False}

def unicode(x): return x
def unicode(x):
return x


from io import StringIO

PARSER_DEFAULT_DEBUG = {
@@ -200,6 +210,7 @@ class SourceWalker(GenericASTTraversal, NonterminalActions, ComprehensionMixin):
Class to traverses a Parse Tree of the bytecode instruction built from parsing to produce some sort of source text.
The Parse tree may be turned an Abstract Syntax tree as an intermediate step.
"""

stacked_params = ("f", "indent", "is_lambda", "_globals")

def __init__(
@@ -288,7 +299,7 @@ def __init__(
self.in_format_string = None

# hide_internal suppresses displaying the additional instructions that sometimes
# exist in code but but were not written in the source code.
# exist in code but were not written in the source code.
# An example is:
# __module__ = __name__
self.hide_internal = True
@@ -355,7 +366,6 @@ def str_with_template1(self, ast, indent, sibNum=None) -> str:
indent += " "
i = 0
for node in ast:

if hasattr(node, "__repr1__"):
if enumerate_children:
child = self.str_with_template1(node, indent, i)
@@ -375,9 +385,9 @@ def str_with_template1(self, ast, indent, sibNum=None) -> str:
i += 1
return rv

def indent_if_source_nl(self, line_number, indent):
def indent_if_source_nl(self, line_number: int, indent: int):
if line_number != self.line_number:
self.write("\n" + self.indent + INDENT_PER_LEVEL[:-1])
self.write("\n" + indent + INDENT_PER_LEVEL[:-1])
return self.line_number

f = property(
@@ -685,7 +695,7 @@ def kv_map(self, kv_node, sep, line_number, indent):

def template_engine(self, entry, startnode):
"""The format template interpretation engine. See the comment at the
beginning of this module for the how we interpret format
beginning of this module for how we interpret format
specifications such as %c, %C, and so on.
"""

@@ -729,20 +739,31 @@ def template_engine(self, entry, startnode):
if isinstance(index[1], str):
# if node[index[0]] != index[1]:
# from trepan.api import debug; debug()
assert node[index[0]] == index[1], (
"at %s[%d], expected '%s' node; got '%s'"
% (node.kind, arg, index[1], node[index[0]].kind,)
assert (
node[index[0]] == index[1]
), "at %s[%d], expected '%s' node; got '%s'" % (
node.kind,
arg,
index[1],
node[index[0]].kind,
)
else:
assert node[index[0]] in index[1], (
"at %s[%d], expected to be in '%s' node; got '%s'"
% (node.kind, arg, index[1], node[index[0]].kind,)
assert (
node[index[0]] in index[1]
), "at %s[%d], expected to be in '%s' node; got '%s'" % (
node.kind,
arg,
index[1],
node[index[0]].kind,
)

index = index[0]
assert isinstance(index, int), (
"at %s[%d], %s should be int or tuple"
% (node.kind, arg, type(index),)
assert isinstance(
index, int
), "at %s[%d], %s should be int or tuple" % (
node.kind,
arg,
type(index),
)

try:
@@ -765,14 +786,22 @@ def template_engine(self, entry, startnode):
if len(tup) == 3:
(index, nonterm_name, self.prec) = tup
if isinstance(tup[1], str):
assert node[index] == nonterm_name, (
"at %s[%d], expected '%s' node; got '%s'"
% (node.kind, arg, nonterm_name, node[index].kind,)
assert (
node[index] == nonterm_name
), "at %s[%d], expected '%s' node; got '%s'" % (
node.kind,
arg,
nonterm_name,
node[index].kind,
)
else:
assert node[tup[0]] in tup[1], (
"at %s[%d], expected to be in '%s' node; got '%s'"
% (node.kind, arg, index[1], node[index[0]].kind,)
assert (
node[tup[0]] in tup[1]
), "at %s[%d], expected to be in '%s' node; got '%s'" % (
node.kind,
arg,
index[1],
node[index[0]].kind,
)

else:
@@ -885,52 +914,51 @@ def customize(self, customize):
"CALL_FUNCTION_VAR_KW",
"CALL_FUNCTION_KW",
):

# FIXME: handle everything in customize.
# Right now, some of this is here, and some in that.

if v == 0:
str = "%c(%C" # '%C' is a dummy here ...
p2 = (0, 0, None) # .. because of the None in this
template_str = "%c(%C" # '%C' is a dummy here ...
p2 = (0, 0, None) # because of the None in this
else:
str = "%c(%C, "
template_str = "%c(%C, "
p2 = (1, -2, ", ")
if op == "CALL_FUNCTION_VAR":
# Python 3.5 only puts optional args (the VAR part)
# the lowest down the stack
if self.version == (3, 5):
if str == "%c(%C, ":
if template_str == "%c(%C, ":
entry = ("%c(*%C, %c)", 0, p2, -2)
elif str == "%c(%C":
elif template_str == "%c(%C":
entry = ("%c(*%C)", 0, (1, 100, ""))
elif self.version == (3, 4):
# CALL_FUNCTION_VAR's top element of the stack contains
# the variable argument list
if v == 0:
str = "%c(*%c)"
entry = (str, 0, -2)
template_str = "%c(*%c)"
entry = (template_str, 0, -2)
else:
str = "%c(%C, *%c)"
entry = (str, 0, p2, -2)
template_str = "%c(%C, *%c)"
entry = (template_str, 0, p2, -2)
else:
str += "*%c)"
entry = (str, 0, p2, -2)
template_str += "*%c)"
entry = (template_str, 0, p2, -2)
elif op == "CALL_FUNCTION_KW":
str += "**%c)"
entry = (str, 0, p2, -2)
template_str += "**%c)"
entry = (template_str, 0, p2, -2)
elif op == "CALL_FUNCTION_VAR_KW":
str += "*%c, **%c)"
template_str += "*%c, **%c)"
# Python 3.5 only puts optional args (the VAR part)
# the lowest down the stack
na = v & 0xFF # positional parameters
if self.version == (3, 5) and na == 0:
if p2[2]:
p2 = (2, -2, ", ")
entry = (str, 0, p2, 1, -2)
entry = (template_str, 0, p2, 1, -2)
else:
if p2[2]:
p2 = (1, -3, ", ")
entry = (str, 0, p2, -3, -2)
entry = (template_str, 0, p2, -3, -2)
pass
else:
assert False, "Unhandled CALL_FUNCTION %s" % op
@@ -1014,7 +1042,7 @@ def build_class(self, code):
if ast[0] == "sstmt":
ast[0] = ast[0][0]
first_stmt = ast[0]
except:
except Exception:
pass

try:
@@ -1023,7 +1051,7 @@ def build_class(self, code):
del ast[0]
first_stmt = ast[0]
pass
except:
except Exception:
pass

have_qualname = False
@@ -1035,17 +1063,15 @@ def build_class(self, code):
if self.version < (3, 0):
# Should we ditch this in favor of the "else" case?
qualname = ".".join(self.classes)
QUAL_NAME = SyntaxTree(
qual_name_tree = SyntaxTree(
"assign",
[
SyntaxTree("expr", [Token("LOAD_CONST", pattr=qualname)]),
SyntaxTree(
"store", [Token("STORE_NAME", pattr="__qualname__")]
),
SyntaxTree("store", [Token("STORE_NAME", pattr="__qualname__")]),
],
)
# FIXME: is this right now that we've redone the grammar?
have_qualname = ast[0] == QUAL_NAME
have_qualname = ast[0] == qual_name_tree
else:
# Python 3.4+ has constants like 'cmp_to_key.<locals>.K'
# which are not simple classes like the < 3 case.
@@ -1057,7 +1083,7 @@ def build_class(self, code):
and first_stmt[1][0] == Token("STORE_NAME", pattr="__qualname__")
):
have_qualname = True
except:
except Exception:
pass

if have_qualname:
@@ -1078,7 +1104,7 @@ def build_class(self, code):
try:
# FIXME: Is there an extra [0]?
docstring = ast[i][0][0][0][0].pattr
except:
except Exception:
docstring = code.co_consts[0]
if print_docstring(self, indent, docstring):
self.println()
@@ -1104,7 +1130,6 @@ def build_class(self, code):
# else:
# print stmt[-1]


globals, nonlocals = find_globals_and_nonlocals(
ast, set(), set(), code, self.version
)
@@ -1148,7 +1173,7 @@ def gen_source(
else:
self.customize(customize)
self.text = self.traverse(ast, is_lambda=is_lambda)
# In a formatted string using "lambda', we should not add "\n".
# In a formatted string using "lambda", we should not add "\n".
# For example in:
# f'{(lambda x:x)("8")!r}'
# Adding a "\n" after "lambda x: x" will give an error message:
@@ -1167,7 +1192,6 @@ def build_ast(
noneInNames=False,
is_top_level_module=False,
):

# FIXME: DRY with fragments.py

# assert isinstance(tokens[0], Token)
@@ -1298,7 +1322,7 @@ def code_deparse(
is_top_level_module=is_top_level_module,
)

#### XXX workaround for profiling
# XXX workaround for profiling
if deparsed.ast is None:
return None

@@ -1406,7 +1430,7 @@ def deparse_code2str(
if __name__ == "__main__":

def deparse_test(co):
"This is a docstring"
"""This is a docstring"""
s = deparse_code2str(co)
# s = deparse_code2str(co, debug_opts={"asm": "after", "tree": {'before': False, 'after': False}})
print(s)

0 comments on commit db6c715

Please sign in to comment.