Compare commits

...

73 Commits

Author SHA1 Message Date
rocky
d080b4402d Get ready for release 3.0.0 2018-03-02 10:30:11 -05:00
rocky
7200d298a5 Back off unconditional_true test for now - Sigh 2018-03-02 10:06:38 -05:00
rocky
bb13988126 Instruction fixup broken 3.x make_func...
for handling default values
2018-03-02 08:03:51 -05:00
rocky
8d503682b3 Use get_inst and self.insts more..
needed more in 3.6 to handle EXTENDED_ARGS before JUMP_xxx
2018-03-02 07:15:23 -05:00
rocky
26e1df835c Better "continue" detection for 2.7 2018-03-01 23:34:26 -05:00
rocky
6807015526 Better CONTINUE detection on 3.x
Helps when line numbers have been stripped say in optimization
2018-03-01 22:47:36 -05:00
rocky
99d9beac76 Code generation changes between 3.4.2 and 3.4.4 2018-03-01 21:23:44 -05:00
rocky
452d17a6c3 3.4 while1 bug fix 2018-03-01 18:56:08 -05:00
rocky
8d1c454376 small test tweak..
Allow it to run not under pytest
2018-03-01 17:23:58 -05:00
rocky
2edc757b6f more deparse_code -> code_deparse API additions 2018-03-01 16:55:45 -05:00
rocky
ef076c065b Imports yet again 2018-03-01 08:45:31 -05:00
rocky
a0e3759f76 Towards better 3.6 parameter handling
3.6 is still a mess though.
2018-03-01 08:09:53 -05:00
rocky
f7439f506a Fix 3.6 MAKE_FUNCTION for kw params...
and remove duplicated attributies in pattr of MAKE_FUNCITON token.
2018-03-01 07:14:46 -05:00
rocky
257c3ca454 Exclude early versions of Python in last change 2018-03-01 00:00:23 -05:00
rocky
e23315b2e6 Fallout from more precise token attributes 2018-02-28 23:35:52 -05:00
rocky
413df51dfa Token.format(), shows CONST values better...
We were not showing the proper value for None, or False.

Start a unit test for Token().

I think this cleans the Token class up a little more.

More work is needed for MAKE_FUNCTION...

Note: Some debug stuff is commented out in make_funciton.py for upcoming work.
2018-02-28 22:05:12 -05:00
rocky
1fe432585e Keep pre-3.6 listcomp code patterns in 3.6 2018-02-28 16:22:33 -05:00
rocky
b128e4fde6 Fix 3.6+ nested list comprehensions
Modified "load_closure" rule and
changed semantic actions since LOAD_CLOSUREs are stored
inside a MAKE_TUPLE.

FIXME: Not sure if we have additional "if" clauses correct. Test and correct
2018-02-28 11:13:52 -05:00
rocky
7eb19a8617 revise API for aligner 2018-02-27 22:02:51 -05:00
rocky
b6d96929cb Start simplifying higher-level API 2018-02-27 17:48:26 -05:00
rocky
68692abaf6 Start changing API to make version optional...
and use debug option dictionary
2018-02-27 11:42:29 -05:00
rocky
55a10d9e20 Remove trepan debug 2018-02-27 11:16:27 -05:00
rocky
e9d1b86a5b Revise comprehension walking in 3.x...
less rigidly and with less magic and more verbiage as to what's going on
2018-02-27 11:13:55 -05:00
rocky
afb90dd12e 3.6+ try/finally bugs
Another day another 3.6 bug fix attempted
2018-02-27 10:37:18 -05:00
rocky
c43c9a19aa Move to GPL3 license 2018-02-27 06:40:36 -05:00
rocky
d3f5ec6f30 Fix Bug in recalculating prev_op ...
in removing EXTENDED_ARG
2018-02-27 06:15:37 -05:00
rocky
5c2d0484e5 3.6 MAKE_FUNCTION workarounds
Still wrong, but points to diretions for improvements
2018-02-26 09:10:00 -05:00
rocky
31de0d2af5 3.6 keyword args bugs in CALL_FUNCTION_KW 2018-02-26 07:44:37 -05:00
rocky
195075ac01 3.6 while-if-while bug 2018-02-26 01:27:00 -05:00
rocky
d09e820d89 Use get_inst() to paper around EXTENDED_ARG 2018-02-25 23:02:23 -05:00
rocky
b584a0f6b0 More EXTENDED_ARGS woes on 3.6+ 2018-02-25 22:42:18 -05:00
rocky
df7cfa2d20 Add another 3.6 test for EXTENDED_ARGS 2018-02-25 21:46:42 -05:00
R. Bernstein
7f3956c996 Merge pull request #161 from rocky/extended_args
Extended args handling to address 3.6+ problems
2018-02-25 21:40:20 -05:00
rocky
98394d18bb pytest adjust: 2.7 should have be unchanged 2018-02-25 21:31:30 -05:00
rocky
493835b8cd bild_lines API changed. Adjust test for change 2018-02-25 21:27:09 -05:00
rocky
f6aa775d58 EXTENDED_ARG in 3.6 fixes
3.6 uses EXTENDED_ARG more often appearing as the targets of jump
instructions at the beginning of line statements. All of this wreaks
havoc in the current way that our instruction processing works
2018-02-25 21:06:57 -05:00
rocky
2f6a85d538 Merge branch 'master' into extended_args 2018-02-25 19:19:32 -05:00
rocky
8c0f256b78 Sync python2 and python3 scanner/injest code more 2018-02-25 09:42:04 -05:00
rocky
6e2ca8f53d Add another guard on a test 2018-02-25 08:59:12 -05:00
rocky
c1ed6dea62 Merge branch 'master' into extended_args 2018-02-25 08:57:11 -05:00
rocky
ab1c2ec5bb Merge branch 'master' of github.com:rocky/python-uncompyle6 2018-02-25 08:53:15 -05:00
rocky
e3d0b1b9ce move more toward instruction-based ingest...
rather than bytecode-based. This cleans up code
up a little bit better by using more general instructions
2018-02-25 08:51:50 -05:00
rocky
aaf83ea35d WIP: Handle EXTENDED_ARGS better 2018-02-25 08:22:07 -05:00
R. Bernstein
cb24973147 Merge pull request #160 from wangym5106/master
Keep global statements in fixed order
2018-02-24 10:34:50 -05:00
Yiming Wang
3545c7dc6f Keep global statements in fixed order 2018-02-24 22:22:50 +08:00
rocky
8463221527 yield before 2.4 may need "None" 2018-02-22 22:26:56 -05:00
rocky
6b6a085b08 Go over runtest.sh exceptions 2018-02-22 20:27:56 -05:00
rocky
30fbdff29c More 2.4-2.6 try vs try/else determination 2018-02-22 20:14:52 -05:00
rocky
26e65e0d90 2.4 test_typespy was fixed by commit from a while ago 2018-02-22 19:30:51 -05:00
rocky
a2b6ebc669 grammar tree -> parse tree 2018-02-22 14:34:42 -05:00
rocky
09efb24a3e Start distinguishing AST from grammar tree 2018-02-22 11:17:09 -05:00
rocky
a1b2a91d88 == -> is 2018-02-21 18:09:24 -05:00
rocky
859ce2206d version=None in deparse_code() uses sys.version...
(the current interpreter version) as the version to decompile.

Fixes #159.
2018-02-21 17:40:43 -05:00
rocky
ee89b5decd Fix 2.4/2.5 try/else detection...
in a hacky way
2018-02-21 04:17:08 -05:00
rocky
8864034966 Narrow the 2.7 unconditional_true reduction 2018-02-19 17:06:18 -05:00
rocky
deb9903a97 DRY and limit STORE_LOCALS 2018-02-19 08:03:09 -05:00
rocky
f4b7e54313 Refine 2.7 dead code test ..
in a hacky way. Will probalby have to expand this in the future or
better do dead code analysis
2018-02-19 07:06:04 -05:00
rocky
08c7966ef9 2.7 for .. try-else bug 2018-02-18 09:13:22 -05:00
rocky
a5c388c13b Another 2.7 "if" with return fix
This works is in conjunction with the commit before the previous release commit.
2018-02-17 11:11:32 -05:00
rocky
c82095e6ac Get ready for release 2.16.0 2018-02-17 07:30:22 -05:00
rocky
67ad08fd4a Beter 2.7 end_if and COME_FROM determination
Fixes #149

... Add more tests too
2018-02-17 07:16:14 -05:00
rocky
fa4f614295 Wierd comprehension bug seen via new loctraceback 2018-02-15 12:15:49 -05:00
rocky
f7f0aa5ea9 Merge branch 'master' of github.com:rocky/python-uncompyle6 2018-02-15 10:42:29 -05:00
rocky
083ae5f3fd Add deparsed_find() used by the trepan debuggers 2018-02-15 10:42:00 -05:00
rocky
a01091a46e Misc pydisasm fixes 2018-02-15 07:30:01 -05:00
rocky
b5a825f4d8 Fix up 3.6+ CALL_FUNCTION_EX 2018-02-12 07:45:20 -05:00
rocky
730b0549d5 Handle 3.6+ FUNCTION_EX a little more generally 2018-02-12 04:26:52 -05:00
rocky
e431e49d77 Isolate Python 3.5 custom parse rules...
are isolated into parse35.py now and removed from parse3.py

This causes some code duplicated from parse3.py into parse3{5,6}.py

We will deal with that later.
2018-02-12 03:59:44 -05:00
rocky
230a38d537 Fix Python 3.5 CALL_FUNCTION_VAR handling 2018-02-12 03:07:03 -05:00
rocky
6d29ed9077 Python 3.5 CALL_FUNCTION_VAR bugs 2018-02-09 16:48:11 -05:00
rocky
bb45be2dc7 Start to handle 3.5+ BUILD_LIST_UNPACK in call ..
to implement multple star arguments
2018-02-09 03:41:13 -05:00
rocky
f7999d2754 Add custom 3.5 handling for f(*a, *b, *c) 2018-02-08 08:42:38 -05:00
rocky
5aeb0424fc Administrivia 2018-02-05 06:37:50 -05:00
85 changed files with 2301 additions and 455 deletions

674
COPYING Normal file
View File

@@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

View File

@@ -1,4 +1,4 @@
This project has history of over 17 years spanning back to Python 1.5
This project has history of over 18 years spanning back to Python 1.5
There have been a number of people who have worked on this. I am awed
by the amount of work, number of people who have contributed to this,

22
LICENSE
View File

@@ -1,22 +0,0 @@
Copyright (c) 2015 by Rocky Bernstein
Copyright (c) 2000 by hartmut Goebel <h.goebel@crazy-compilers.com>
Copyright (c) 1998-2002 John Aycock
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -2,7 +2,7 @@ include README.rst
include ChangeLog
include HISTORY.md
include HOW-TO-REPORT-A-BUG.md
include LICENSE
include COPYING
include Makefile
include requirements.txt
include requirements-dev.txt

38
NEWS
View File

@@ -1,3 +1,41 @@
uncompyle6 3.0.0 2018-02-17
- deparse_code() and lookalikes from the various semantic actions are
now deprecated. Instead use new API code_deparse() which makes the
version optional and bundles debug options into a dictionary.
- License changed to GPL3.
- Many Python 3.6 fixes, especially around handling EXTENDED_ARGS
Due to the reduction in operand size for JUMP's there are many
more EXTENDED_ARGS instructions which can be the targets
of jumps, and messes up the peephole-like analysis that is
done for control flow since we don't have something better in place.
- Code has been reorganized to be more instruction nametuple based where it
has been more byecode array based. There was and still is code that had
had magic numbers to advance instructions or to pick out operands.
- Bug fixes in numerous other Python versions
- Instruction display improved
- Keep global statements in fixed order (from wangym5106)
A bit more work is still needed for 3.6 especially in the area of
function calls and definitions.
uncompyle6 2.16.0 2018-02-17
- API additions:
- add fragments.op_at_code_loc() and
- fragments.deparsed_find()_
- Better 2.7 end_if and COME_FROM determination
- Fix up 3.6+ CALL_FUNCTION_EX
- Misc pydisasm fixes
- Wierd comprehension bug seen via new loctraceback
- Fix Python 3.5+ CALL_FUNCTION_VAR and BUILD_LIST_UNPACK in call; with this
we can can handle 3.5+ f(a, b, *c, *d, *e) now
uncompyle6 2.15.1 2018-02-05
- More bug fixes and revert an improper bug fix in 2.15.0
uncompyle6 2.15.0 2018-02-05 pycon2018.co
- Bug fixes

View File

