This commit is contained in:
rocky
2024-02-03 14:47:08 -05:00
parent 9839cfe93b
commit db6c7159f8
5 changed files with 119 additions and 92 deletions

View File

@@ -90,7 +90,7 @@ def decompile(
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 decompile(
)
)
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 decompile(
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,7 +375,7 @@ 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(
@@ -384,7 +383,6 @@ def main(
% (
infile,
status_msg(
do_verify,
tot_files,
okay_files,
failed_files,
@@ -405,14 +403,14 @@ 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()
@@ -420,11 +418,11 @@ if sys.platform.startswith("linux") and os.uname()[2][:2] in ["2.", "3.", "4."]:
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"

View File

@@ -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 @@ class LineMapWalker(SourceWalker):
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 @@ class LineMapWalker(SourceWalker):
# 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

View File

@@ -141,17 +141,25 @@ from uncompyle6.parsers.treenode import SyntaxTree
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.semantics.transform import TreeTransform, is_docstring
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 @@ class SourceWalker(GenericASTTraversal, NonterminalActions, ComprehensionMixin):
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 @@ class SourceWalker(GenericASTTraversal, NonterminalActions, ComprehensionMixin):
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 @@ class SourceWalker(GenericASTTraversal, NonterminalActions, ComprehensionMixin):
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 @@ class SourceWalker(GenericASTTraversal, NonterminalActions, ComprehensionMixin):
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 @@ class SourceWalker(GenericASTTraversal, NonterminalActions, ComprehensionMixin):
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 @@ class SourceWalker(GenericASTTraversal, NonterminalActions, ComprehensionMixin):
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 @@ class SourceWalker(GenericASTTraversal, NonterminalActions, ComprehensionMixin):
"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 @@ class SourceWalker(GenericASTTraversal, NonterminalActions, ComprehensionMixin):
if ast[0] == "sstmt":
ast[0] = ast[0][0]
first_stmt = ast[0]
except:
except Exception:
pass
try:
@@ -1023,7 +1051,7 @@ class SourceWalker(GenericASTTraversal, NonterminalActions, ComprehensionMixin):
del ast[0]
first_stmt = ast[0]
pass
except:
except Exception:
pass
have_qualname = False
@@ -1035,17 +1063,15 @@ class SourceWalker(GenericASTTraversal, NonterminalActions, ComprehensionMixin):
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 @@ class SourceWalker(GenericASTTraversal, NonterminalActions, ComprehensionMixin):
and first_stmt[1][0] == Token("STORE_NAME", pattr="__qualname__")
):
have_qualname = True
except:
except Exception:
pass
if have_qualname:
@@ -1078,7 +1104,7 @@ class SourceWalker(GenericASTTraversal, NonterminalActions, ComprehensionMixin):
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 @@ class SourceWalker(GenericASTTraversal, NonterminalActions, ComprehensionMixin):
# else:
# print stmt[-1]
globals, nonlocals = find_globals_and_nonlocals(
ast, set(), set(), code, self.version
)
@@ -1148,7 +1173,7 @@ class SourceWalker(GenericASTTraversal, NonterminalActions, ComprehensionMixin):
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 @@ class SourceWalker(GenericASTTraversal, NonterminalActions, ComprehensionMixin):
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)