@@ -1,3 +1,17 @@
# Copyright (C) 2018 Rocky Bernstein <rocky@gnu.org>
#
# 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
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""uncompyle6 packaging information"""
# To the extent possible we make this file look more like a
@@ -14,6 +28,7 @@ Copyright (C) 2015-2018 Rocky Bernstein <rb@dustyfeet.com>.
classifiers = ['Development Status :: 5 - Production/Stable',
'Intended Audience :: Developers',
'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 2.4',

View File

@@ -45,6 +45,8 @@
$ source admin-tools/setup-python-2.4.sh
$ git merge master
# Add and fix merge conflicts
$ git commit
# Check against older versions

View File

@@ -1,5 +1,5 @@
import pytest
from uncompyle6.semantics.fragments import deparse_code as deparse
from uncompyle6.semantics.fragments import code_deparse as deparse, deparsed_find
from uncompyle6 import PYTHON_VERSION, PYTHON3
def map_stmts(x, y):
@@ -31,17 +31,19 @@ def list_comp():
def get_parsed_for_fn(fn):
code = fn.__code__ if PYTHON3 else fn.func_code
return deparse(PYTHON_VERSION, code)
return deparse(code, version=PYTHON_VERSION)
def check_expect(expect, parsed, fn_name):
debug = False
i = 2
max_expect = len(expect)
code = get_parsed_for_fn(fn_name)
for name, offset in sorted(parsed.offsets.keys()):
assert i+1 <= max_expect, (
"%s: ran out if items in testing node" % fn_name)
nodeInfo = parsed.offsets[name, offset]
node = nodeInfo.node
nodeInfo2 = deparsed_find((name, offset), parsed, code)
extractInfo = parsed.extract_node_info(node)
assert expect[i] == extractInfo.selectedLine, \

View File

@@ -71,7 +71,7 @@ def test_if_in_for():
elif 3.2 < PYTHON_VERSION <= 3.4:
bytecode = Bytecode(code, scan.opc)
scan.code = array('B', code.co_code)
scan.build_lines_data(code)
scan.lines = scan.build_lines_data(code)
scan.build_prev_op()
scan.insts = list(bytecode)
scan.offset2inst_index = {}

View File

@@ -66,7 +66,7 @@ def test_grammar():
expect_dup_rhs = frozenset([('COME_FROM',), ('CONTINUE',), ('JUMP_ABSOLUTE',),
('LOAD_CONST',),
('JUMP_BACK',), ('JUMP_FORWARD',)])
reduced_dup_rhs = {k: dup_rhs[k] for k in dup_rhs if k not in expect_dup_rhs}
reduced_dup_rhs = dict((k, dup_rhs[k]) for k in dup_rhs if k not in expect_dup_rhs)
for k in reduced_dup_rhs:
print(k, reduced_dup_rhs[k])
# assert not reduced_dup_rhs, reduced_dup_rhs

23
pytest/test_token.py Normal file
View File

@@ -0,0 +1,23 @@
from uncompyle6.scanners.tok import Token
def test_token():
# Test token formatting of: LOAD_CONST None
t = Token('LOAD_CONST', offset=0, attr=None, pattr=None, has_arg=True)
expect = ' 0 LOAD_CONST None'
# print(t.format())
assert t
assert t.format() == expect
# Make sure equality testing of tokens ignores offset
t2 = Token('LOAD_CONST', offset=2, attr=None, pattr=None, has_arg=True)
assert t2 == t
# Make sure formatting of: LOAD_CONST False. We assume False is the 0th index
# of co_consts.
t = Token('LOAD_CONST', offset=1, attr=False, pattr=False, has_arg=True)
expect = ' 1 LOAD_CONST 0 False'
assert t.format() == expect
if __name__ == '__main__':
test_token()

View File

@@ -8,5 +8,5 @@
9 STORE_NAME 2 'b'
12 JUMP_FORWARD 0 'to 15'
15_0 COME_FROM 12 '12'
15 LOAD_CONST 0 ''
15 LOAD_CONST 0 None
18 RETURN_VALUE

View File

@@ -11,5 +11,5 @@
6 15 LOAD_CONST 1 2
18 STORE_NAME 2 'd'
21_0 COME_FROM 12 '12'
21 LOAD_CONST 2 ''
21 LOAD_CONST 2 None
24 RETURN_VALUE

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

0
test/bytecode_3.6_run/.gitignore vendored Normal file
View File

View File

@@ -1,18 +1,17 @@
# From Python 2.4. test_cgi.py
# Bug was in putting try block inside the ifelse statement.
# Note: this is a self testing program - will assert on failure.
def do_test(method):
if method == "GET":
rc = 0
elif method == "POST":
rc = 1
else:
raise ValueError, "unknown method: %s" % method
# Bug found in 2.4 test_math.py
# Bug was turning last try/except/else into try/else
import math
def test_exceptions():
try:
rc = 2
except ZeroDivisionError:
rc = 3
return rc
x = math.exp(-1000000000)
except:
raise RuntimeError
assert 2 == do_test("GET")
try:
x = math.sqrt(-1.0)
except ValueError:
return x
else:
raise RuntimeError
test_exceptions()

View File

@@ -0,0 +1,9 @@
# Bug found in 2.7 test_itertools.py
# Bug was erroneously using reduction to unconditional_true
# A proper fix would be to use unconditional_true only when we
# can determine there is or was dead code.
from itertools import izip_longest
for args in [['abc', range(6)]]:
target = [tuple([arg[i] if i < len(arg) else None for arg in args])
for i in range(max(map(len, args)))]
assert list(izip_longest(*args)) == target

View File

@@ -0,0 +1,20 @@
# Bug found in 2.7 test_itertools.py
def test_iziplongest(self):
# Having a for loop seems important
for args in ['abc']:
self.assertEqual(1, 2)
pass # Having this seems important
# The bug was the except jumping back
# to the beginning of this for loop
for stmt in [
"izip_longest('abc', fv=1)",
]:
try:
eval(stmt)
except TypeError:
pass
else:
self.fail()

View File

@@ -0,0 +1,12 @@
# From 2.7 test_itertools.py
# Bug was in 2.7 decompiling like the commented out
# code below
from itertools import izip_longest
for args in [
['abc', range(6)],
]:
# target = [tuple([ arg[i] if 1 else None for arg in args if i < len(arg) ])
# for i in range(max(map(len, args)))]
target = [tuple([arg[i] if i < len(arg) else None for arg in args])
for i in range(max(map(len, args)))]
assert list(izip_longest(*args)) == target

View File

@@ -0,0 +1,17 @@
# From 3.4.4 mailcap.py
# Bug was needing a grammar rule to add POP_BLOCK before the end of the while1.
# 3.3 apparently doesn't add this.
def readmailcapfile(line):
while 1:
if not line: break
if line[0] == '#' or line.strip() == '':
continue
if not line:
continue
for j in range(3):
line[j] = line[j].strip()
if '/' in line:
line['/'].append('a')
else:
line['/'] = 'a'
return

View File

@@ -1,9 +1,16 @@
# Bug in Python 3.5 is getting the two star'd arguments right.
# Bug in 3.5 is detecting when there is more than
# one * in a call. There is a "BUILD_UNPACK_LIST" instruction used
# to unpack star arguments
def sum(a, b, c, d):
return a + b + c + d
args=(1,2)
args, a, b, c = (1, 2), 1, 2, 3
sum(*args, *args)
sum(*args, *args, *args)
sum(a, *args, *args)
sum(a, b, *args)
sum(a, b, *args, *args)
# FIXME: this is handled incorrectly
# (*c,) = (3,4)

View File

@@ -0,0 +1,49 @@
# Bug was found in 3.6 _osx_support.py in if/elif needing
# EXTENDED_ARGS which are the targets of jumps.
def get_platform_osx(_config_vars, osname, release, machine, sys, re):
"""Filter values for get_platform()"""
macver = _config_vars.get('MACOSX_DEPLOYMENT_TARGET', '')
macrelease = release or 10
macver = macver or macrelease
if macver:
release = macver
osname = "macosx"
cflags = _config_vars.get('CFLAGS', _config_vars.get('CFLAGS', ''))
if macrelease:
try:
macrelease = tuple(int(i) for i in macrelease.split('.')[0:2])
except ValueError:
macrelease = (10, 0)
else:
macrelease = (10, 0)
if (macrelease >= (10, 4)) and '-arch' in cflags.strip():
machine = 'fat'
archs = re.findall(r'-arch\s+(\S+)', cflags)
archs = tuple(sorted(set(archs)))
if len(archs) == 1:
machine = archs[0]
elif archs == ('i386', 'ppc'):
machine = 'fat'
elif archs == ('i386', 'x86_64'):
machine = 'intel'
elif archs == ('i386', 'ppc', 'x86_64'):
machine = 'fat3'
elif archs == ('ppc64', 'x86_64'):
machine = 'fat64'
elif archs == ('i386', 'ppc', 'ppc64', 'x86_64'):
machine = 'universal'
else:
raise ValueError(
"Don't know machine value for archs=%r" % (archs,))
elif machine == 'i386':
if sys.maxsize >= 2**32:
machine = 'x86_64'
return (osname, release, machine)

View File

@@ -7,3 +7,9 @@ def foo3(bar, baz=1, qux=2):
return 3
def foo4(bar, baz, qux=1, quux=2):
return 4
# From 3.6 compileall.
# Bug was in omitting default which when used in an "if"
# are treated as False would be
def _walk_dir(dir, ddir=None, maxlevels=10, quiet=0):
return

View File

@@ -0,0 +1,20 @@
# From 3.6 _pyio.py
# Bug was in "return" not having "COME_FROM"
# and in 1st try/finally no END_FINALLY (which really
# hooks into the control-flow analysis).
# The 2nd try/finally has an END_FINALLY although still
# no "COME_FROM".
def getvalue(self):
try:
return 3
finally:
return 1
def getvalue1(self):
try:
return 4
finally:
pass
return 2

View File

@@ -0,0 +1,4 @@
# From 3.6 _pydecimal. Bug was handling
# keyword args in the return (CALL_FUNCTION_KW_2)
def to_eng_string(self, context=None):
return self.__str__(eng=True, context=context)

View File

@@ -0,0 +1,8 @@
# From 3.6 base64.py. Bug was handling *, and keyword args
def a85decode(b, *, foldspaces=False, adobe=False, ignorechars=b' \t\n\r\v'):
return
# From 3.6 configparser.py. Same problem as above.
_UNSET = object()
def get(self, section, option, *, raw=False, vars=None, fallback=_UNSET):
return

View File

@@ -0,0 +1,24 @@
# From 3.6 _sitebuiltins.py
# Bug was in handling double nested kinds of things like:
# for a in b for c in d
# This required grammar modification and
# and semantic action changes. LOAD_CLOSUREs are stored
# inside a MAKE_TUPLE.
# FIXME: test and try additional "if" clauses.
def __init__(self, path, name, files=(), dirs=(), volumes=()):
f = [path.join(dir, filename)
for dir in dirs
for filename in files]
f2 = [path.join(drive, dir, filename)
for dir in dirs
for filename in files
for drive in volumes]
return f, f2
# From 3.6 codeop. The below listcomp is generated still
# like it was in 3.5
import __future__
_features = [getattr(__future__, fname)
for fname in __future__.all_feature_names]

View File

@@ -0,0 +1,37 @@
# From 3.6 argparse. Bug was in handling EXTENDED_ARGS in a JUMP_FORWARD
# messing up control flow detection
def _format_usage(self, usage, actions, groups, prefix):
if usage:
usage = usage % dict(prog=self._prog)
elif usage is None:
prog = 5
for action in actions:
if action.option_strings:
actions.append(action)
else:
actions.append(action)
action_usage = format(optionals + positionals, groups)
text_width = self._width - self._current_indent
if len(prefix) + len(usage) > text_width:
if len(prefix) + len(prog) <= 0.75 * text_width:
indent = ' ' * (len(prefix) + len(prog) + 1)
if opt_parts:
lines.extend(get_lines(pos_parts, indent))
elif pos_parts:
lines = get_lines([prog] + pos_parts, indent, prefix)
else:
lines = [prog]
else:
if len(lines) > 1:
lines.extend(get_lines(pos_parts, indent))
lines = [prog] + lines
usage = '\n'.positionals(lines)
return

View File

@@ -0,0 +1,9 @@
# From 3.6 _markupbase.py
# Bug was parsing the inner while
def _parse_doctype_subset(c, j, rawdata, n):
while n:
if c:
j += 1
while j < n and rawdata[j]:
j += 1
return -1

View File

@@ -27,24 +27,18 @@ case $PYVERSION in
SKIP_TESTS=(
[test_dis.py]=1 # We change line numbers - duh!
[test_grp.py]=1 # Long test - might work Control flow?
[test_math.py]=1 # Control flow?
[test_pwd.py]=1 # Long test - might work? Control flow?
[test_queue.py]=1 # Control flow?
[test_sax.py]=1 # Control flow?
# [test_threading.py]=1 # Long test - works
[test_types.py]=1 # Control flow?
)
;;
2.5)
SKIP_TESTS=(
[test_contextlib.py]=1
[test_contextlib.py]=1 # Syntax error - look at
[test_dis.py]=1 # We change line numbers - duh!
[test_exceptions.py]=1
[test_functools.py]=1
[test_grammar.py]=1 # Too many stmts. Handle large stmts
[test_grp.py]=1 # Long test - might work Control flow?
[test_math.py]=1 # Control flow?
[test_pdb.py]=1
[test_pdb.py]=1 # Line-number specific
[test_pwd.py]=1 # Long test - might work? Control flow?
[test_queue.py]=1 # Control flow?
[test_re.py]=1 # Probably Control flow?
@@ -77,7 +71,8 @@ case $PYVERSION in
[test_grammar.py]=1 # Too many stmts. Handle large stmts
[test_io.py]=1 # Test takes too long to run
[test_ioctl.py]=1 # Test takes too long to run
[test_itertools.py]=1 # Syntax error - look at!
[test_itertools.py]=1 # Fix erroneous reduction to "conditional_true".
# See test/simple_source/bug27+/05_not_unconditional.py
[test_memoryio.py]=1 # FIX
[test_multiprocessing.py]=1 # On uncompyle2, taks 24 secs
[test_pep352.py]=1 # ?

View File

@@ -9,6 +9,7 @@ Usage-Examples:
test_pyenvlib.py --all --verify # decomyile all tests and verify results
test_pyenvlib.py --test # decompile only the testsuite
test_pyenvlib.py --2.7.12 --verify # decompile and verify python lib 2.7.11
test_pyenvlib.py --3.6.4 --max 10 # decompile first 10 of 3.6.4
Adding own test-trees:

View File

@@ -1,7 +1,7 @@
"""
Copyright (c) 1999 John Aycock
Copyright (c) 2015, 2018 by Rocky Bernstein
Copyright (c) 2000 by hartmut Goebel <h.goebel@crazy-compilers.com>
Copyright (c) 2015 by Rocky Bernstein
Copyright (c) 1999 John Aycock
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
@@ -55,6 +55,10 @@ from uncompyle6.main import decompile_file
uncompyle_file = decompile_file
# Conventience functions so you can say:
# from uncompyle6 import deparse_code
# from uncompyle6 import (code_deparse, deparse_code2str)
code_deparse = uncompyle6.semantics.pysource.code_deparse
deparse_code2str = uncompyle6.semantics.pysource.deparse_code2str
# This is deprecated:
deparse_code = uncompyle6.semantics.pysource.deparse_code

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python
# Mode: -*- python -*-
#
# Copyright (c) 2015-2016 by Rocky Bernstein <rb@dustyfeet.com>
# Copyright (c) 2015-2016, 2018 by Rocky Bernstein <rb@dustyfeet.com>
#
from __future__ import print_function
import sys, os, getopt
@@ -13,16 +13,23 @@ program, ext = os.path.splitext(os.path.basename(__file__))
__doc__ = """
Usage:
%s [OPTIONS]... FILE
%s [--help | -h | -V | --version]
{0} [OPTIONS]... FILE
{0} [--help | -h | -V | --version]
Disassemble FILE with the instruction mangling that is done to
assist uncompyle6 in parsing the instruction stream. For example
instructions with variable-length arguments like CALL_FUNCTION and
BUILD_LIST have arguement counts appended to the instruction name, and
COME_FROM instructions are inserted into the instruction stream.
Examples:
{0} foo.pyc
{0} foo.py # same thing as above but find the file
{0} foo.pyc bar.pyc # disassemble foo.pyc and bar.pyc
See also `pydisasm' from the `xdis' package.
Options:
-U | --uncompyle6 show instructions with uncompyle6 mangling
-V | --version show version and stop
-h | --help show this message
@@ -34,8 +41,6 @@ def main():
Usage_short = """usage: %s FILE...
Type -h for for full help.""" % program
native = True
if len(sys.argv) == 1:
print("No file(s) given", file=sys.stderr)
print(Usage_short, file=sys.stderr)
@@ -55,8 +60,6 @@ Type -h for for full help.""" % program
elif opt in ('-V', '--version'):
print("%s %s" % (program, VERSION))
sys.exit(0)
elif opt in ('-U', '--uncompyle6'):
native = False
else:
print(opt)
print(Usage_short, file=sys.stderr)
@@ -64,7 +67,7 @@ Type -h for for full help.""" % program
for file in files:
if os.path.exists(files[0]):
disassemble_file(file, sys.stdout, native)
disassemble_file(file, sys.stdout)
else:
print("Can't read %s - skipping" % files[0],
file=sys.stderr)

View File

@@ -2,6 +2,19 @@
# Copyright (c) 2005 by Dan Pascu <dan@windowmaker.org>
# Copyright (c) 2000-2002 by hartmut Goebel <h.goebel@crazy-compilers.com>
# Copyright (c) 1999 John Aycock
#
# 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
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
CPython magic- and version- independent disassembly routines

View File

@@ -1,3 +1,18 @@
# Copyright (c) 2015-2016, 2818 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
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# 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 collections import deque
from xdis.code import iscode

View File

@@ -1,3 +1,17 @@
# Copyright (C) 2018 Rocky Bernstein <rocky@gnu.org>
#
# 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
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# 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 __future__ import print_function
import datetime, os, subprocess, sys, tempfile
@@ -9,8 +23,8 @@ from uncompyle6.parser import ParserError
from uncompyle6.version import VERSION
# from uncompyle6.linenumbers import line_number_mapping
from uncompyle6.semantics.pysource import deparse_code
from uncompyle6.semantics.fragments import deparse_code as deparse_code_fragments
from uncompyle6.semantics.pysource import code_deparse
from uncompyle6.semantics.fragments import code_deparse as code_deparse_fragments
from uncompyle6.semantics.linemap import deparse_code_with_map
from xdis.load import load_module
@@ -29,7 +43,7 @@ def _get_outstream(outfile):
def decompile(
bytecode_version, co, out=None, showasm=None, showast=False,
timestamp=None, showgrammar=False, code_objects={},
source_size=None, is_pypy=False, magic_int=None,
source_size=None, is_pypy=None, magic_int=None,
mapstream=None, do_fragments=False):
"""
ingests and deparses a given code block 'co'
@@ -60,6 +74,12 @@ def decompile(
if source_size:
write('# Size of source mod 2**32: %d bytes' % source_size)
debug_opts = {
'asm': showasm,
'ast': showast,
'grammar': showgrammar
}
try:
if mapstream:
if isinstance(mapstream, str):
@@ -77,11 +97,11 @@ def decompile(
mapstream.write("\n\n# %s\n" % linemap)
else:
if do_fragments:
deparse_fn = deparse_code_fragments
deparse_fn = code_deparse_fragments
else:
deparse_fn = deparse_code
deparsed = deparse_fn(bytecode_version, co, out, showasm, showast,
showgrammar, code_objects=code_objects,
deparse_fn = code_deparse
deparsed = deparse_fn(co, out, bytecode_version,
debug_opts = debug_opts,
is_pypy=is_pypy)
pass
return deparsed
@@ -217,6 +237,7 @@ def main(in_base, out_base, files, codes, outfile=None,
sys.stdout.write("\n")
sys.stderr.write("\n# file %s\n# %s\n" % (infile, e))
failed_files += 1
tot_files += 1
except KeyboardInterrupt:
if outfile:
outstream.close()

View File

@@ -1,7 +1,20 @@
# Copyright (c) 2015-2017 Rocky Bernstein
# Copyright (c) 2015-2018 Rocky Bernstein
# Copyright (c) 2005 by Dan Pascu <dan@windowmaker.org>
# Copyright (c) 2000-2002 by hartmut Goebel <h.goebel@crazy-compilers.com>
# Copyright (c) 1999 John Aycock
#
# 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
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Common uncompyle6 parser routines.
"""

View File

@@ -81,15 +81,16 @@ class Python25Parser(Python26Parser):
""")
super(Python25Parser, self).customize_grammar_rules(tokens, customize)
if self.version == 2.5:
self.check_reduce['tryelsestmt'] = 'tokens'
self.check_reduce['try_except'] = 'tokens'
def reduce_is_invalid(self, rule, ast, tokens, first, last):
invalid = super(Python25Parser,
self).reduce_is_invalid(rule, ast,
tokens, first, last)
if invalid:
return invalid
return False
## Don't need this for 2.5 yet..
# def reduce_is_invalid(self, rule, ast, tokens, first, last):
# invalid = super(Python25Parser,
# self).reduce_is_invalid(rule, ast,
# tokens, first, last)
# if invalid or tokens is None:
# return invalid
# return False
class Python25ParserSingle(Python26Parser, PythonParserSingle):

View File

@@ -314,6 +314,7 @@ class Python26Parser(Python2Parser):
self.check_reduce['and'] = 'AST'
self.check_reduce['list_for'] = 'AST'
self.check_reduce['try_except'] = 'tokens'
self.check_reduce['tryelsestmt'] = 'tokens'
def reduce_is_invalid(self, rule, ast, tokens, first, last):
invalid = super(Python26Parser,
@@ -341,7 +342,7 @@ class Python26Parser(Python2Parser):
ja_attr = ast[4].attr
return tokens[last].offset != ja_attr
elif rule[0] == 'try_except':
# We need to distingush try_except from try_except_else and we do that
# We need to distingush try_except from tryelsestmt and we do that
# by checking the jump before the END_FINALLY
# If we have:
# insn
@@ -349,14 +350,45 @@ class Python26Parser(Python2Parser):
# END_FINALLY
# COME_FROM
# then insn has to be either a JUMP_FORWARD or a RETURN_VALUE
# and if it is JUMP_FORWARD, then it has to be a JUMP_FORWARD to right after
# COME_FROM
if last == len(tokens):
last -= 1
if tokens[last] != 'COME_FROM' and tokens[last-1] == 'COME_FROM':
last -= 1
return (tokens[last] == 'COME_FROM'
if (tokens[last] == 'COME_FROM'
and tokens[last-1] == 'END_FINALLY'
and tokens[last-2] == 'POP_TOP'
and tokens[last-3].kind not in frozenset(('JUMP_FORWARD', 'RETURN_VALUE')))
and tokens[last-2] == 'POP_TOP'):
# A jump of 2 is a jump around POP_TOP, END_FINALLY which
# would indicate try/else rather than try
return (tokens[last-3].kind not in frozenset(('JUMP_FORWARD', 'RETURN_VALUE'))
or (tokens[last-3] == 'JUMP_FORWARD' and tokens[last-3].attr != 2))
elif rule[0] == 'tryelsestmt':
# We need to distingush try_except from tryelsestmt and we do that
# by checking the jump before the END_FINALLY
# If we have:
# insn
# POP_TOP
# END_FINALLY
# COME_FROM
# then insn is neither a JUMP_FORWARD nor RETURN_VALUE,
# or if it is JUMP_FORWARD, then it can't be a JUMP_FORWARD to right after
# COME_FROM
if last == len(tokens):
last -= 1
while tokens[last-1] == 'COME_FROM' and tokens[last-2] == 'COME_FROM':
last -= 1
if tokens[last] == 'COME_FROM' and tokens[last-1] == 'COME_FROM':
last -= 1
if (tokens[last] == 'COME_FROM'
and tokens[last-1] == 'END_FINALLY'
and tokens[last-2] == 'POP_TOP'):
# A jump of 2 is a jump around POP_TOP, END_FINALLY which
# would indicate try/else rather than try
return (tokens[last-3].kind in frozenset(('JUMP_FORWARD', 'RETURN_VALUE'))
and (tokens[last-3] != 'JUMP_FORWARD' or tokens[last-3].attr == 2))
return False
class Python26ParserSingle(Python2Parser, PythonParserSingle):
pass

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2016-2017 Rocky Bernstein
# Copyright (c) 2016-2018 Rocky Bernstein
# Copyright (c) 2005 by Dan Pascu <dan@windowmaker.org>
# Copyright (c) 2000-2002 by hartmut Goebel <hartmut@goebel.noris.de>
@@ -17,9 +17,11 @@ class Python27Parser(Python2Parser):
list_for ::= expr for_iter store list_iter JUMP_BACK
list_comp ::= BUILD_LIST_0 list_iter
lc_body ::= expr LIST_APPEND
for_iter ::= GET_ITER COME_FROM FOR_ITER
stmt ::= setcomp_func
# Dictionary and set comprehensions were added in Python 2.7
expr ::= dict_comp
dict_comp ::= LOAD_DICTCOMP MAKE_FUNCTION_0 expr GET_ITER CALL_FUNCTION_1
@@ -53,6 +55,9 @@ class Python27Parser(Python2Parser):
tryelsestmtl ::= SETUP_EXCEPT suite_stmts_opt POP_BLOCK
except_handler else_suitel JUMP_BACK COME_FROM
tryelsestmtl ::= SETUP_EXCEPT suite_stmts_opt POP_BLOCK
except_handler else_suitel
except_stmt ::= except_cond2 except_suite
except_cond1 ::= DUP_TOP expr COMPARE_OP
@@ -60,6 +65,9 @@ class Python27Parser(Python2Parser):
except_cond2 ::= DUP_TOP expr COMPARE_OP
jmp_false POP_TOP store POP_TOP
for_block ::= l_stmts_opt JUMP_BACK
"""
def p_jump27(self, args):
@@ -94,7 +102,10 @@ class Python27Parser(Python2Parser):
# conditional_true are for conditions which always evaluate true
# There is dead or non-optional remnants of the condition code though,
# and we use that to match on to reconstruct the source more accurately
# and we use that to match on to reconstruct the source more accurately.
# FIXME: we should do analysis and reduce *only* if there is dead code?
# right now we check that expr is "or". Any other nodes types?
expr ::= conditional_true
conditional_true ::= expr JUMP_FORWARD expr COME_FROM
@@ -130,6 +141,11 @@ class Python27Parser(Python2Parser):
whileelsestmt ::= SETUP_LOOP testexpr l_stmts_opt JUMP_BACK POP_BLOCK
else_suitel COME_FROM
return_stmts ::= _stmts return_stmt
return_stmts ::= return_stmt
return_stmt ::= return
ifstmt ::= testexpr return_stmts COME_FROM
ifstmt ::= testexpr return_if_stmts COME_FROM
ifelsestmt ::= testexpr c_stmts_opt JUMP_FORWARD else_suite COME_FROM
ifelsestmtc ::= testexpr c_stmts_opt JUMP_ABSOLUTE else_suitec
@@ -154,6 +170,7 @@ class Python27Parser(Python2Parser):
""")
super(Python27Parser, self).customize_grammar_rules(tokens, customize)
self.check_reduce['and'] = 'AST'
# self.check_reduce['conditional_true'] = 'AST'
return
def reduce_is_invalid(self, rule, ast, tokens, first, last):
@@ -169,6 +186,12 @@ class Python27Parser(Python2Parser):
jmp_target = jmp_false.offset + jmp_false.attr + 3
return not (jmp_target == tokens[last].offset or
tokens[last].pattr == jmp_false.pattr)
# elif rule[0] == ('conditional_true'):
# # FIXME: the below is a hack: we check expr for
# # nodes that could have possibly been a been a Boolean.
# # We should also look for the presence of dead code.
# return ast[0] == 'expr' and ast[0] == 'or'
return False

View File

@@ -135,7 +135,7 @@ class Python3Parser(PythonParser):
iflaststmtl ::= testexpr c_stmts_opt JUMP_BACK COME_FROM_LOOP
iflaststmtl ::= testexpr c_stmts_opt JUMP_BACK POP_BLOCK
# These are used to keep AST indices the same
# These are used to keep parse tree indices the same
jump_forward_else ::= JUMP_FORWARD ELSE
jump_absolute_else ::= JUMP_ABSOLUTE ELSE
@@ -483,33 +483,15 @@ class Python3Parser(PythonParser):
token.kind = self.call_fn_name(token)
uniq_param = args_kw + args_pos
if self.version == 3.5 and opname.startswith('CALL_FUNCTION_VAR'):
# Python 3.5 changes the stack position of *args. KW args come
# after *args.
# Python 3.6+ replaces CALL_FUNCTION_VAR_KW with CALL_FUNCTION_EX
if opname.endswith('KW'):
kw = 'expr '
else:
kw = ''
rule = ('call ::= expr expr ' +
('pos_arg ' * args_pos) +
('kwarg ' * args_kw) + kw + token.kind)
self.add_unique_rule(rule, token.kind, uniq_param, customize)
# Note: 3.5+ have subclassed this method; so we don't handle
# 'CALL_FUNCTION_VAR' or 'CALL_FUNCTION_EX' here.
rule = ('call ::= expr ' +
('pos_arg ' * args_pos) +
('kwarg ' * args_kw) +
'expr ' * nak + token.kind)
self.add_unique_rule(rule, token.kind, uniq_param, customize)
if self.version >= 3.5 and seen_GET_AWAITABLE_YIELD_FROM:
rule = ('async_call ::= expr ' +
('pos_arg ' * args_pos) +
('kwarg ' * args_kw) +
'expr ' * nak + token.kind +
' GET_AWAITABLE LOAD_CONST YIELD_FROM')
self.add_unique_rule(rule, token.kind, uniq_param, customize)
self.add_unique_rule('expr ::= async_call', token.kind, uniq_param, customize)
if possible_class_decorator:
if next_token == 'CALL_FUNCTION' and next_token.attr == 1:
@@ -670,6 +652,11 @@ class Python3Parser(PythonParser):
rule = ('build_map_unpack_with_call ::= ' + 'expr1024 ' * int(v//1024) +
'expr32 ' * int((v//32) % 32) +
'expr ' * (v % 32) + opname)
self.addRule(rule, nop_func)
elif opname.startswith('BUILD_TUPLE_UNPACK_WITH_CALL'):
v = token.attr
rule = ('starred ::= %s %s' % ('expr ' * v, opname))
self.addRule(rule, nop_func)
elif opname_base in ('BUILD_LIST', 'BUILD_SET', 'BUILD_TUPLE'):
v = token.attr
@@ -898,6 +885,13 @@ class Python3Parser(PythonParser):
"GET_ITER CALL_FUNCTION_1" % ('pos_arg '* args_pos, opname))
self.add_make_function_rule(rule_pat, opname, token.attr, customize)
if is_pypy or (i >= 2 and tokens[i-2] == 'LOAD_LISTCOMP'):
if self.version >= 3.6:
# 3.6+ sometimes bundles all of the
# 'exprs' in the rule above into a
# tuple.
rule_pat = ("listcomp ::= load_closure LOAD_LISTCOMP %%s%s "
"expr GET_ITER CALL_FUNCTION_1" % (opname,))
self.add_make_function_rule(rule_pat, opname, token.attr, customize)
rule_pat = ("listcomp ::= %sLOAD_LISTCOMP %%s%s expr "
"GET_ITER CALL_FUNCTION_1" % ('expr ' * args_pos, opname))
self.add_make_function_rule(rule_pat, opname, token.attr, customize)
@@ -1116,10 +1110,6 @@ class Python30Parser(Python3Parser):
def p_30(self, args):
"""
# Store locals is only in Python 3.0 to 3.3
stmt ::= store_locals
store_locals ::= LOAD_FAST STORE_LOCALS
jmp_true ::= JUMP_IF_TRUE_OR_POP POP_TOP
_ifstmts_jump ::= c_stmts_opt JUMP_FORWARD POP_TOP COME_FROM
"""

View File

@@ -11,9 +11,6 @@ class Python30Parser(Python31Parser):
def p_30(self, args):
"""
# Store locals is only in Python 3.0 to 3.3
stmt ::= store_locals
store_locals ::= LOAD_FAST STORE_LOCALS
# FIXME: combine with parse3.2
whileTruestmt ::= SETUP_LOOP l_stmts_opt JUMP_BACK

View File

@@ -26,6 +26,13 @@ class Python33Parser(Python32Parser):
jump_excepts come_from_except_clauses
"""
def p_30to33(self, args):
"""
# Store locals is only in Python 3.0 to 3.3
stmt ::= store_locals
store_locals ::= LOAD_FAST STORE_LOCALS
"""
def customize_grammar_rules(self, tokens, customize):
self.remove_rules("""
# 3.3+ adds POP_BLOCKS

View File

@@ -1,4 +1,17 @@
# Copyright (c) 2017 Rocky Bernstein
# Copyright (c) 2017-2018 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
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
spark grammar differences over Python 3.3 for Python 3.4
"""
@@ -18,6 +31,10 @@ class Python34Parser(Python33Parser):
expr ::= LOAD_ASSERT
# Seems to be needed starting 3.4.4 or so
while1stmt ::= SETUP_LOOP l_stmts
COME_FROM JUMP_BACK POP_BLOCK COME_FROM_LOOP
# FIXME the below masks a bug in not detecting COME_FROM_LOOP
# grammar rules with COME_FROM -> COME_FROM_LOOP already exist
whileelsestmt ::= SETUP_LOOP testexpr l_stmts_opt JUMP_BACK POP_BLOCK
@@ -30,8 +47,10 @@ class Python34Parser(Python33Parser):
"""
def customize_grammar_rules(self, tokens, customize):
# self.remove_rules("""
# """)
self.remove_rules("""
# 3.4.2 has this. 3.4.4 may now
# while1stmt ::= SETUP_LOOP l_stmts COME_FROM JUMP_BACK COME_FROM_LOOP
""")
super(Python34Parser, self).customize_grammar_rules(tokens, customize)
return

View File

@@ -187,6 +187,52 @@ class Python35Parser(Python34Parser):
pass
return
def custom_classfunc_rule(self, opname, token, customize,
seen_LOAD_BUILD_CLASS,
seen_GET_AWAITABLE_YIELD_FROM,
*args):
args_pos, args_kw = self.get_pos_kw(token)
# Additional exprs for * and ** args:
# 0 if neither
# 1 for CALL_FUNCTION_VAR or CALL_FUNCTION_KW
# 2 for * and ** args (CALL_FUNCTION_VAR_KW).
# Yes, this computation based on instruction name is a little bit hoaky.
nak = ( len(opname)-len('CALL_FUNCTION') ) // 3
uniq_param = args_kw + args_pos
if seen_GET_AWAITABLE_YIELD_FROM:
rule = ('async_call ::= expr ' +
('pos_arg ' * args_pos) +
('kwarg ' * args_kw) +
'expr ' * nak + token.kind +
' GET_AWAITABLE LOAD_CONST YIELD_FROM')
self.add_unique_rule(rule, token.kind, uniq_param, customize)
self.add_unique_rule('expr ::= async_call', token.kind, uniq_param, customize)
uniq_param = args_kw + args_pos
if opname.startswith('CALL_FUNCTION_VAR'):
# Python 3.5 changes the stack position of *args. KW args come
# after *args.
# Note: Python 3.6+ replaces CALL_FUNCTION_VAR and
# CALL_FUNCTION_VAR_KW with CALL_FUNCTION_EX
token.kind = self.call_fn_name(token)
if opname.endswith('KW'):
kw = 'expr '
else:
kw = ''
rule = ('call ::= expr expr ' +
('pos_arg ' * args_pos) +
('kwarg ' * args_kw) + kw + token.kind)
self.add_unique_rule(rule, token.kind, uniq_param, customize)
else:
super(Python35Parser, self).custom_classfunc_rule(opname, token, customize,
seen_LOAD_BUILD_CLASS,
seen_GET_AWAITABLE_YIELD_FROM,
*args)
class Python35ParserSingle(Python35Parser, PythonParserSingle):
pass

View File

@@ -1,4 +1,17 @@
# Copyright (c) 2016-2017 Rocky Bernstein
# Copyright (c) 2016-2018 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
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
spark grammar differences over Python 3.5 for Python 3.6.
"""
@@ -17,6 +30,8 @@ class Python36Parser(Python35Parser):
def p_36misc(self, args):
"""
sstmt ::= sstmt RETURN_LAST
# 3.6 redoes how return_closure works
return_closure ::= LOAD_CLOSURE DUP_TOP STORE_NAME RETURN_VALUE RETURN_LAST
@@ -30,6 +45,8 @@ class Python36Parser(Python35Parser):
for_block ::= l_stmts_opt come_from_loops JUMP_BACK
come_from_loops ::= COME_FROM_LOOP*
whilestmt ::= SETUP_LOOP testexpr l_stmts_opt
JUMP_BACK COME_FROM POP_BLOCK COME_FROM_LOOP
# This might be valid in < 3.6
and ::= expr jmp_false expr
@@ -49,7 +66,14 @@ class Python36Parser(Python35Parser):
except_handler36 ::= COME_FROM_EXCEPT except_stmts END_FINALLY
stmt ::= try_except36
try_except36 ::= SETUP_EXCEPT returns except_handler36 opt_come_from_except
try_except36 ::= SETUP_EXCEPT returns except_handler36
opt_come_from_except
stmt ::= tryfinally36
tryfinally36 ::= SETUP_FINALLY returns
COME_FROM_FINALLY suite_stmts
tryfinally36 ::= SETUP_FINALLY returns
COME_FROM_FINALLY suite_stmts_opt END_FINALLY
"""
def customize_grammar_rules(self, tokens, customize):
@@ -132,6 +156,26 @@ class Python36Parser(Python35Parser):
def custom_classfunc_rule(self, opname, token, customize,
possible_class_decorator,
seen_GET_AWAITABLE_YIELD_FROM, next_token):
args_pos, args_kw = self.get_pos_kw(token)
# Additional exprs for * and ** args:
# 0 if neither
# 1 for CALL_FUNCTION_VAR or CALL_FUNCTION_KW
# 2 for * and ** args (CALL_FUNCTION_VAR_KW).
# Yes, this computation based on instruction name is a little bit hoaky.
nak = ( len(opname)-len('CALL_FUNCTION') ) // 3
uniq_param = args_kw + args_pos
if seen_GET_AWAITABLE_YIELD_FROM:
rule = ('async_call ::= expr ' +
('pos_arg ' * args_pos) +
('kwarg ' * args_kw) +
'expr ' * nak + token.kind +
' GET_AWAITABLE LOAD_CONST YIELD_FROM')
self.add_unique_rule(rule, token.kind, uniq_param, customize)
self.add_unique_rule('expr ::= async_call', token.kind, uniq_param, customize)
if opname.startswith('CALL_FUNCTION_KW'):
self.addRule("expr ::= call_kw", nop_func)
values = 'expr ' * token.attr

View File

@@ -1,4 +1,17 @@
# Copyright (c) 2017 Rocky Bernstein
# Copyright (c) 2017-2018 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
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
spark grammar differences over Python 3.6 for Python 3.7
"""

View File

@@ -3,7 +3,18 @@
# Copyright (c) 2000-2002 by hartmut Goebel <h.goebel@crazy-compilers.com>
# Copyright (c) 1999 John Aycock
#
# See LICENSE
# 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
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
"""
scanner/ingestion module. From here we call various version-specific
@@ -17,8 +28,8 @@ import sys
from uncompyle6 import PYTHON3, IS_PYPY
from uncompyle6.scanners.tok import Token
import xdis
from xdis.bytecode import op_size, extended_arg_val
from xdis.magics import py_str2float, canonic_python_version
from xdis.bytecode import instruction_size, extended_arg_val, next_offset
from xdis.magics import canonic_python_version
from xdis.util import code2num
# The byte code versions we support.
@@ -91,19 +102,38 @@ class Scanner(object):
That is, it is ether "JUMP_FORWARD" or an absolute jump that
goes forward.
"""
if self.code[offset] == self.opc.JUMP_FORWARD:
opname = self.get_inst(offset).opname
if opname == 'JUMP_FORWARD':
return True
if self.code[offset] != self.opc.JUMP_ABSOLUTE:
if opname != 'JUMP_ABSOLUTE':
return False
# FIXME 0 isn't always correct
return offset < self.get_target(offset, 0)
return offset < self.get_target(offset)
def get_target(self, pos, op=None):
if op is None:
op = self.code[pos]
target = self.get_argument(pos)
if op in self.opc.JREL_OPS:
target += pos + 3
def prev_offset(self, offset):
return self.insts[self.offset2inst_index[offset]-1].offset
def get_inst(self, offset):
# Instructions can get moved as a result of EXTENDED_ARGS removal.
# So if "offset" is not in self.offset2inst_index, then
# we assume that it was an instruction moved back.
# We check that assumption though by looking at
# self.code's opcode.
if offset not in self.offset2inst_index:
offset -= instruction_size(self.opc.EXTENDED_ARG, self.opc)
assert self.code[offset] == self.opc.EXTENDED_ARG
return self.insts[self.offset2inst_index[offset]]
def get_target(self, offset, extended_arg=0):
"""
Get next instruction offset for op located at given <offset>.
NOTE: extended_arg is no longer used
"""
inst = self.get_inst(offset)
if inst.opcode in self.opc.JREL_OPS | self.opc.JABS_OPS:
target = inst.argval
else:
# No jump offset, so use fall-through offset
target = next_offset(inst.opcode, self.opc, inst.offset)
return target
def get_argument(self, pos):
@@ -269,7 +299,7 @@ class Scanner(object):
"""
while start < end:
yield start
start += op_size(self.code[start], self.opc)
start += instruction_size(self.code[start], self.opc)
def remove_mid_line_ifs(self, ifs):
"""

View File

@@ -1,6 +1,19 @@
# Copyright (c) 2015-2018 by Rocky Bernstein
# Copyright (c) 2005 by Dan Pascu <dan@windowmaker.org>
# Copyright (c) 2000-2002 by hartmut Goebel <h.goebel@crazy-compilers.com>
#
# 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
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Python 2 Generic bytecode scanner/deparser
@@ -26,7 +39,9 @@ from collections import namedtuple
from array import array
from xdis.code import iscode
from xdis.bytecode import Bytecode, op_has_argument, op_size, instruction_size
from xdis.bytecode import (
Bytecode, op_has_argument, instruction_size,
_get_const_info)
from xdis.util import code2num
from uncompyle6.scanner import Scanner
@@ -72,13 +87,14 @@ class Scanner2(Scanner):
def ingest(self, co, classname=None, code_objects={}, show_asm=None):
"""
Pick out tokens from an uncompyle6 code object, and transform them,
returning a list of uncompyle6 'Token's.
returning a list of uncompyle6 Token's.
The transformations are made to assist the deparsing grammar.
Specificially:
- various types of LOAD_CONST's are categorized in terms of what they load
- COME_FROM instructions are added to assist parsing control structures
- MAKE_FUNCTION and FUNCTION_CALLS append the number of positional arguments
- some EXTENDED_ARGS instructions are removed
Also, when we encounter certain tokens, we add them to a set which will cause custom
grammar rules. Specifically, variable arg tokens like MAKE_FUNCTION or BUILD_LIST
@@ -141,8 +157,10 @@ class Scanner2(Scanner):
if names[self.get_argument(i+3)] == 'AssertionError':
self.load_asserts.add(i+3)
# Get jump targets
# Format: {target offset: [jump offsets]}
jump_targets = self.find_jump_targets(show_asm)
# contains (code, [addrRefToCode])
# print("XXX2", jump_targets)
last_stmt = self.next_stmt[0]
i = self.next_stmt[last_stmt]
@@ -217,7 +235,13 @@ class Scanner2(Scanner):
# (id(const), const.co_filename, const.co_name)
pattr = '<code_object ' + const.co_name + '>'
else:
if oparg < len(co.co_consts):
argval, _ = _get_const_info(oparg, co.co_consts)
# Why don't we use _ above for "pattr" rather than "const"?
# This *is* a little hoaky, but we have to coordinate with
# other parts like n_LOAD_CONST in pysource.py for example.
pattr = const
pass
elif op in self.opc.NAME_OPS:
pattr = names[oparg]
elif op in self.opc.JREL_OPS:
@@ -267,8 +291,21 @@ class Scanner2(Scanner):
target = self.get_target(offset)
if target <= offset:
op_name = 'JUMP_BACK'
if (offset in self.stmts
and self.code[offset+3] not in (self.opc.END_FINALLY,
# 'Continue's include jumps to loops that are not
# and the end of a block which follow with POP_BLOCK and COME_FROM_LOOP.
# If the JUMP_ABSOLUTE is
# either to a FOR_ITER or the instruction after a SETUP_LOOP
# and it is followed by another JUMP_FORWARD
# then we'll take it as a "continue".
j = self.offset2inst_index[offset]
target_index = self.offset2inst_index[target]
is_continue = (self.insts[target_index-1].opname == 'SETUP_LOOP'
and self.insts[j+1].opname == 'JUMP_FORWARD') and False
if is_continue:
op_name = 'CONTINUE'
if (offset in self.stmts and
self.code[offset+3] not in (self.opc.END_FINALLY,
self.opc.POP_BLOCK)):
if ((offset in self.linestartoffsets and
self.code[self.prev[offset]] == self.opc.JUMP_ABSOLUTE)
@@ -383,7 +420,7 @@ class Scanner2(Scanner):
if elem != code[i]:
match = False
break
i += op_size(code[i], self.opc)
i += instruction_size(code[i], self.opc)
if match:
i = self.prev[i]
@@ -629,7 +666,7 @@ class Scanner2(Scanner):
'start': jump_back_offset+3,
'end': loop_end_offset})
elif op == self.opc.SETUP_EXCEPT:
start = offset + op_size(op, self.opc)
start = offset + instruction_size(op, self.opc)
target = self.get_target(offset, op)
end_offset = self.restrict_to_parent(target, parent)
if target != end_offset:
@@ -653,7 +690,7 @@ class Scanner2(Scanner):
setup_except_nest -= 1
elif self.code[end_finally_offset] == self.opc.SETUP_EXCEPT:
setup_except_nest += 1
end_finally_offset += op_size(code[end_finally_offset], self.opc)
end_finally_offset += instruction_size(code[end_finally_offset], self.opc)
pass
# Add the except blocks
@@ -866,7 +903,7 @@ class Scanner2(Scanner):
else:
# We still have the case in 2.7 that the next instruction
# is a jump to a SETUP_LOOP target.
next_offset = target + op_size(self.code[target], self.opc)
next_offset = target + instruction_size(self.code[target], self.opc)
next_op = self.code[next_offset]
if self.op_name(next_op) == 'JUMP_FORWARD':
jump_target = self.get_target(next_offset, next_op)
@@ -958,16 +995,31 @@ class Scanner2(Scanner):
'end': end_offset})
elif code_pre_rtarget == self.opc.RETURN_VALUE:
if self.version == 2.7 or pre_rtarget not in self.ignore_if:
# 10 is exception-match. If there is an exception match in the
# compare, then this is an exception clause not an if-then clause
# Below, 10 is exception-match. If there is an exception
# match in the compare, then this is an exception
# clause not an if-then clause
if (self.code[self.prev[offset]] != self.opc.COMPARE_OP or
self.code[self.prev[offset]+1] != 10):
self.structs.append({'type': 'if-then',
'start': start,
'end': rtarget})
self.thens[start] = rtarget
if self.version == 2.7 or code[pre_rtarget+1] != self.opc.JUMP_FORWARD:
if (self.version == 2.7 or
code[pre_rtarget+1] != self.opc.JUMP_FORWARD):
# The below is a big hack until we get
# better control flow analysis: disallow
# END_IF if the instruction before the
# END_IF instruction happens to be a jump
# target. In this case, probably what's
# gone on is that we messed up on the
# END_IF location and it should be the
# instruction before.
self.fixed_jumps[offset] = rtarget
if (self.version == 2.7 and
self.insts[self.offset2inst_index[pre[pre_rtarget]]].is_jump_target):
self.return_end_ifs.add(pre[pre_rtarget])
pass
else:
self.return_end_ifs.add(pre_rtarget)
pass
pass

View File

@@ -1,6 +1,19 @@
# Copyright (c) 2015-2017 by Rocky Bernstein
# Copyright (c) 2005 by Dan Pascu <dan@windowmaker.org>
# Copyright (c) 2000-2002 by hartmut Goebel <h.goebel@crazy-compilers.com>
#
# 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
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Python 2.6 bytecode scanner
@@ -20,6 +33,8 @@ from uncompyle6.scanner import L65536
# bytecode verification, verify(), uses JUMP_OPs from here
from xdis.opcodes import opcode_26
from xdis.bytecode import Bytecode
from xdis.bytecode import _get_const_info
JUMP_OPS = opcode_26.JUMP_OPS
class Scanner26(scan.Scanner2):
@@ -212,7 +227,13 @@ class Scanner26(scan.Scanner2):
# (id(const), const.co_filename, const.co_name)
pattr = '<code_object ' + const.co_name + '>'
else:
if oparg < len(co.co_consts):
argval, _ = _get_const_info(oparg, co.co_consts)
# Why don't we use _ above for "pattr" rather than "const"?
# This *is* a little hoaky, but we have to coordinate with
# other parts like n_LOAD_CONST in pysource.py for example.
pattr = const
pass
elif op in self.opc.NAME_OPS:
pattr = names[oparg]
elif op in self.opc.JREL_OPS:

View File

@@ -1,6 +1,19 @@
# Copyright (c) 2015-2018 by Rocky Bernstein
# Copyright (c) 2005 by Dan Pascu <dan@windowmaker.org>
# Copyright (c) 2000-2002 by hartmut Goebel <h.goebel@crazy-compilers.com>
#
# 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
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Python 3 Generic bytecode scanner/deparser
@@ -25,9 +38,8 @@ from __future__ import print_function
from collections import namedtuple
from array import array
from uncompyle6.scanner import Scanner
from xdis.code import iscode
from xdis.bytecode import Bytecode, instruction_size
from xdis.bytecode import Bytecode, instruction_size, _get_const_info
from uncompyle6.scanner import Token, parse_fn_counts
import xdis
@@ -35,6 +47,8 @@ import xdis
# Get all the opcodes into globals
import xdis.opcodes.opcode_33 as op3
from uncompyle6.scanner import Scanner
import sys
from uncompyle6 import PYTHON3
if PYTHON3:
@@ -140,6 +154,42 @@ class Scanner3(Scanner):
# FIXME: remove the above in favor of:
# self.varargs_ops = frozenset(self.opc.hasvargs)
def remove_extended_args(self, instructions):
"""Go through instructions removing extended ARG.
get_instruction_bytes previously adjusted the operand values
to account for these"""
new_instructions = []
last_was_extarg = False
n = len(instructions)
for i, inst in enumerate(instructions):
if (inst.opname == 'EXTENDED_ARG' and
i+1 < n and instructions[i+1].opname != 'MAKE_FUNCTION'):
last_was_extarg = True
starts_line = inst.starts_line
is_jump_target = inst.is_jump_target
offset = inst.offset
continue
if last_was_extarg:
# j = self.stmts.index(inst.offset)
# self.lines[j] = offset
new_inst= inst._replace(starts_line=starts_line,
is_jump_target=is_jump_target,
offset=offset)
inst = new_inst
if i < n:
new_prev = self.prev_op[instructions[i].offset]
j = instructions[i+1].offset
old_prev = self.prev_op[j]
while self.prev_op[j] == old_prev and j < n:
self.prev_op[j] = new_prev
j += 1
last_was_extarg = False
new_instructions.append(inst)
return new_instructions
def ingest(self, co, classname=None, code_objects={}, show_asm=None):
"""
Pick out tokens from an uncompyle6 code object, and transform them,
@@ -171,17 +221,13 @@ class Scanner3(Scanner):
# list of tokens/instructions
tokens = []
# "customize" is a dict whose keys are nonterminals
# and the value is the argument stack entries for that
# nonterminal. The count is a little hoaky. It is mostly
# not used, but sometimes it is.
# "customize" is a dict whose keys are nonterminals
# "customize" is in the process of going away here
customize = {}
if self.is_pypy:
customize['PyPy'] = 0
self.build_lines_data(co)
self.lines = self.build_lines_data(co)
self.build_prev_op()
# FIXME: put as its own method?
@@ -189,7 +235,8 @@ class Scanner3(Scanner):
# turn 'LOAD_GLOBAL' to 'LOAD_ASSERT'.
# 'LOAD_ASSERT' is used in assert statements.
self.load_asserts = set()
self.insts = list(bytecode)
self.insts = self.remove_extended_args(list(bytecode))
self.offset2inst_index = {}
n = len(self.insts)
for i, inst in enumerate(self.insts):
@@ -219,15 +266,16 @@ class Scanner3(Scanner):
last_op_was_break = False
for i, inst in enumerate(bytecode):
for i, inst in enumerate(self.insts):
argval = inst.argval
op = inst.opcode
if op == self.opc.EXTENDED_ARG:
if inst.opname == 'EXTENDED_ARG':
# FIXME: The EXTENDED_ARG is used to signal annotation
# parameters
if self.insts[i+1].opcode != self.opc.MAKE_FUNCTION:
if (i+1 < n and
self.insts[i+1].opcode != self.opc.MAKE_FUNCTION):
continue
if inst.offset in jump_targets:
@@ -241,6 +289,10 @@ class Scanner3(Scanner):
for jump_offset in sorted(jump_targets[inst.offset], reverse=True):
come_from_name = 'COME_FROM'
opname = self.opname_for_offset(jump_offset)
if opname == 'EXTENDED_ARG':
j = xdis.next_offset(op, self.opc, jump_offset)
opname = self.opname_for_offset(j)
if opname.startswith('SETUP_'):
come_from_type = opname[len('SETUP_'):]
come_from_name = 'COME_FROM_%s' % come_from_type
@@ -287,6 +339,11 @@ class Scanner3(Scanner):
# (id(const), const.co_filename, const.co_name)
pattr = '<code_object ' + const.co_name + '>'
else:
if isinstance(inst.arg, int) and inst.arg < len(co.co_consts):
argval, _ = _get_const_info(inst.arg, co.co_consts)
# Why don't we use _ above for "pattr" rather than "const"?
# This *is* a little hoaky, but we have to coordinate with
# other parts like n_LOAD_CONST in pysource.py for example.
pattr = const
pass
elif opname in ('MAKE_FUNCTION', 'MAKE_CLOSURE'):
@@ -297,11 +354,6 @@ class Scanner3(Scanner):
attr = []
for flag in self.MAKE_FUNCTION_FLAGS:
bit = flags & 1
if bit:
if pattr:
pattr += ", " + flag
else:
pattr += flag
attr.append(bit)
flags >>= 1
attr = attr[:4] # remove last value: attr[5] == False
@@ -364,15 +416,24 @@ class Scanner3(Scanner):
# as CONTINUE, but that's okay since we add a grammar
# rule for that.
pattr = argval
# FIXME: 0 isn't always correct
target = self.get_target(inst.offset)
if target <= inst.offset:
next_opname = self.insts[i+1].opname
if (inst.offset in self.stmts and
# 'Continue's include jumps to loops that are not
# and the end of a block which follow with POP_BLOCK and COME_FROM_LOOP.
# If the JUMP_ABSOLUTE is to a FOR_ITER and it is followed by another JUMP_FORWARD
# then we'll take it as a "continue".
is_continue = (self.insts[self.offset2inst_index[target]]
.opname == 'FOR_ITER'
and self.insts[i+1].opname == 'JUMP_FORWARD')
if (is_continue or
(inst.offset in self.stmts and
(self.version != 3.0 or (hasattr(inst, 'linestart'))) and
(next_opname not in ('END_FINALLY', 'POP_BLOCK',
# Python 3.0 only uses POP_TOP
'POP_TOP'))):
'POP_TOP')))):
opname = 'CONTINUE'
else:
opname = 'JUMP_BACK'
@@ -416,7 +477,7 @@ class Scanner3(Scanner):
if show_asm in ('both', 'after'):
for t in tokens:
print(t)
print(t.format(line_prefix='L.'))
print()
return tokens, customize
@@ -432,7 +493,7 @@ class Scanner3(Scanner):
self.linestart_offsets = set(a for (a, _) in linestarts)
# 'List-map' which shows line number of current op and offset of
# first op on following line, given offset of op as index
self.lines = lines = []
lines = []
LineTuple = namedtuple('LineTuple', ['l_no', 'next'])
# Iterate through available linestarts, and fill
# the data for all code offsets encountered until
@@ -450,6 +511,7 @@ class Scanner3(Scanner):
while offset < codelen:
lines.append(LineTuple(prev_line_no, codelen))
offset += 1
return lines
def build_prev_op(self):
"""
@@ -510,6 +572,11 @@ class Scanner3(Scanner):
if inst.has_arg:
label = self.fixed_jumps.get(offset)
oparg = inst.arg
if (self.version >= 3.6 and
self.code[offset] == self.opc.EXTENDED_ARG):
j = xdis.next_offset(op, self.opc, offset)
next_offset = xdis.next_offset(op, self.opc, j)
else:
next_offset = xdis.next_offset(op, self.opc, offset)
if label is None:
@@ -619,37 +686,14 @@ class Scanner3(Scanner):
# Finish filling the list for last statement
slist += [codelen] * (codelen-len(slist))
def get_target(self, offset, extended_arg=0):
"""
Get target offset for op located at given <offset>.
"""
op = self.code[offset]
rel_offset = 0
if self.version >= 3.6:
target = self.code[offset+1]
if op in self.opc.JREL_OPS:
rel_offset = offset + 2
else:
target = self.code[offset+1] + self.code[offset+2] * 256
if op in self.opc.JREL_OPS:
rel_offset = offset + 3
pass
pass
target += rel_offset
target += extended_arg
return target
def detect_control_flow(self, offset, targets, inst_index):
"""
Detect structures and their boundaries to fix optimized jumps
Detect type of block structures and their boundaries to fix optimized jumps
in python2.3+
"""
# TODO: check the struct boundaries more precisely -Dan
code = self.code
op = code[offset]
op = self.insts[inst_index].opcode
# Detect parent structure
parent = self.structs[0]
@@ -673,8 +717,7 @@ class Scanner3(Scanner):
# It could be a return instruction.
start += instruction_size(op, self.opc)
# FIXME: 0 = extended arg which is not right. Use self.insts instead
target = self.get_target(offset, 0)
target = self.get_target(offset)
end = self.restrict_to_parent(target, parent)
self.setup_loops[target] = offset
@@ -715,7 +758,7 @@ class Scanner3(Scanner):
target = next_line_byte
end = xdis.next_offset(code[jump_back], self.opc, jump_back)
else:
if self.get_target(jump_back, 0) >= next_line_byte:
if self.get_target(jump_back) >= next_line_byte:
jump_back = self.last_instr(start, end, self.opc.JUMP_ABSOLUTE, start, False)
# This is wrong for 3.6+
@@ -728,23 +771,24 @@ class Scanner3(Scanner):
self.fixed_jumps[offset] = jump_back+4
end = jump_back+4
# I think 0 right because jump_back has been adjusted for any EXTENDED_ARG
# it encounters
target = self.get_target(jump_back)
if code[target] in (self.opc.FOR_ITER, self.opc.GET_ITER):
loop_type = 'for'
else:
loop_type = 'while'
test = self.prev_op[next_line_byte]
if test == offset:
if next_line_byte < len(code):
test_inst = self.insts[self.offset2inst_index[next_line_byte]-1]
if test_inst.offset == offset:
loop_type = 'while 1'
elif self.code[test] in self.opc.JUMP_OPs:
self.ignore_if.add(test)
test_target = self.get_target(test)
elif test_inst.opcode in self.opc.JUMP_OPs:
self.ignore_if.add(test_inst.offset)
test_target = self.get_target(test_inst.offset)
if test_target > (jump_back+3):
jump_back = test_target
pass
pass
pass
self.not_continue.add(jump_back)
self.loops.append(target)
self.structs.append({'type': loop_type + '-loop',
@@ -770,7 +814,7 @@ class Scanner3(Scanner):
# not myself? If so, it's part of a larger conditional.
# rocky: if we have a conditional jump to the next instruction, then
# possibly I am "skipping over" a "pass" or null statement.
pretarget = self.insts[self.offset2inst_index[prev_op[target]]]
pretarget = self.get_inst(prev_op[target])
if (pretarget.opcode in self.pop_jump_if_pop and
(target > offset) and pretarget.offset != offset):
@@ -888,7 +932,7 @@ class Scanner3(Scanner):
# like whether the target is "END_FINALLY"
# or if the condition jump is to a forward location
if self.is_jump_forward(pre_rtarget):
if_end = self.get_target(pre_rtarget, 0)
if_end = self.get_target(pre_rtarget)
# If the jump target is back, we are looping
if (if_end < pre_rtarget and
@@ -990,6 +1034,8 @@ class Scanner3(Scanner):
elif op == self.opc.POP_EXCEPT:
next_offset = xdis.next_offset(op, self.opc, offset)
target = self.get_target(next_offset)
if target is None:
from trepan.api import debug; debug()
if target > next_offset:
next_op = code[next_offset]
if (self.opc.JUMP_ABSOLUTE == next_op and

View File

@@ -1,4 +1,17 @@
# Copyright (c) 2015-2017 by Rocky Bernstein
# Copyright (c) 2015-2018 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
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Python 3.3 bytecode scanner/deparser

View File

@@ -1,4 +1,17 @@
# Copyright (c) 2015-2017 by Rocky Bernstein
# Copyright (c) 2015-2018 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
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Python 3.4 bytecode decompiler scanner

View File

@@ -1,4 +1,17 @@
# Copyright (c) 2017 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
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Python 3.5 bytecode decompiler scanner

View File

@@ -35,6 +35,8 @@ class Scanner36(Scanner3):
t.kind = 'CALL_FUNCTION_KW_{t.attr}'.format(**locals())
elif t.op == self.opc.BUILD_MAP_UNPACK_WITH_CALL:
t.kind = 'BUILD_MAP_UNPACK_WITH_CALL_%d' % t.attr
elif t.op == self.opc.BUILD_TUPLE_UNPACK_WITH_CALL:
t.kind = 'BUILD_TUPLE_UNPACK_WITH_CALL_%d' % t.attr
pass
return tokens, customize

View File

@@ -1,4 +1,17 @@
# Copyright (c) 2016-2017 by Rocky Bernstein
# Copyright (c) 2016-2018 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
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Python 3.7 bytecode decompiler scanner

View File

@@ -1,6 +1,19 @@
# Copyright (c) 2016-2018 by Rocky Bernstein
# Copyright (c) 2000-2002 by hartmut Goebel <h.goebel@crazy-compilers.com>
# Copyright (c) 1999 John Aycock
#
# 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
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import re, sys
from uncompyle6 import PYTHON3
@@ -22,7 +35,6 @@ class Token():
def __init__(self, opname, attr=None, pattr=None, offset=-1,
linestart=None, op=None, has_arg=None, opc=None):
self.kind = intern(opname)
self.op = op
self.has_arg = has_arg
self.attr = attr
self.pattr = pattr
@@ -31,7 +43,16 @@ class Token():
if has_arg is False:
self.attr = None
self.pattr = None
if opc is None:
from xdis.std import _std_api
self.opc = _std_api.opc
else:
self.opc = opc
if op is None:
self.op = self.opc.opmap.get(self.kind, None)
else:
self.op = op
def __eq__(self, o):
""" '==' on kind and "pattr" attributes.
@@ -65,7 +86,7 @@ class Token():
if not self.has_arg:
return "%s%s" % (prefix, offset_opname)
argstr = "%6d " % self.attr if isinstance(self.attr, int) else (' '*7)
if self.pattr:
if self.has_arg:
pattr = self.pattr
if self.opc:
if self.op in self.opc.JREL_OPS:
@@ -76,6 +97,11 @@ class Token():
if not self.pattr.startswith('to '):
pattr = "to " + str(self.pattr)
pass
elif self.op in self.opc.CONST_OPS:
# Compare with pysource n_LOAD_CONST
attr = self.attr
if attr is None:
pattr = None
elif self.op in self.opc.hascompare:
if isinstance(self.attr, int):
pattr = self.opc.cmp_op[self.attr]

View File

@@ -1,7 +1,25 @@
# Copyright (c) 2018 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
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys
from uncompyle6.semantics.pysource import (
SourceWalker, SourceWalkerError, find_globals, ASSIGN_DOC_STRING, RETURN_NONE)
from spark_parser import DEFAULT_DEBUG as PARSER_DEFAULT_DEBUG
from uncompyle6 import IS_PYPY
class AligningWalker(SourceWalker, object):
def __init__(self, version, out, scanner, showast=False,
debug_parser=PARSER_DEFAULT_DEBUG,
@@ -82,26 +100,45 @@ from uncompyle6.show import (
maybe_show_asm,
)
def align_deparse_code(version, co, out=sys.stderr, showasm=False, showast=False,
showgrammar=False, code_objects={}, compile_mode='exec', is_pypy=False):
#
DEFAULT_DEBUG_OPTS = {
'asm': False,
'tree': False,
'grammar': False
}
def code_deparse_align(co, out=sys.stderr, version=None, is_pypy=None,
debug_opts=DEFAULT_DEBUG_OPTS,
code_objects={}, compile_mode='exec'):
"""
ingests and deparses a given code block 'co'
"""
assert iscode(co)
if version is None:
version = float(sys.version[0:3])
if is_pypy is None:
is_pypy = IS_PYPY
# store final output stream for case of error
scanner = get_scanner(version, is_pypy=is_pypy)
tokens, customize = scanner.ingest(co, code_objects=code_objects)
maybe_show_asm(showasm, tokens)
show_asm = debug_opts.get('asm', None)
maybe_show_asm(show_asm, tokens)
debug_parser = dict(PARSER_DEFAULT_DEBUG)
if showgrammar:
debug_parser['reduce'] = showgrammar
show_grammar = debug_opts.get('grammar', None)
show_grammar = debug_opts.get('grammar', None)
if show_grammar:
debug_parser['reduce'] = show_grammar
debug_parser['errorstack'] = True
# Build AST from disassembly.
deparsed = AligningWalker(version, scanner, out, showast=showast,
# Build a parse tree from tokenized and massaged disassembly.
show_ast = debug_opts.get('ast', None)
deparsed = AligningWalker(version, scanner, out, showast=show_ast,
debug_parser=debug_parser, compile_mode=compile_mode,
is_pypy = is_pypy)
@@ -125,11 +162,11 @@ def align_deparse_code(version, co, out=sys.stderr, showasm=False, showast=False
except:
pass
# What we've been waiting for: Generate source from AST!
# What we've been waiting for: Generate Python source from the parse tree!
deparsed.gen_source(deparsed.ast, co.co_name, customize)
for g in deparsed.mod_globs:
deparsed.write('# global %s ## Warning: Unused global' % g)
for g in sorted(deparsed.mod_globs):
deparsed.write('# global %s ## Warning: Unused global\n' % g)
if deparsed.ERROR:
raise SourceWalkerError("Deparsing stopped due to parse error")
@@ -138,10 +175,7 @@ def align_deparse_code(version, co, out=sys.stderr, showasm=False, showast=False
if __name__ == '__main__':
def deparse_test(co):
"This is a docstring"
sys_version = sys.version_info.major + (sys.version_info.minor / 10.0)
# deparsed = deparse_code(sys_version, co, showasm=True, showast=True)
deparsed = align_deparse_code(sys_version, co, showasm=False, showast=False,
showgrammar=False)
deparsed = code_deparse_align(co)
print(deparsed.text)
return
deparse_test(deparse_test.__code__)

View File

@@ -1,5 +1,5 @@
"""
Python AST grammar checker.
Python parse tree checker.
Our rules sometimes give erroneous results. Until we have perfect rules,
This checker will catch mistakes in decompilation we've made.

View File

@@ -1,4 +1,17 @@
# Copyright (c) 2017 by Rocky Bernstein
# Copyright (c) 2017, 2018 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
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""Constants and initial table values used in pysource.py and fragments.py"""
import re, sys
@@ -16,8 +29,8 @@ else:
LINE_LENGTH = 80
# Some ASTs used for comparing code fragments (like 'return None' at
# the end of functions).
# Some parse trees created below are used for comparing code
# fragments (like 'return None' at the end of functions).
RETURN_LOCALS = AST('return',
[ AST('ret_expr', [AST('expr', [ Token('LOAD_LOCALS') ])]),

View File

@@ -1,4 +1,17 @@
# Copyright (c) 2018 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
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""Isolate Python version-specific semantic actions here.
"""
@@ -43,9 +56,9 @@ def customize_for_version(self, is_pypy, version):
if version < 3.0:
TABLE_R.update({
'STORE_SLICE+0': ( '%c[:]', 0 ),
'STORE_SLICE+1': ( '%c[%p:]', 0, (1, 100) ),
'STORE_SLICE+2': ( '%c[:%p]', 0, (1, 100) ),
'STORE_SLICE+3': ( '%c[%p:%p]', 0, (1, 100), (2, 100) ),
'STORE_SLICE+1': ( '%c[%p:]', 0, (1, -1) ),
'STORE_SLICE+2': ( '%c[:%p]', 0, (1, -1) ),
'STORE_SLICE+3': ( '%c[%p:%p]', 0, (1, -1), (2, -1) ),
'DELETE_SLICE+0': ( '%|del %c[:]\n', 0 ),
'DELETE_SLICE+1': ( '%|del %c[%c:]\n', 0, 1 ),
'DELETE_SLICE+2': ( '%|del %c[:%c]\n', 0, 1 ),
@@ -251,8 +264,8 @@ def customize_for_version(self, is_pypy, version):
node.kind == 'call'
p = self.prec
self.prec = 80
self.template_engine(('%c(%P)', 0,
(1, -4, ', ', 100)), node)
self.template_engine(('%c(%P)', 0, (1, -4, ', ',
100)), node)
self.prec = p
node.kind == 'async_call'
self.prune()
@@ -270,7 +283,7 @@ def customize_for_version(self, is_pypy, version):
if key.kind.startswith('CALL_FUNCTION_VAR_KW'):
# Python 3.5 changes the stack position of *args. kwargs come
# after *args whereas in earlier Pythons, *args is at the end
# which simpilfiies things from our perspective.
# which simplifies things from our perspective.
# Python 3.6+ replaces CALL_FUNCTION_VAR_KW with CALL_FUNCTION_EX
# We will just swap the order to make it look like earlier Python 3.
entry = table[key.kind]
@@ -282,6 +295,27 @@ def customize_for_version(self, is_pypy, version):
node[kwarg_pos], node[args_pos] = node[args_pos], node[kwarg_pos]
args_pos = kwarg_pos
kwarg_pos += 1
elif key.kind.startswith('CALL_FUNCTION_VAR'):
nargs = node[-1].attr & 0xFF
if nargs > 0:
template = ('%c(%C, ', 0, (1, nargs+1, ', '))
else:
template = ('%c(', 0)
self.template_engine(template, node)
args_node = node[-2]
if args_node == 'pos_arg':
args_node = args_node[0]
if args_node == 'expr':
args_node = args_node[0]
if args_node == 'build_list_unpack':
template = ('*%P)', (0, len(args_node)-1, ', *', 100))
self.template_engine(template, args_node)
else:
template = ('*%c)', -2)
self.template_engine(template, node)
self.prune()
self.default(node)
self.n_call = n_call
@@ -320,13 +354,14 @@ def customize_for_version(self, is_pypy, version):
#######################
TABLE_DIRECT.update({
'tryfinally36': ( '%|try:\n%+%c%-%|finally:\n%+%c%-\n\n',
(1, 'returns'), 3 ),
'fstring_expr': ( "{%c%{conversion}}", 0),
'fstring_single': ( "f'{%c%{conversion}}'", 0),
'fstring_multi': ( "f'%c'", 0),
'func_args36': ( "%c(**", 0),
'try_except36': ( '%|try:\n%+%c%-%c\n\n', 1, 2 ),
'unpack_list': ( '*%c', (0, 'list') ),
'starred': ( '*%c', (0, 'expr') ),
'call_ex' : (
'%c(%c)',
(0, 'expr'), 1),
@@ -491,7 +526,7 @@ def customize_for_version(self, is_pypy, version):
sep = INDENT_PER_LEVEL[:-1]
line_number = self.line_number
assert node[0].kind.startswith('kvlist')
if node[0].kind.startswith('kvlist'):
# Python 3.5+ style key/value list in dict
kv_node = node[0]
l = list(kv_node)
@@ -515,6 +550,25 @@ def customize_for_version(self, is_pypy, version):
line_number = self.line_number
i += 2
pass
elif node[-1].kind.startswith('BUILD_CONST_KEY_MAP'):
keys_node = node[-2]
keys = keys_node.attr
# from trepan.api import debug; debug()
assert keys_node == 'LOAD_CONST' and isinstance(keys, tuple)
for i in range(node[-1].attr):
self.write(sep)
self.write(keys[i], '=')
value = self.traverse(node[i], indent='')
self.write(value)
sep = ", "
if line_number != self.line_number:
sep += "\n" + self.indent + INDENT_PER_LEVEL[:-1]
line_number = self.line_number
pass
pass
else:
assert False, "Don't known to to untangle dictionary"
self.prec = p
self.indent_less(INDENT_PER_LEVEL)
return
@@ -555,7 +609,13 @@ def customize_for_version(self, is_pypy, version):
num_kwargs = len(keys)
num_posargs = len(node) - (num_kwargs + 1)
n = len(node)
assert n >= len(keys)+2
assert n >= len(keys)+1, \
'not enough parameters keyword-tuple values'
# try:
# assert n >= len(keys)+1, \
# 'not enough parameters keyword-tuple values'
# except:
# from trepan.api import debug; debug()
sep = ''
# FIXME: adjust output for line breaks?
for i in range(num_posargs):
@@ -570,6 +630,7 @@ def customize_for_version(self, is_pypy, version):
self.write(sep)
self.write(keys[j] + '=')
self.preorder(node[i])
sep=', '
i += 1
j += 1
self.write(')')
@@ -577,6 +638,27 @@ def customize_for_version(self, is_pypy, version):
return
self.n_kwargs_36 = kwargs_36
def starred(node):
l = len(node)
assert l > 0
pos_args = node[0]
if pos_args == 'expr':
pos_args = pos_args[0]
if pos_args == 'tuple':
star_start = 1
template = '%C', (0, -1, ', ')
self.template_engine(template, pos_args)
self.write(', ')
else:
star_start = 0
if l > 1:
template = ( '*%C', (star_start, -1, ', *') )
else:
template = ( '*%c', (star_start, 'expr') )
self.template_engine(template, node)
self.prune()
self.n_starred = starred
def return_closure(node):
# Nothing should be output here

View File

@@ -1,7 +1,20 @@
# Copyright (c) 2015-2018 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
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Creates Python source code from an uncompyle6 abstract syntax tree,
Creates Python source code from an uncompyle6 parse tree,
and indexes fragments which can be accessed by instruction offset
address.
@@ -60,10 +73,11 @@ from uncompyle6 import parser
from uncompyle6.scanner import Token, Code, get_scanner
import uncompyle6.parser as python_parser
from uncompyle6.semantics.check_ast import checker
from uncompyle6 import IS_PYPY
from uncompyle6.show import (
maybe_show_asm,
maybe_show_ast,
maybe_show_tree,
)
from uncompyle6.parsers.astnode import AST
@@ -766,7 +780,8 @@ class FragmentsWalker(pysource.SourceWalker, object):
self.set_pos_info(node[-1], gen_start, len(self.f.getvalue()))
def listcomprehension_walk2(self, node):
"""List comprehensions the way they are done in Python3.
"""List comprehensions the way they are done in Python 2 (and
some Python 3?).
They're more other comprehensions, e.g. set comprehensions
See if we can combine code.
"""
@@ -776,14 +791,21 @@ class FragmentsWalker(pysource.SourceWalker, object):
code = Code(node[1].attr, self.scanner, self.currentclass)
ast = self.build_ast(code._tokens, code._customize)
self.customize(code._customize)
if node == 'set_comp':
ast = ast[0][0][0]
else:
ast = ast[0][0][0][0][0]
if ast == 'expr':
ast = ast[0]
n = ast[1]
collection = node[-3]
list_if = None
assert n == 'list_iter'
# find innermost node
# Find the list comprehension body. It is the inner-most
# node that is not list_.. .
while n == 'list_iter':
n = n[0] # recurse one step
if n == 'list_for':
@@ -1042,7 +1064,7 @@ class FragmentsWalker(pysource.SourceWalker, object):
n_classdefdeco2 = n_classdef
def gen_source(self, ast, name, customize, is_lambda=False, returnNone=False):
"""convert AST to Python source code"""
"""convert parse tree to Python source code"""
rn = self.return_none
self.return_none = returnNone
@@ -1080,7 +1102,7 @@ class FragmentsWalker(pysource.SourceWalker, object):
self.p.insts = p_insts
except (parser.ParserError, AssertionError) as e:
raise ParserError(e, tokens)
maybe_show_ast(self.showast, ast)
maybe_show_tree(self.showast, ast)
return ast
# The bytecode for the end of the main routine has a
@@ -1108,7 +1130,7 @@ class FragmentsWalker(pysource.SourceWalker, object):
if len(tokens) == 0:
return PASS
# Build AST from disassembly.
# Build parse tree from tokenized and massaged disassembly.
try:
# FIXME: have p.insts update in a better way
# modularity is broken here
@@ -1119,7 +1141,7 @@ class FragmentsWalker(pysource.SourceWalker, object):
except (parser.ParserError, AssertionError) as e:
raise ParserError(e, tokens)
maybe_show_ast(self.showast, ast)
maybe_show_tree(self.showast, ast)
checker(ast, False, self.ast_errors)
@@ -1696,9 +1718,29 @@ class FragmentsWalker(pysource.SourceWalker, object):
pass
#
DEFAULT_DEBUG_OPTS = {
'asm': False,
'tree': False,
'grammar': False
}
# This interface is deprecated
def deparse_code(version, co, out=StringIO(), showasm=False, showast=False,
showgrammar=False, code_objects={}, compile_mode='exec',
is_pypy=False, walker=FragmentsWalker):
is_pypy=None, walker=FragmentsWalker):
debug_opts = {
'asm': showasm,
'ast': showast,
'grammar': showgrammar
}
return code_deparse(co, out, version, debug_opts, code_objects, compile_mode,
is_pypy, walker)
def code_deparse(co, out=StringIO(), version=None, is_pypy=None,
debug_opts=DEFAULT_DEBUG_OPTS,
code_objects={}, compile_mode='exec',
walker=FragmentsWalker):
"""
Convert the code object co into a python source fragment.
@@ -1706,40 +1748,44 @@ def deparse_code(version, co, out=StringIO(), showasm=False, showast=False,
example 2.6, 2.7, 3.2, 3.3, 3.4, 3.5 etc.
:param co: The code object to parse.
:param out: File like object to write the output to.
:param showasm: Flag which determines whether the ingestd code
is written to sys.stdout or not. (It is also to
pass a file like object, into which the asm will be
written).
:param showast: Flag which determines whether the constructed
abstract syntax tree is written to sys.stdout or
not. (It is also to pass a file like object, into
which the ast will be written).
:param showgrammar: Flag which determines whether the grammar
is written to sys.stdout or not. (It is also to
pass a file like object, into which the grammer
will be written).
:param debug_opts: A dictionary with keys
'asm': value determines whether to show
mangled bytecode disdassembly
'ast': value determines whether to show
'grammar': boolean determining whether to show
grammar reduction rules.
If value is a file-like object, output that object's write method will
be used rather than sys.stdout
:return: The deparsed source fragment.
"""
assert iscode(co)
# store final output stream for case of error
if version is None:
version = float(sys.version[0:3])
if is_pypy is None:
is_pypy = IS_PYPY
scanner = get_scanner(version, is_pypy=is_pypy)
show_asm = debug_opts.get('asm', None)
tokens, customize = scanner.ingest(co, code_objects=code_objects,
show_asm=showasm)
show_asm=show_asm)
tokens, customize = scanner.ingest(co)
maybe_show_asm(showasm, tokens)
maybe_show_asm(show_asm, tokens)
debug_parser = dict(PARSER_DEFAULT_DEBUG)
if showgrammar:
debug_parser['reduce'] = showgrammar
show_grammar = debug_opts.get('grammar', None)
if show_grammar:
debug_parser['reduce'] = show_grammar
debug_parser['errorstack'] = True
# Build AST from disassembly.
# Build Syntax Tree from tokenized and massaged disassembly.
# deparsed = pysource.FragmentsWalker(out, scanner, showast=showast)
deparsed = walker(version, scanner, showast=showast,
show_ast = debug_opts.get('ast', None)
deparsed = walker(version, scanner, showast=show_ast,
debug_parser=debug_parser, compile_mode=compile_mode,
is_pypy=is_pypy)
@@ -1760,8 +1806,8 @@ def deparse_code(version, co, out=StringIO(), showasm=False, showast=False,
deparsed.set_pos_info(deparsed.ast, 0, len(deparsed.text))
deparsed.fixup_parents(deparsed.ast, None)
for g in deparsed.mod_globs:
deparsed.write('# global %s ## Warning: Unused global' % g)
for g in sorted(deparsed.mod_globs):
deparsed.write('# global %s ## Warning: Unused global\n' % g)
if deparsed.ast_errors:
deparsed.write("# NOTE: have decompilation errors.\n")
@@ -1812,29 +1858,62 @@ def deparse_code_around_offset(name, offset, version, co, out=StringIO(),
return deparsed
def op_at_code_loc(code, loc, opc):
"""Return the instruction name at code[loc] using
opc to look up instruction names. Returns 'got IndexError'
if code[loc] is invalid.
`code` is instruction bytecode, `loc` is an offset (integer) and
`opc` is an opcode module from `xdis`.
"""
try:
op = code[loc]
except IndexError:
return 'got IndexError'
return opc.opname[op]
def deparsed_find(tup, deparsed, code):
"""Return a NodeInfo nametuple for a fragment-deparsed `deparsed` at `tup`.
`tup` is a name and offset tuple, `deparsed` is a fragment object
and `code` is instruction bytecode.
"""
nodeInfo = None
name, last_i = tup
if (name, last_i) in deparsed.offsets.keys():
nodeInfo = deparsed.offsets[name, last_i]
else:
from uncompyle6.scanner import get_scanner
scanner = get_scanner(deparsed.version)
co = code.co_code
if op_at_code_loc(co, last_i, scanner.opc) == 'DUP_TOP':
offset = deparsed.scanner.next_offset(co[last_i], last_i)
if (name, offset) in deparsed.offsets:
nodeInfo = deparsed.offsets[name, offset]
return nodeInfo
# if __name__ == '__main__':
# from uncompyle6 import IS_PYPY
# def deparse_test(co, is_pypy=IS_PYPY):
# from xdis.magics import sysinfo2float
# float_version = sysinfo2float()
# walk = deparse_code(float_version, co, showasm=False, showast=False,
# showgrammar=False, is_pypy=IS_PYPY)
# deparsed = code_deparse(co, is_pypy=IS_PYPY)
# print("deparsed source")
# print(walk.text, "\n")
# print(deparsed.text, "\n")
# print('------------------------')
# for name, offset in sorted(walk.offsets.keys(),
# for name, offset in sorted(deparsed.offsets.keys(),
# key=lambda x: str(x[0])):
# print("name %s, offset %s" % (name, offset))
# nodeInfo = walk.offsets[name, offset]
# nodeInfo = deparsed.offsets[name, offset]
# nodeInfo2 = deparsed_find((name, offset), deparsed, co)
# assert nodeInfo == nodeInfo2
# node = nodeInfo.node
# extractInfo = walk.extract_node_info(node)
# extractInfo = deparsed.extract_node_info(node)
# print("code: %s" % node.kind)
# # print extractInfo
# print(extractInfo.selectedText)
# print(extractInfo.selectedLine)
# print(extractInfo.markerLine)
# extractInfo, p = walk.extract_parent_info(node)
# extractInfo, p = deparsed.extract_parent_info(node)
# if extractInfo:
# print("Contained in...")
@@ -1848,23 +1927,26 @@ def deparse_code_around_offset(name, offset, version, co, out=StringIO(),
# def deparse_test_around(offset, name, co, is_pypy=IS_PYPY):
# sys_version = sys.version_info.major + (sys.version_info.minor / 10.0)
# walk = deparse_code_around_offset(name, offset, sys_version, co, showasm=False, showast=False,
# showgrammar=False, is_pypy=IS_PYPY)
# deparsed = deparse_code_around_offset(name, offset, sys_version, co,
# showasm=False,
# showast=False,
# showgrammar=False,
# is_pypy=IS_PYPY)
# print("deparsed source")
# print(walk.text, "\n")
# print(deparsed.text, "\n")
# print('------------------------')
# for name, offset in sorted(walk.offsets.keys(),
# for name, offset in sorted(deparsed.offsets.keys(),
# key=lambda x: str(x[0])):
# print("name %s, offset %s" % (name, offset))
# nodeInfo = walk.offsets[name, offset]
# nodeInfo = deparsed.offsets[name, offset]
# node = nodeInfo.node
# extractInfo = walk.extract_node_info(node)
# extractInfo = deparsed.extract_node_info(node)
# print("code: %s" % node.kind)
# # print extractInfo
# print(extractInfo.selectedText)
# print(extractInfo.selectedLine)
# print(extractInfo.markerLine)
# extractInfo, p = walk.extract_parent_info(node)
# extractInfo, p = deparsed.extract_parent_info(node)
# if extractInfo:
# print("Contained in...")
# print(extractInfo.selectedLine)

View File

@@ -16,7 +16,7 @@ read_global_ops = frozenset(('STORE_GLOBAL', 'DELETE_GLOBAL'))
# FIXME: this and find_globals could be paramaterized with one of the
# above global ops
def find_all_globals(node, globs):
"""Search AST node to find variable names that are global."""
"""Search Syntax Tree node to find variable names that are global."""
for n in node:
if isinstance(n, AST):
globs = find_all_globals(n, globs)
@@ -25,7 +25,8 @@ def find_all_globals(node, globs):
return globs
def find_globals(node, globs):
"""search AST node to find variable names that need a 'global' added."""
"""search a node of parse tree to find variable names that need a
'global' added."""
for n in node:
if isinstance(n, AST):
globs = find_globals(n, globs)

View File

@@ -1,5 +1,18 @@
# Copyright (c) 2018 by Rocky Bernstein
from uncompyle6.semantics.pysource import SourceWalker, deparse_code
#
# 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
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# 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.pysource import SourceWalker, code_deparse
import uncompyle6.semantics.fragments as fragments
# FIXME: does this handle nested code, and lambda properly
@@ -46,42 +59,49 @@ class LineMapFragmentWalker(fragments.FragmentsWalker, LineMapWalker):
def deparse_code_with_map(*args, **kwargs):
"""
Like deparse_code but saves line number correspondences.
Deprecated. Use code_deparse_with_map
"""
kwargs['walker'] = LineMapWalker
return deparse_code(*args, **kwargs)
return code_deparse(*args, **kwargs)
def code_deparse_with_map(*args, **kwargs):
"""
Like code_deparse but saves line number correspondences.
"""
kwargs['walker'] = LineMapWalker
return code_deparse(*args, **kwargs)
def deparse_code_with_fragments_and_map(*args, **kwargs):
"""
Like deparse_code_with_map but saves fragments.
Deprecated. Use code_deparse_with_fragments_and_map
"""
kwargs['walker'] = LineMapFragmentWalker
return fragments.deparse_code(*args, **kwargs)
def code_deparse_with_fragments_and_map(*args, **kwargs):
"""
Like code_deparse_with_map but saves fragments.
"""
kwargs['walker'] = LineMapFragmentWalker
return fragments.code_deparse(*args, **kwargs)
if __name__ == '__main__':
def deparse_test(co):
"This is a docstring"
import sys
sys_version = float(sys.version[0:3])
# deparsed = deparse_code(sys_version, co, showasm=True, showast=True)
deparsed = deparse_code_with_map(sys_version, co, showasm=False,
showast=False,
showgrammar=False)
deparsed = code_deparse_with_map(co)
a = 1; b = 2
print("\n")
linemap = [(line_no, deparsed.source_linemap[line_no])
for line_no in
sorted(deparsed.source_linemap.keys())]
print(linemap)
deparsed = deparse_code_with_fragments_and_map(sys_version,
co, showasm=False,
showast=False,
showgrammar=False)
a = 1; b = 2
deparsed = code_deparse_with_fragments_and_map(co)
print("\n")
linemap2 = [(line_no, deparsed.source_linemap[line_no])
for line_no in
sorted(deparsed.source_linemap.keys())]
print(linemap2)
assert linemap == linemap2
# assert linemap == linemap2
return
deparse_test(deparse_test.__code__)

View File

@@ -1,5 +1,18 @@
# Copyright (c) 2015-2018 by Rocky Bernstein
# Copyright (c) 2000-2002 by hartmut Goebel <h.goebel@crazy-compilers.com>
#
# 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
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
All the crazy things we have to do to handle Python functions
"""
@@ -18,7 +31,7 @@ if PYTHON3:
else:
from itertools import izip_longest as zip_longest
from uncompyle6.show import maybe_show_ast_param_default
from uncompyle6.show import maybe_show_tree_param_default
# FIXME: DRY the below code...
@@ -35,7 +48,7 @@ def make_function3_annotate(self, node, is_lambda, nested=1,
"""
if default:
value = self.traverse(default, indent='')
maybe_show_ast_param_default(self.showast, name, value)
maybe_show_tree_param_default(self.showast, name, value)
result = '%s=%s' % (name, value)
if result[-2:] == '= ': # default was 'LOAD_CONST None'
result += 'None'
@@ -256,7 +269,7 @@ def make_function3_annotate(self, node, is_lambda, nested=1,
assert ast == 'stmts'
all_globals = find_all_globals(ast, set())
for g in ((all_globals & self.mod_globs) | find_globals(ast, set())):
for g in sorted((all_globals & self.mod_globs) | find_globals(ast, set())):
self.println(self.indent, 'global ', g)
self.mod_globs -= all_globals
has_none = 'None' in code.co_names
@@ -288,7 +301,7 @@ def make_function2(self, node, is_lambda, nested=1, codeNode=None):
if default:
value = self.traverse(default, indent='')
maybe_show_ast_param_default(self.showast, name, value)
maybe_show_tree_param_default(self.showast, name, value)
result = '%s=%s' % (name, value)
if result[-2:] == '= ': # default was 'LOAD_CONST None'
result += 'None'
@@ -409,7 +422,7 @@ def make_function2(self, node, is_lambda, nested=1, codeNode=None):
assert ast == 'stmts'
all_globals = find_all_globals(ast, set())
for g in ((all_globals & self.mod_globs) | find_globals(ast, set())):
for g in sorted((all_globals & self.mod_globs) | find_globals(ast, set())):
self.println(self.indent, 'global ', g)
self.mod_globs -= all_globals
has_none = 'None' in code.co_names
@@ -446,26 +459,28 @@ def make_function3(self, node, is_lambda, nested=1, codeNode=None):
# MAKE_CLOSURE adds an additional closure slot
# Thank you, Python, for a such a well-thought out system that has
# changed 4 or so times.
# Thank you, Python: such a well-thought out system that has
# changed and continues to change many times.
def build_param(ast, name, default):
"""build parameters:
- handle defaults
- handle format tuple parameters
"""
if default:
if self.version >= 3.6:
value = default
else:
value = self.traverse(default, indent='')
maybe_show_ast_param_default(self.showast, name, value)
maybe_show_tree_param_default(self.showast, name, value)
result = '%s=%s' % (name, value)
# The below can probably be removed. This is probably
# a holdover from days when LOAD_CONST erroneously
# didn't handle LOAD_CONST None properly
if result[-2:] == '= ': # default was 'LOAD_CONST None'
result += 'None'
return result
else:
return name
# MAKE_FUNCTION_... or MAKE_CLOSURE_...
assert node[-1].kind.startswith('MAKE_')
@@ -539,8 +554,14 @@ def make_function3(self, node, is_lambda, nested=1, codeNode=None):
kw_pairs = args_node.attr[1] if self.version >= 3.0 else 0
# build parameters
params = [build_param(ast, name, d) for
name, d in zip_longest(paramnames, defparams, fillvalue=None)]
params = []
if defparams:
for i, defparam in enumerate(defparams):
params.append(build_param(ast, paramnames[i], defparam))
params += paramnames[i+1:]
else:
params = paramnames
if not 3.0 <= self.version <= 3.1 or self.version >= 3.6:
params.reverse() # back to correct order
@@ -619,12 +640,37 @@ def make_function3(self, node, is_lambda, nested=1, codeNode=None):
ends_in_comma = False
break
elif self.version >= 3.6:
d = node[1]
if d == 'expr':
d = d[0]
assert d == 'dict'
defaults = [self.traverse(n, indent='') for n in d[:-2]]
names = eval(self.traverse(d[-2]))
# argc = node[-1].attr
# co = node[-3].attr
# argcount = co.co_argcount
# kwonlyargcount = co.co_kwonlyargcount
free_tup = annotate_dict = kw_dict = default_tup = None
fn_bits = node[-1].attr
index = -4 # Skip over:
# MAKE_FUNCTION,
# LOAD_CONST qualified name,
# LOAD_CONST code object
if fn_bits[-1]:
free_tup = node[index]
index -= 1
if fn_bits[-2]:
annotate_dict = node[index]
index -= 1
if fn_bits[-3]:
kw_dict = node[index]
index -= 1
if fn_bits[-4]:
default_tup = node[index]
if kw_dict == 'expr':
kw_dict = kw_dict[0]
# FIXME: handle free_tup, annotate_dict, and default_tup
if kw_dict:
assert kw_dict == 'dict'
defaults = [self.traverse(n, indent='') for n in kw_dict[:-2]]
names = eval(self.traverse(kw_dict[-2]))
assert len(defaults) == len(names)
sep = ''
# FIXME: possibly handle line breaks
@@ -655,7 +701,7 @@ def make_function3(self, node, is_lambda, nested=1, codeNode=None):
assert ast == 'stmts'
all_globals = find_all_globals(ast, set())
for g in ((all_globals & self.mod_globs) | find_globals(ast, set())):
for g in sorted((all_globals & self.mod_globs) | find_globals(ast, set())):
self.println(self.indent, 'global ', g)
self.mod_globs -= all_globals
has_none = 'None' in code.co_names

View File

@@ -1,3 +1,17 @@
# Copyright (c) 2018 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
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import uncompyle6.parser as python_parser
class ParserError(python_parser.ParserError):
def __init__(self, error, tokens):

View File

@@ -2,8 +2,21 @@
# Copyright (c) 2005 by Dan Pascu <dan@windowmaker.org>
# Copyright (c) 2000-2002 by hartmut Goebel <h.goebel@crazy-compilers.com>
# Copyright (c) 1999 John Aycock
#
# 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
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""Creates Python source code from an uncompyle6 abstract syntax tree.
"""Creates Python source code from an uncompyle6 parse tree.
The terminal symbols are CPython bytecode instructions. (See the
python documentation under module "dis" for a list of instructions
@@ -47,7 +60,7 @@ Python.
# In the diagram below, N is a nonterminal name, and K also a nonterminal
# name but the one used as a key in the table.
# we show where those are with respect to each other in the
# AST tree for N.
# parse tree for N.
#
#
# N&K N N
@@ -139,7 +152,7 @@ from uncompyle6.semantics.consts import (
from uncompyle6.show import (
maybe_show_ast,
maybe_show_tree,
)
if PYTHON3:
@@ -951,6 +964,10 @@ class SourceWalker(GenericASTTraversal, object):
if node != AST('yield', [NONE, Token('YIELD_VALUE')]):
self.write(' ')
self.preorder(node[0])
elif self.version <= 2.4:
# Early versions of Python don't allow a plain "yield"
self.write(' None')
self.prune() # stop recursing
# In Python 3.3+ only
@@ -1062,6 +1079,7 @@ class SourceWalker(GenericASTTraversal, object):
self.write(')')
def n_LOAD_CONST(self, node):
attr = node.attr
data = node.pattr; datatype = type(data)
if isinstance(data, float) and str(data) in frozenset(['nan', '-nan', 'inf', '-inf']):
# float values 'nan' and 'inf' are not directly representable in Python at least
@@ -1076,13 +1094,15 @@ class SourceWalker(GenericASTTraversal, object):
self.write( hex(data) )
elif datatype is type(Ellipsis):
self.write('...')
elif data is None:
elif attr is None:
# LOAD_CONST 'None' only occurs, when None is
# implicit eg. in 'return' w/o params
# pass
self.write('None')
elif isinstance(data, tuple):
self.pp_tuple(data)
elif isinstance(attr, bool):
self.write(repr(attr))
elif self.FUTURE_UNICODE_LITERALS:
# The FUTURE_UNICODE_LITERALS compiler flag
# in 2.6 on change the way
@@ -1273,8 +1293,17 @@ class SourceWalker(GenericASTTraversal, object):
def n_import_from(self, node):
relative_path_index = 0
if self.version >= 2.5 and node[relative_path_index].pattr > 0:
node[2].pattr = '.'*node[relative_path_index].pattr + node[2].pattr
if self.version >= 2.5:
if node[relative_path_index].attr > 0:
node[2].pattr = ('.' * node[relative_path_index].pattr) + node[2].pattr
if self.version > 2.7:
if isinstance(node[1].pattr, tuple):
imports = node[1].pattr
for pattr in imports:
node[1].pattr = pattr
self.default(node)
return
pass
self.default(node)
n_import_from_star = n_import_from
@@ -1515,10 +1544,9 @@ class SourceWalker(GenericASTTraversal, object):
self.prune()
def comprehension_walk3(self, node, iter_index, code_index=-5):
"""
List comprehensions the way they are done in Python3.
They're more other comprehensions, e.g. set comprehensions
See if we can combine code.
"""Non-closure-based comprehensions the way they are done in Python3.
They are other comprehensions, e.g. set comprehensions See if
we can combine code.
"""
p = self.prec
self.prec = 27
@@ -1530,8 +1558,13 @@ class SourceWalker(GenericASTTraversal, object):
ast = self.build_ast(code._tokens, code._customize)
self.customize(code._customize)
# skip over stmt return ret_expr
ast = ast[0][0][0]
# skip over: sstmt, stmt, return, ret_expr
# and other singleton derivations
while (len(ast) == 1
or (ast in ('sstmt', 'return')
and ast[-1] in ('RETURN_LAST', 'RETURN_VALUE'))):
ast = ast[0]
store = None
if ast in ['setcomp_func', 'dictcomp_func']:
for k in ast:
@@ -1543,7 +1576,6 @@ class SourceWalker(GenericASTTraversal, object):
pass
pass
else:
ast = ast[0][0]
n = ast[iter_index]
assert n == 'list_iter', n
@@ -1579,7 +1611,7 @@ class SourceWalker(GenericASTTraversal, object):
assert n.kind in ('lc_body', 'comp_body', 'setcomp_func', 'set_comp_body'), ast
assert store, "Couldn't find store in list/set comprehension"
# Issue created with later Python code generation is that there
# A problem created with later Python code generation is that there
# is a lamda set up with a dummy argument name that is then called
# So we can't just translate that as is but need to replace the
# dummy name. Below we are picking out the variable name as seen
@@ -1620,7 +1652,8 @@ class SourceWalker(GenericASTTraversal, object):
self.prec = p
def listcomprehension_walk2(self, node):
"""List comprehensions the way they are done in Python 2.
"""List comprehensions the way they are done in Python 2 and
sometimes in Python 3.
They're more other comprehensions, e.g. set comprehensions
See if we can combine code.
"""
@@ -1630,42 +1663,72 @@ class SourceWalker(GenericASTTraversal, object):
code = Code(node[1].attr, self.scanner, self.currentclass)
ast = self.build_ast(code._tokens, code._customize)
self.customize(code._customize)
if node == 'set_comp':
ast = ast[0][0][0]
else:
ast = ast[0][0][0][0][0]
# skip over: sstmt, stmt, return, ret_expr
# and other singleton derivations
while (len(ast) == 1
or (ast in ('sstmt', 'return')
and ast[-1] in ('RETURN_LAST', 'RETURN_VALUE'))):
ast = ast[0]
n = ast[1]
collection = node[-3]
list_if = None
# collection = node[-3]
collections = [node[-3]]
list_ifs = []
assert n == 'list_iter'
stores = []
# Find the list comprehension body. It is the inner-most
# node that is not list_.. .
while n == 'list_iter':
n = n[0] # recurse one step
if n == 'list_for':
store = n[2]
stores.append(n[2])
n = n[3]
if self.version >= 3.6 and n[0] == 'list_for':
# Dog-paddle down largely singleton reductions
# to find the collection (expr)
c = n[0][0]
if c == 'expr':
c = c[0]
# FIXME: grammar is wonky here? Is this really an attribute?
if c == 'attribute':
c = c[0]
collections.append(c)
pass
elif n in ('list_if', 'list_if_not'):
# FIXME: just a guess
if n[0].kind == 'expr':
list_if = n
list_ifs.append(n)
else:
list_if = n[1]
list_ifs.append([1])
n = n[2]
pass
pass
assert n == 'lc_body', ast
# FIXME: add indentation around "for"'s and "in"'s
self.preorder(n[0])
if self.version < 3.6:
self.write(' for ')
self.preorder(stores[0])
self.write(' in ')
self.preorder(collections[0])
if list_ifs:
self.preorder(list_ifs[0])
pass
else:
for i, store in enumerate(stores):
self.write(' for ')
self.preorder(store)
self.write(' in ')
self.preorder(collection)
if list_if:
self.preorder(list_if)
self.preorder(collections[i])
if i < len(list_ifs):
self.preorder(list_ifs[i])
pass
pass
self.prec = p
def n_listcomp(self, node):
@@ -1680,7 +1743,7 @@ class SourceWalker(GenericASTTraversal, object):
n_dict_comp = n_set_comp
def setcomprehension_walk3(self, node, collection_index):
"""List comprehensions the way they are done in Python3.
"""Set comprehensions the way they are done in Python3.
They're more other comprehensions, e.g. set comprehensions
See if we can combine code.
"""
@@ -2509,7 +2572,7 @@ class SourceWalker(GenericASTTraversal, object):
# Add "global" declaration statements at the top
# of the function
for g in find_globals(ast, set()):
for g in sorted(find_globals(ast, set())):
self.println(indent, 'global ', g)
old_name = self.name
@@ -2561,7 +2624,7 @@ class SourceWalker(GenericASTTraversal, object):
self.p.insts = p_insts
except (python_parser.ParserError, AssertionError) as e:
raise ParserError(e, tokens)
maybe_show_ast(self.showast, ast)
maybe_show_tree(self.showast, ast)
return ast
# The bytecode for the end of the main routine has a
@@ -2583,7 +2646,7 @@ class SourceWalker(GenericASTTraversal, object):
if len(tokens) == 0:
return PASS
# Build AST from disassembly.
# Build a parse tree from a tokenized and massaged disassembly.
try:
# FIXME: have p.insts update in a better way
# modularity is broken here
@@ -2594,7 +2657,7 @@ class SourceWalker(GenericASTTraversal, object):
except (python_parser.ParserError, AssertionError) as e:
raise ParserError(e, tokens)
maybe_show_ast(self.showast, ast)
maybe_show_tree(self.showast, ast)
checker(ast, False, self.ast_errors)
@@ -2605,28 +2668,51 @@ class SourceWalker(GenericASTTraversal, object):
return MAP.get(node, MAP_DIRECT)
#
DEFAULT_DEBUG_OPTS = {
'asm': False,
'tree': False,
'grammar': False
}
# This interface is deprecated. Use simpler code_deparse.
def deparse_code(version, co, out=sys.stdout, showasm=None, showast=False,
showgrammar=False, code_objects={}, compile_mode='exec',
is_pypy=False, walker=SourceWalker):
debug_opts = {
'asm': showasm,
'ast': showast,
'grammar': showgrammar
}
return code_deparse(co, out, version, debug_opts, code_objects, compile_mode,
is_pypy, walker)
def code_deparse(co, out=sys.stdout, version=None, debug_opts=DEFAULT_DEBUG_OPTS,
code_objects={}, compile_mode='exec', is_pypy=False, walker=SourceWalker):
"""
ingests and deparses a given code block 'co'
ingests and deparses a given code block 'co'. If version is None,
we will use the current Python interpreter version.
"""
assert iscode(co)
if version is None:
version = float(sys.version[0:3])
# store final output stream for case of error
scanner = get_scanner(version, is_pypy=is_pypy)
tokens, customize = scanner.ingest(co, code_objects=code_objects,
show_asm=showasm)
show_asm=debug_opts['asm'])
debug_parser = dict(PARSER_DEFAULT_DEBUG)
if showgrammar:
debug_parser['reduce'] = showgrammar
if debug_opts.get('grammar', None):
debug_parser['reduce'] = debug_opts['grammar']
debug_parser['errorstack'] = 'full'
# Build AST from disassembly.
# Build Syntax Tree from disassembly.
linestarts = dict(scanner.opc.findlinestarts(co))
deparsed = walker(version, out, scanner, showast=showast,
deparsed = walker(version, out, scanner, showast=debug_opts.get('ast', None),
debug_parser=debug_parser, compile_mode=compile_mode,
is_pypy=is_pypy, linestarts=linestarts)
@@ -2658,11 +2744,11 @@ def deparse_code(version, co, out=sys.stdout, showasm=None, showast=False,
deparsed.FUTURE_UNICODE_LITERALS = (
COMPILER_FLAG_BIT['FUTURE_UNICODE_LITERALS'] & co.co_flags != 0)
# What we've been waiting for: Generate source from AST!
# What we've been waiting for: Generate source from Syntax Tree!
deparsed.gen_source(deparsed.ast, co.co_name, customize)
for g in deparsed.mod_globs:
deparsed.write('# global %s ## Warning: Unused global' % g)
for g in sorted(deparsed.mod_globs):
deparsed.write('# global %s ## Warning: Unused global\n' % g)
if deparsed.ast_errors:
deparsed.write("# NOTE: have internal decompilation grammar errors.\n")
@@ -2675,13 +2761,24 @@ def deparse_code(version, co, out=sys.stdout, showasm=None, showast=False,
raise SourceWalkerError("Deparsing stopped due to parse error")
return deparsed
def deparse_code2str(code, out=sys.stdout, version=None,
debug_opts=DEFAULT_DEBUG_OPTS,
code_objects={}, compile_mode='exec',
is_pypy=False, walker=SourceWalker):
"""Return the deparsed text for a Python code object. `out` is where any intermediate
output for assembly or tree output will be sent.
"""
return deparse_code(version, code, out, showasm=debug_opts.get('asm', None),
showast=debug_opts.get('tree', None),
showgrammar=debug_opts.get('grammar', None), code_objects=code_objects,
compile_mode=compile_mode, is_pypy=is_pypy, walker=walker).text
if __name__ == '__main__':
def deparse_test(co):
"This is a docstring"
sys_version = float(sys.version[0:3])
deparsed = deparse_code(sys_version, co, showasm='after', showast=True)
# deparsed = deparse_code(sys_version, co, showasm=None, showast=False,
s = deparse_code2str(co, debug_opts={'asm':'after', 'tree':True})
# s = deparse_code2str(co, showasm=None, showast=False,
# showgrammar=True)
print(deparsed.text)
print(s)
return
deparse_test(deparse_test.__code__)

View File

@@ -1,3 +1,17 @@
# Copyright (C) 2018 Rocky Bernstein <rocky@gnu.org>
#
# 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
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys
@@ -18,37 +32,37 @@ def maybe_show_asm(showasm, tokens):
stream.write('\n')
def maybe_show_ast(showast, ast):
def maybe_show_tree(show_tree, ast):
"""
Show the ast based on the showast flag (or file object), writing to the
appropriate stream depending on the type of the flag.
:param showasm: Flag which determines whether the abstract syntax tree is
:param show_tree: Flag which determines whether the parse tree is
written to sys.stdout or not. (It is also to pass a file
like object, into which the ast will be written).
:param ast: The ast to show.
"""
if showast:
stream = showast if hasattr(showast, 'write') else sys.stdout
if show_tree:
stream = show_tree if hasattr(show_tree, 'write') else sys.stdout
stream.write(str(ast))
stream.write('\n')
def maybe_show_ast_param_default(showast, name, default):
def maybe_show_tree_param_default(show_tree, name, default):
"""
Show a function parameter with default for an ast based on the showast flag
Show a function parameter with default for an grammar-tree based on the show_tree flag
(or file object), writing to the appropriate stream depending on the type
of the flag.
:param showasm: Flag which determines whether the function parameter with
:param show_tree: Flag which determines whether the function parameter with
default is written to sys.stdout or not. (It is also to
pass a file like object, into which the ast will be
written).
:param name: The function parameter name.
:param default: The function parameter default.
"""
if showast:
stream = showast if hasattr(showast, 'write') else sys.stdout
if show_tree:
stream = show_tree if hasattr(show_tree, 'write') else sys.stdout
stream.write('\n')
stream.write('--' + name)
stream.write('\n')

View File

@@ -2,6 +2,18 @@
# (C) Copyright 2015-2018 by Rocky Bernstein
# (C) Copyright 2000-2002 by hartmut Goebel <h.goebel@crazy-compilers.com>
#
# 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
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
byte-code verification
"""

View File

@@ -1,3 +1,15 @@
# 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
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# This file is suitable for sourcing inside bash as
# well as importing into Python
VERSION='2.15.1'
VERSION='3.0.0'