Compare commits
441 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3782aa7d0a | |||
| d5740c1b08 | |||
| 3357979cc9 | |||
| 2f548dd9eb | |||
| f715d6ce68 | |||
| 350b605400 | |||
| 6a99d00487 | |||
| f87bf3f14d | |||
| 87347c0a04 | |||
| 217737b3e8 | |||
| efd8bd13da | |||
| 051bb63a81 | |||
| e4f4de6c8d | |||
| e6aa85e01d | |||
| cc4d94321e | |||
| c1292dff75 | |||
| 1d81cab4a1 | |||
| 2815cef1bb | |||
| d4523c4e53 | |||
| 5d894b6150 | |||
| 2eddbb9119 | |||
| a2513240ff | |||
| 0d509f94b7 | |||
| e4fbbcf2d6 | |||
| 9afacf72f8 | |||
| 78a7e65a2d | |||
| 3314de8dde | |||
| 8dab9b83be | |||
| 7b264ef2be | |||
| 5a6600f748 | |||
| 14ed0c3a3d | |||
| 229d78f1ef | |||
| f770e4ef42 | |||
| 66aa2f8f2a | |||
| 99d831c498 | |||
| a532287ddf | |||
| 7844e554aa | |||
| 10de4ff490 | |||
| eed65421ea | |||
| 7accc6e516 | |||
| fa8f9ccfaa | |||
| 8a264ca321 | |||
| f366eac7eb | |||
| 46d3992b41 | |||
| 164123f542 | |||
| 72c301dc54 | |||
| e8fd1e1dc7 | |||
| 2b7f8931a4 | |||
| ec3b71e5b6 | |||
| f7303881aa | |||
| 1b98be0b0a | |||
| e5b84d942e | |||
| 22e9ac22ba | |||
| 8a6cdec796 | |||
| c5c4499a55 | |||
| 30138f7a38 | |||
| 883429fa47 | |||
| 380ee75d9a | |||
| 99d9814083 | |||
| 141398aeac | |||
| 07cef6fd62 | |||
| aac041f960 | |||
| 6ef1600041 | |||
| 733836ea2d | |||
| b4767626d9 | |||
| 84edfac8fa | |||
| 69252ce721 | |||
| df1152516a | |||
| 02f9c25f52 | |||
| 7fb3988173 | |||
| a50352780b | |||
| ff093aeebb | |||
| aa691af664 | |||
| e0ffb01852 | |||
| 53be92c616 | |||
| 5f8f454b55 | |||
| 3700ecb717 | |||
| 811b0e7f30 | |||
| 08ea61f4df | |||
| 1d5368f5a2 | |||
| 90fb95e785 | |||
| 0f97f07461 | |||
| 7fe6b842a6 | |||
| 02a97bcb3a | |||
| fd4289aa64 | |||
| 716db8b964 | |||
| b55975a35a | |||
| 4cb34394b4 | |||
| aacb83290e | |||
| ddab4c269d | |||
| 6ddb0036fa | |||
| 0f7ca8cea4 | |||
| c4367e25a9 | |||
| e081aadd27 | |||
| 2bacab7dc0 | |||
| 824db6be2b | |||
| 2fdb26146b | |||
| b87d1a7fe1 | |||
| c242a62bcc | |||
| 6c91bce663 | |||
| 7fd46633a3 | |||
| 3c425990f6 | |||
| 55f16cc3ec | |||
| e01789bb0d | |||
| e3696af8ea | |||
| a26d7b5a8b | |||
| c4fe9150bf | |||
| ffc642048e | |||
| 8de6190a81 | |||
| d6e2c69202 | |||
| 1a85fa8e3c | |||
| c7b8508c6f | |||
| c35f6e2543 | |||
| 8052a90d04 | |||
| 3d20d7d330 | |||
| 5e722c6827 | |||
| 10198bc87f | |||
| a6b4043e8c | |||
| 9cea0163fa | |||
| 577176dd31 | |||
| a135eb44f3 | |||
| 252ed0e1e4 | |||
| fcb120a3ed | |||
| 988628a2e7 | |||
| c24cdf5cc1 | |||
| d748e004d2 | |||
| 380b73d1b9 | |||
| ef85e29a9b | |||
| 1daf5d1090 | |||
| 9d2c0e4aea | |||
| 7277ebb9c4 | |||
| c18074f6aa | |||
| 8a706193e7 | |||
| 9d77f5f5df | |||
| 6951d0e646 | |||
| 73dd55eac2 | |||
| b5a9389cc6 | |||
| d905c96fbe | |||
| 03f03f85af | |||
| 2b00a8a406 | |||
| f31c2dcd21 | |||
| 7699cfac02 | |||
| 5c48a457b4 | |||
| b5f439e1aa | |||
| 202fe5a0a9 | |||
| 68ccf57bd4 | |||
| 84970759d8 | |||
| 53cac58ebe | |||
| adc32ed319 | |||
| 7f0815a7b2 | |||
| 68f5565b63 | |||
| c552fb857d | |||
| 8a4ec47b92 | |||
| d281126337 | |||
| 4fb6ada5ec | |||
| ab924faa1e | |||
| b12b129af7 | |||
| 017c6b4d42 | |||
| d55cd5fbb4 | |||
| 13a6b1c8c6 | |||
| 0bc37e5d32 | |||
| 46c8572887 | |||
| e6b919007c | |||
| ac5a6096bb | |||
| db527fbbda | |||
| 8f201f1fee | |||
| d1e0762c12 | |||
| 010ae99c69 | |||
| a4632d6e86 | |||
| 2a3162f869 | |||
| 2063fd0742 | |||
| 128fe8a839 | |||
| 2478fc3a1b | |||
| 5a68d3bef7 | |||
| 195eeceb62 | |||
| ec8309af49 | |||
| 627a4dc802 | |||
| e2018535ef | |||
| ee56610f06 | |||
| fb9ff7748a | |||
| cdfb46d9d3 | |||
| 5545a94a9e | |||
| 9e811d959b | |||
| 957d5394d2 | |||
| 95afe1219e | |||
| 07937f1d71 | |||
| 671be0af0a | |||
| 7e9278f992 | |||
| 9194441c47 | |||
| 4f307c0085 | |||
| 3bdda55102 | |||
| b657b0fb1f | |||
| 4935ae6da5 | |||
| 72a50eae43 | |||
| fa37b90cff | |||
| 052a8db606 | |||
| 88ccba166e | |||
| 58998089a6 | |||
| f0a73b329e | |||
| c97678a477 | |||
| 2ad739275f | |||
| caad78885d | |||
| a234227b9f | |||
| 16f736e773 | |||
| 1fe24ad11d | |||
| 33c5e0827a | |||
| cbd36aeb8f | |||
| 2963bb3f41 | |||
| 09a6ceac63 | |||
| 75d8a01cab | |||
| 0968f75e9a | |||
| bc0db88afa | |||
| 5f11f12d0c | |||
| 2d18950542 | |||
| 50d314445a | |||
| f8d57d9265 | |||
| ebbe6db378 | |||
| 543cad3a23 | |||
| 41cc83dbf6 | |||
| ce7101be88 | |||
| 0a241e3a9c | |||
| 37857e88ea | |||
| 6fbcf46a8b | |||
| a36bc8f29a | |||
| 813b7bca6e | |||
| e2945f2a42 | |||
| eaf623a560 | |||
| 26aa504590 | |||
| e4dde3f4b6 | |||
| 9c90699c40 | |||
| b67cd50e8a | |||
| d2acaa03f5 | |||
| f2aa4cd10b | |||
| b940b99e75 | |||
| 868e0706ea | |||
| 324f544ba2 | |||
| 0a1981f90e | |||
| 0a36bfb088 | |||
| 0d94af099b | |||
| 4a6115ed64 | |||
| 42eb319751 | |||
| 343bddc6ad | |||
| 632a742ea9 | |||
| 08c9d1228a | |||
| 11d8b28fb4 | |||
| 12b6371209 | |||
| 24d22aaafb | |||
| ebf7822628 | |||
| 7669fa1582 | |||
| e49ba61917 | |||
| 96db1c2479 | |||
| 7abdb41a9e | |||
| 14f6d2f3b0 | |||
| 4e4b4975ad | |||
| 93fafcf886 | |||
| 82cc88a1b9 | |||
| 5c94e0bccc | |||
| 18a1788d2d | |||
| d0aa19118b | |||
| 039f6eebda | |||
| 8a464e8274 | |||
| 066b5a895d | |||
| 4c4af7928e | |||
| a0d8d9fcc6 | |||
| a2142b2ff8 | |||
| 5ed5ec5f7d | |||
| 95795620d5 | |||
| 890c0a9909 | |||
| b73cb40690 | |||
| ca448fc4d8 | |||
| 7a51c0d087 | |||
| 8762125bbf | |||
| 3d0c6e49ab | |||
| 03da35b29e | |||
| 3ccab60f43 | |||
| ed64b8c121 | |||
| 2a60ac47fe | |||
| 9cd72fe1e9 | |||
| 476b2c3735 | |||
| 5258c8363a | |||
| eb6d145dca | |||
| 63c003a02d | |||
| 5557fd814b | |||
| b1dc26ee06 | |||
| 56c0a588de | |||
| 47d65fcd87 | |||
| 85ab095630 | |||
| 1b5f0f6af6 | |||
| 2cf28eb2e7 | |||
| 2b300341a0 | |||
| 01fabca358 | |||
| 4ace552a27 | |||
| b61daaed33 | |||
| c6f0c89cf6 | |||
| 3c84975a09 | |||
| bb4ef4f0a2 | |||
| fd00330e6e | |||
| d10efec1ab | |||
| 3f08c99f19 | |||
| e3606d1b53 | |||
| ab593e3cd9 | |||
| 4a0aacf104 | |||
| 917cf20d37 | |||
| dabaeed8df | |||
| 4923b36e70 | |||
| ebf06fde65 | |||
| 438b3b50d9 | |||
| 6bac5c162e | |||
| 5cbf71bde6 | |||
| a85d382e89 | |||
| 4caa58f5fd | |||
| 43913d47ec | |||
| 9f51cabf69 | |||
| 1c60e5e315 | |||
| a9290f3131 | |||
| e46dfc555e | |||
| e54b764588 | |||
| 37f03bcf9e | |||
| 1d0f23dfbb | |||
| 30355cc9d6 | |||
| ed67f8e118 | |||
| 4531256005 | |||
| 662ebb6451 | |||
| 4a63f52259 | |||
| c416f77e99 | |||
| fde431d131 | |||
| 272e0d3754 | |||
| b44a1e3a4f | |||
| b18dabee15 | |||
| b6befbdcf2 | |||
| 2cfc208aa9 | |||
| 132b8d0618 | |||
| 5dc4c28da5 | |||
| 7342ae18a6 | |||
| eafe080c41 | |||
| 4f61ddd4b7 | |||
| 86b0458673 | |||
| 36cfc9d189 | |||
| b2f189b572 | |||
| eec524ad85 | |||
| d94087b939 | |||
| 1ba19d3600 | |||
| 07402ba4c0 | |||
| d60698206e | |||
| c59b65e71c | |||
| bd4c61d300 | |||
| 00a6b6efd2 | |||
| 04ac3b2eb7 | |||
| 6bc2d3321c | |||
| c95211925e | |||
| 95e9da36c5 | |||
| 9bf7270bf3 | |||
| a99e0e9618 | |||
| 01c4706013 | |||
| 89c7b9a848 | |||
| 1358a05a74 | |||
| 1b0a8990f7 | |||
| 4edfffae27 | |||
| cde8d72510 | |||
| d7ce0245f6 | |||
| 49c5ceb06e | |||
| 4c03a4245b | |||
| 4454e013c4 | |||
| 1e7546f4a3 | |||
| 7742d34111 | |||
| a413aaf140 | |||
| e94396532e | |||
| cc1be673e7 | |||
| f9e87d4da0 | |||
| ab8fa23fc3 | |||
| 7985466213 | |||
| e92ed48502 | |||
| c508e72c19 | |||
| 940de24099 | |||
| 6ddb71e21f | |||
| d0f120c314 | |||
| 54f4c6d2cb | |||
| 1f21760bbe | |||
| 67eb55a95d | |||
| fa097cc6b2 | |||
| 34222dae0a | |||
| 3a62d04376 | |||
| ca2c935f65 | |||
| ddf2174cae | |||
| 7096c38299 | |||
| c4cdd8514d | |||
| 25b2c8fe5b | |||
| 36da79feb8 | |||
| 571b5590ac | |||
| 7eb5defc2a | |||
| ce7d6f0156 | |||
| cbbb73355b | |||
| f51d633707 | |||
| bca90c1f41 | |||
| 17c0fd21d2 | |||
| fb43d716d9 | |||
| 3598a1279c | |||
| 5a40d960b2 | |||
| d6a468f0fc | |||
| 69eb57cbd7 | |||
| e3a10391ee | |||
| 8da0ba82e4 | |||
| 35ee0a2549 | |||
| 60615d01c3 | |||
| cb6ff60671 | |||
| 26800fb790 | |||
| 59292a2bc1 | |||
| b0bcea958c | |||
| dfe97b768e | |||
| b3fa8dbeed | |||
| 8eae42364f | |||
| 81ee9e6b7d | |||
| d5737adec7 | |||
| 210c8e547c | |||
| 4e284c4ce2 | |||
| c363bea59f | |||
| 3fcbca9456 | |||
| 56eac437f1 | |||
| b4d08bdc55 | |||
| c7ed985767 | |||
| a6f6115184 | |||
| 533883b5aa | |||
| 2e40ca17dc | |||
| 0e04dc72b9 | |||
| 484e07df8d | |||
| a55f4c59ce | |||
| 4e7ef9f4d2 | |||
| e60b599260 | |||
| 96e3e887ce | |||
| 87794d25c1 | |||
| c4f2119955 | |||
| 76feab3f2a | |||
| 550659d372 | |||
| ba1524dceb | |||
| 0ee499c54c | |||
| 3b84aec57e | |||
| cc318b13ad | |||
| d662b2c50c | |||
| a617a77d1f | |||
| 62a28c8e88 | |||
| 045a643bba |
+11
-1
@@ -1,15 +1,25 @@
|
|||||||
|
# Eclipse files
|
||||||
.classpath
|
.classpath
|
||||||
.project
|
.project
|
||||||
.settings/
|
.settings/
|
||||||
|
|
||||||
|
# IntelliJ Idea files
|
||||||
|
.idea/
|
||||||
|
out/
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
|
||||||
bin/
|
bin/
|
||||||
target/
|
target/
|
||||||
build/
|
build/
|
||||||
idea/
|
idea/
|
||||||
.gradle/
|
.gradle/
|
||||||
|
gradle.properties
|
||||||
|
|
||||||
|
*-tmp/
|
||||||
|
|
||||||
*.dex
|
*.dex
|
||||||
*.jar
|
|
||||||
*.class
|
*.class
|
||||||
*.dump
|
*.dump
|
||||||
*.log
|
*.log
|
||||||
|
|||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
language: java
|
||||||
|
jdk:
|
||||||
|
- oraclejdk8
|
||||||
|
- oraclejdk7
|
||||||
|
- openjdk6
|
||||||
|
|
||||||
|
before_install:
|
||||||
|
- chmod +x gradlew
|
||||||
|
|
||||||
|
script:
|
||||||
|
- TERM=dumb ./gradlew clean build dist
|
||||||
|
|
||||||
|
after_success:
|
||||||
|
- TERM=dumb ./gradlew jacocoTestReport coveralls
|
||||||
|
|
||||||
|
sudo: false
|
||||||
|
|
||||||
|
cache:
|
||||||
|
directories:
|
||||||
|
- $HOME/.gradle
|
||||||
|
|
||||||
|
notifications:
|
||||||
|
email:
|
||||||
|
- skylot@gmail.com
|
||||||
@@ -0,0 +1,201 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright {yyyy} {name of copyright owner}
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
The majority of jadx is written and copyrighted by me (Skylot)
|
The majority of jadx is written and copyrighted by me (Skylot)
|
||||||
and released under the Apache 2.0 license:
|
and released under the Apache 2.0 license (see LICENSE file for full license text):
|
||||||
|
|
||||||
*******************************************************************************
|
*******************************************************************************
|
||||||
Copyright 2013 Skylot
|
Copyright 2015, Skylot
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@@ -104,3 +104,78 @@ under the terms of the GNU Lesser General Public License version 2.1
|
|||||||
as published by the Free Software Foundation.
|
as published by the Free Software Foundation.
|
||||||
*******************************************************************************
|
*******************************************************************************
|
||||||
|
|
||||||
|
|
||||||
|
ASM library:
|
||||||
|
|
||||||
|
*******************************************************************************
|
||||||
|
Copyright (c) 2000-2011 INRIA, France Telecom
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions
|
||||||
|
are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
3. Neither the name of the copyright holders nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
||||||
|
THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*******************************************************************************
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Jadx-gui components
|
||||||
|
===================
|
||||||
|
|
||||||
|
RSyntaxTextArea library licensed under modified BSD license:
|
||||||
|
|
||||||
|
*******************************************************************************
|
||||||
|
Copyright (c) 2012, Robert Futrell
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
* Neither the name of the author nor the names of its contributors may
|
||||||
|
be used to endorse or promote products derived from this software
|
||||||
|
without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
||||||
|
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*******************************************************************************
|
||||||
|
|
||||||
|
|
||||||
|
Icons copied from several places:
|
||||||
|
- Eclipse Project (JDT UI) - licensed under EPL v1.0 (http://www.eclipse.org/legal/epl-v10.html)
|
||||||
|
- famfamfam silk icon set (http://www.famfamfam.com/lab/icons/silk/) - licensed under Creative Commons Attribution 2.5 License (http://creativecommons.org/licenses/by/2.5/)
|
||||||
|
|
||||||
|
JFontChooser Component - http://sourceforge.jp/projects/jfontchooser/
|
||||||
|
|||||||
@@ -1,46 +1,89 @@
|
|||||||
## About
|
## JADX
|
||||||
|
|
||||||
|
[](https://travis-ci.org/skylot/jadx)
|
||||||
|
[](https://drone.io/github.com/skylot/jadx/latest)
|
||||||
|
[](https://coveralls.io/r/skylot/jadx)
|
||||||
|
[](https://scan.coverity.com/projects/2166)
|
||||||
|
[](http://www.apache.org/licenses/LICENSE-2.0.html)
|
||||||
|
|
||||||
**jadx** - Dex to Java decompiler
|
**jadx** - Dex to Java decompiler
|
||||||
|
|
||||||
|
Command line and GUI tools for produce Java source code from Android Dex and Apk files
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
### Downloads
|
### Downloads
|
||||||
Latest version available at
|
- [unstable](https://drone.io/github.com/skylot/jadx/files)
|
||||||
[sourceforge](http://sourceforge.net/projects/jadx/files/)
|
- from [github](https://github.com/skylot/jadx/releases)
|
||||||
or
|
- from [sourceforge](http://sourceforge.net/projects/jadx/files/)
|
||||||
[bintray](http://bintray.com/pkg/show/general/skylot/jadx/jadx-bundle)
|
|
||||||
|
|
||||||
### Build
|
|
||||||
jadx uses [gradle](http://www.gradle.org/) for build:
|
|
||||||
|
|
||||||
|
### Building from source
|
||||||
git clone https://github.com/skylot/jadx.git
|
git clone https://github.com/skylot/jadx.git
|
||||||
cd jadx
|
cd jadx
|
||||||
./gradlew build
|
./gradlew dist
|
||||||
|
|
||||||
(on windows, use `gradlew.bat` instead of `./gradlew`)
|
(on Windows, use `gradlew.bat` instead of `./gradlew`)
|
||||||
|
|
||||||
|
Scripts for run jadx will be placed in `build/jadx/bin`
|
||||||
|
and also packed to `build/jadx-<version>.zip`
|
||||||
|
|
||||||
Scripts for run jadx will be placed in `build/install/jadx/bin`
|
|
||||||
and also packed to `build/distributions/jadx-<version>.zip`
|
|
||||||
|
|
||||||
### Run
|
### Run
|
||||||
Run **jadx** on itself:
|
Run **jadx** on itself:
|
||||||
|
|
||||||
cd build/install/jadx/
|
cd build/jadx/
|
||||||
bin/jadx -d out lib/jadx-*.jar
|
bin/jadx -d out lib/jadx-core-*.jar
|
||||||
|
#or
|
||||||
|
bin/jadx-gui lib/jadx-core-*.jar
|
||||||
|
|
||||||
|
|
||||||
### Usage
|
### Usage
|
||||||
```
|
```
|
||||||
jadx [options] <input files> (.dex, .apk, .jar or .class)
|
jadx[-gui] [options] <input file> (.dex, .apk, .jar or .class)
|
||||||
options:
|
options:
|
||||||
-d, --output-dir - output directory
|
-d, --output-dir - output directory
|
||||||
-j, --threads-count - processing threads count
|
-j, --threads-count - processing threads count
|
||||||
-f, --fallback - make simple dump (using goto instead of 'if', 'for', etc)
|
-f, --fallback - make simple dump (using goto instead of 'if', 'for', etc)
|
||||||
--not-obfuscated - set this flag if code not obfuscated
|
-r, --no-res - do not decode resources
|
||||||
--cfg - save methods control flow graph
|
-s, --no-src - do not decompile source code
|
||||||
--raw-cfg - save methods control flow graph (use raw instructions)
|
--show-bad-code - show inconsistent code (incorrectly decompiled)
|
||||||
-v, --verbose - verbose output
|
--cfg - save methods control flow graph to dot file
|
||||||
-h, --help - print this help
|
--raw-cfg - save methods control flow graph (use raw instructions)
|
||||||
|
-v, --verbose - verbose output
|
||||||
|
--deobf - activate deobfuscation
|
||||||
|
--deobf-min - min length of name
|
||||||
|
--deobf-max - max length of name
|
||||||
|
--deobf-rewrite-cfg - force to save deobfuscation map
|
||||||
|
-h, --help - print this help
|
||||||
Example:
|
Example:
|
||||||
jadx -d out classes.dex
|
jadx -d out classes.dex
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Troubleshooting
|
||||||
|
##### Out of memory error:
|
||||||
|
- Reduce processing threads count (`-j` option)
|
||||||
|
- Increase maximum java heap size:
|
||||||
|
* command line (example for linux):
|
||||||
|
`JAVA_OPTS="-Xmx4G" jadx -j 1 some.apk`
|
||||||
|
* edit 'jadx' script (jadx.bat on Windows) and setup bigger heap size:
|
||||||
|
`DEFAULT_JVM_OPTS="-Xmx2500M"`
|
||||||
|
|
||||||
|
|
||||||
|
### Contribution
|
||||||
|
|
||||||
|
To support this project you can:
|
||||||
|
- Post thoughts about new features/optimizations that important to you
|
||||||
|
- Submit bug using one of following patterns:
|
||||||
|
* Java code examples which decompiles incorrectly
|
||||||
|
* Error log and link to _public available_ apk file or app page on Google play
|
||||||
|
|
||||||
|
And any other comments will be very helpfull,
|
||||||
|
because at current stage of development it is very time consuming
|
||||||
|
to **find** new bugs, design and implement new features.
|
||||||
|
Also I need to **prioritize** these task for complete most important at first.
|
||||||
|
|
||||||
|
---------------------------------------
|
||||||
*Licensed under the Apache 2.0 License*
|
*Licensed under the Apache 2.0 License*
|
||||||
|
|
||||||
*Copyright 2013 by Skylot*
|
*Copyright 2015 by Skylot*
|
||||||
|
|||||||
+91
-76
@@ -1,94 +1,109 @@
|
|||||||
apply plugin: 'java'
|
buildscript {
|
||||||
apply plugin: 'application'
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
apply plugin: 'eclipse'
|
jcenter()
|
||||||
apply plugin: 'idea'
|
}
|
||||||
|
|
||||||
sourceCompatibility = 1.6
|
|
||||||
targetCompatibility = 1.6
|
|
||||||
|
|
||||||
version = file('version').readLines().get(0)
|
|
||||||
|
|
||||||
mainClassName = "jadx.Main"
|
|
||||||
manifest.mainAttributes("jadx-version" : version)
|
|
||||||
|
|
||||||
project.ext {
|
|
||||||
mainSamplesClass = "jadx.samples.RunTests"
|
|
||||||
samplesJadxSrcDir = "${buildDir}/samples-jadx/src"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
plugins {
|
||||||
compile 'com.google.android.tools:dx:1.7'
|
id "com.github.kt3k.coveralls" version "2.3.1"
|
||||||
compile 'com.beust:jcommander:1.30'
|
id "info.solidsoft.pitest" version "1.1.4"
|
||||||
compile 'org.slf4j:slf4j-api:1.6.6'
|
// id "com.github.ben-manes.versions" version "0.8"
|
||||||
compile 'ch.qos.logback:logback-classic:1.0.9'
|
|
||||||
testCompile 'junit:junit:4.8.2'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
apply plugin: 'sonar-runner'
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
|
|
||||||
sourceSets {
|
ext.jadxVersion = file('version').readLines().get(0)
|
||||||
samples
|
version = jadxVersion
|
||||||
//TODO don't add to eclipse classpath
|
|
||||||
samplesJadx {
|
subprojects {
|
||||||
java {
|
apply plugin: 'java'
|
||||||
srcDir samplesJadxSrcDir
|
apply plugin: 'groovy'
|
||||||
output.classesDir "${buildDir}/samples-jadx/output"
|
apply plugin: 'jacoco'
|
||||||
|
apply plugin: 'com.github.kt3k.coveralls'
|
||||||
|
|
||||||
|
version = jadxVersion
|
||||||
|
|
||||||
|
tasks.withType(JavaCompile) {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_1_6
|
||||||
|
targetCompatibility = JavaVersion.VERSION_1_6
|
||||||
|
|
||||||
|
if (!"$it".contains(':jadx-samples:')) {
|
||||||
|
options.compilerArgs << '-Xlint' << '-Xlint:unchecked' << '-Xlint:deprecation'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jar {
|
||||||
|
version = jadxVersion
|
||||||
|
manifest {
|
||||||
|
mainAttributes('jadx-version': jadxVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compile 'org.slf4j:slf4j-api:1.7.10'
|
||||||
|
|
||||||
|
testCompile 'ch.qos.logback:logback-classic:1.1.2'
|
||||||
|
testCompile 'junit:junit:4.12'
|
||||||
|
testCompile 'org.hamcrest:hamcrest-library:1.3'
|
||||||
|
testCompile 'org.mockito:mockito-core:1.10.19'
|
||||||
|
testCompile 'org.spockframework:spock-core:1.0-groovy-2.4'
|
||||||
|
testCompile 'cglib:cglib-nodep:3.1'
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
mavenLocal()
|
||||||
|
jcenter()
|
||||||
|
}
|
||||||
|
|
||||||
|
jacocoTestReport {
|
||||||
|
reports {
|
||||||
|
xml.enabled = true // coveralls plugin depends on xml format report
|
||||||
|
html.enabled = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
task samplesRun(type: JavaExec, dependsOn: compileSamplesJava) {
|
/* Sonar runner configuration */
|
||||||
classpath = sourceSets.samples.output
|
repositories {
|
||||||
main = mainSamplesClass
|
mavenCentral()
|
||||||
|
}
|
||||||
|
sonarRunner {
|
||||||
|
toolVersion = '2.4'
|
||||||
}
|
}
|
||||||
|
|
||||||
task samplesJar(type: Jar, dependsOn: samplesRun) {
|
task copyArtifacts(type: Sync, dependsOn: ['jadx-cli:installDist', 'jadx-gui:installDist']) {
|
||||||
baseName = 'samples'
|
destinationDir file("$buildDir/jadx")
|
||||||
from sourceSets.samples.output
|
['jadx-cli', 'jadx-gui'].each {
|
||||||
}
|
from tasks.getByPath(":${it}:installDist").destinationDir
|
||||||
|
|
||||||
task samplesJadxCreate(type: JavaExec, dependsOn: [compileJava, samplesJar]) {
|
|
||||||
classpath = sourceSets.main.output + configurations.compile
|
|
||||||
main = mainClassName
|
|
||||||
args = ['-d', samplesJadxSrcDir, samplesJar.archivePath]
|
|
||||||
}
|
|
||||||
|
|
||||||
compileSamplesJadxJava.dependsOn samplesJadxCreate
|
|
||||||
|
|
||||||
task samplesJadxRun(type: JavaExec, dependsOn: compileSamplesJadxJava) {
|
|
||||||
classpath = sourceSets.samplesJadx.output
|
|
||||||
main = mainSamplesClass
|
|
||||||
}
|
|
||||||
|
|
||||||
task samples (dependsOn: samplesJadxRun) {
|
|
||||||
}
|
|
||||||
|
|
||||||
//check.dependsOn samples
|
|
||||||
build.dependsOn distZip
|
|
||||||
build.dependsOn installApp
|
|
||||||
|
|
||||||
startScripts {
|
|
||||||
doLast {
|
|
||||||
// increase default max heap size
|
|
||||||
String var = 'DEFAULT_JVM_OPTS='
|
|
||||||
String args = '-Xmx1400M'
|
|
||||||
unixScript.text = unixScript.text.replace(var + '""', var + '"' + args + '"')
|
|
||||||
windowsScript.text = windowsScript.text.replace(var, var + args)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
applicationDistribution.with {
|
task pack(type: Zip, dependsOn: copyArtifacts) {
|
||||||
into('') {
|
destinationDir buildDir
|
||||||
from '.'
|
archiveName "jadx-${jadxVersion}.zip"
|
||||||
include 'README.md'
|
from copyArtifacts.destinationDir
|
||||||
include 'NOTICE'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
task dist(dependsOn: pack) {
|
||||||
|
description = 'Build jadx distribution zip'
|
||||||
|
}
|
||||||
|
|
||||||
|
task samples(dependsOn: 'jadx-samples:samples') {
|
||||||
|
}
|
||||||
|
|
||||||
|
task pitest(overwrite: true, dependsOn: 'jadx-core:pitest') {
|
||||||
|
}
|
||||||
|
|
||||||
|
task cleanBuildDir(type: Delete) {
|
||||||
|
delete buildDir
|
||||||
|
}
|
||||||
|
|
||||||
|
build.dependsOn(dist, samples)
|
||||||
|
|
||||||
|
clean.dependsOn(cleanBuildDir)
|
||||||
|
|
||||||
task wrapper(type: Wrapper) {
|
task wrapper(type: Wrapper) {
|
||||||
gradleVersion = '1.4'
|
gradleVersion = '2.3'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Vendored
BIN
Binary file not shown.
+1
-1
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
|
|||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=http\://services.gradle.org/distributions/gradle-1.4-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-2.3-all.zip
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
apply plugin: 'application'
|
||||||
|
|
||||||
|
mainClassName = 'jadx.cli.JadxCLI'
|
||||||
|
applicationName = 'jadx'
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compile(project(':jadx-core'))
|
||||||
|
compile 'com.beust:jcommander:1.47'
|
||||||
|
compile 'ch.qos.logback:logback-classic:1.1.2'
|
||||||
|
}
|
||||||
|
|
||||||
|
applicationDistribution.with {
|
||||||
|
into('') {
|
||||||
|
from '../.'
|
||||||
|
include 'README.md'
|
||||||
|
include 'NOTICE'
|
||||||
|
include 'LICENSE'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package jadx.cli;
|
||||||
|
|
||||||
|
import jadx.api.JadxDecompiler;
|
||||||
|
import jadx.core.utils.exceptions.JadxException;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class JadxCLI {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(JadxCLI.class);
|
||||||
|
|
||||||
|
public static void main(String[] args) throws JadxException {
|
||||||
|
try {
|
||||||
|
JadxCLIArgs jadxArgs = new JadxCLIArgs();
|
||||||
|
if (processArgs(jadxArgs, args)) {
|
||||||
|
processAndSave(jadxArgs);
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
LOG.error("jadx error: {}", e.getMessage(), e);
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void processAndSave(JadxCLIArgs jadxArgs) throws JadxException {
|
||||||
|
JadxDecompiler jadx = new JadxDecompiler(jadxArgs);
|
||||||
|
jadx.setOutputDir(jadxArgs.getOutDir());
|
||||||
|
jadx.loadFiles(jadxArgs.getInput());
|
||||||
|
jadx.save();
|
||||||
|
if (jadx.getErrorsCount() != 0) {
|
||||||
|
jadx.printErrorsReport();
|
||||||
|
LOG.error("finished with errors");
|
||||||
|
} else {
|
||||||
|
LOG.info("done");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean processArgs(JadxCLIArgs jadxArgs, String[] args) throws JadxException {
|
||||||
|
if (!jadxArgs.processArgs(args)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (jadxArgs.getInput().isEmpty()) {
|
||||||
|
LOG.error("Please specify input file");
|
||||||
|
jadxArgs.printUsage();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
File outputDir = jadxArgs.getOutDir();
|
||||||
|
if (outputDir == null) {
|
||||||
|
String outDirName;
|
||||||
|
File file = jadxArgs.getInput().get(0);
|
||||||
|
String name = file.getName();
|
||||||
|
int pos = name.lastIndexOf('.');
|
||||||
|
if (pos != -1) {
|
||||||
|
outDirName = name.substring(0, pos);
|
||||||
|
} else {
|
||||||
|
outDirName = name + "-jadx-out";
|
||||||
|
}
|
||||||
|
LOG.info("output directory: {}", outDirName);
|
||||||
|
outputDir = new File(outDirName);
|
||||||
|
jadxArgs.setOutputDir(outputDir);
|
||||||
|
}
|
||||||
|
if (outputDir.exists() && !outputDir.isDirectory()) {
|
||||||
|
throw new JadxException("Output directory exists as file " + outputDir);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,245 @@
|
|||||||
|
package jadx.cli;
|
||||||
|
|
||||||
|
import jadx.api.IJadxArgs;
|
||||||
|
import jadx.api.JadxDecompiler;
|
||||||
|
import jadx.core.utils.exceptions.JadxException;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.PrintStream;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.beust.jcommander.JCommander;
|
||||||
|
import com.beust.jcommander.Parameter;
|
||||||
|
import com.beust.jcommander.ParameterDescription;
|
||||||
|
import com.beust.jcommander.ParameterException;
|
||||||
|
|
||||||
|
public class JadxCLIArgs implements IJadxArgs {
|
||||||
|
|
||||||
|
@Parameter(description = "<input file> (.dex, .apk, .jar or .class)")
|
||||||
|
protected List<String> files;
|
||||||
|
|
||||||
|
@Parameter(names = {"-d", "--output-dir"}, description = "output directory")
|
||||||
|
protected String outDirName;
|
||||||
|
|
||||||
|
@Parameter(names = {"-j", "--threads-count"}, description = "processing threads count")
|
||||||
|
protected int threadsCount = Runtime.getRuntime().availableProcessors();
|
||||||
|
|
||||||
|
@Parameter(names = {"-f", "--fallback"}, description = "make simple dump (using goto instead of 'if', 'for', etc)")
|
||||||
|
protected boolean fallbackMode = false;
|
||||||
|
|
||||||
|
@Parameter(names = {"-r", "--no-res"}, description = "do not decode resources")
|
||||||
|
protected boolean skipResources = false;
|
||||||
|
|
||||||
|
@Parameter(names = {"-s", "--no-src"}, description = "do not decompile source code")
|
||||||
|
protected boolean skipSources = false;
|
||||||
|
|
||||||
|
@Parameter(names = {"--show-bad-code"}, description = "show inconsistent code (incorrectly decompiled)")
|
||||||
|
protected boolean showInconsistentCode = false;
|
||||||
|
|
||||||
|
@Parameter(names = {"--cfg"}, description = "save methods control flow graph to dot file")
|
||||||
|
protected boolean cfgOutput = false;
|
||||||
|
|
||||||
|
@Parameter(names = {"--raw-cfg"}, description = "save methods control flow graph (use raw instructions)")
|
||||||
|
protected boolean rawCfgOutput = false;
|
||||||
|
|
||||||
|
@Parameter(names = {"-v", "--verbose"}, description = "verbose output")
|
||||||
|
protected boolean verbose = false;
|
||||||
|
|
||||||
|
@Parameter(names = {"--deobf"}, description = "activate deobfuscation")
|
||||||
|
protected boolean deobfuscationOn = false;
|
||||||
|
|
||||||
|
@Parameter(names = {"--deobf-min"}, description = "min length of name")
|
||||||
|
protected int deobfuscationMinLength = 2;
|
||||||
|
|
||||||
|
@Parameter(names = {"--deobf-max"}, description = "max length of name")
|
||||||
|
protected int deobfuscationMaxLength = 64;
|
||||||
|
|
||||||
|
@Parameter(names = {"--deobf-rewrite-cfg"}, description = "force to save deobfuscation map")
|
||||||
|
protected boolean deobfuscationForceSave = false;
|
||||||
|
|
||||||
|
@Parameter(names = {"-h", "--help"}, description = "print this help", help = true)
|
||||||
|
protected boolean printHelp = false;
|
||||||
|
|
||||||
|
private final List<File> input = new ArrayList<File>(1);
|
||||||
|
private File outputDir;
|
||||||
|
|
||||||
|
public boolean processArgs(String[] args) {
|
||||||
|
return parse(args) && process();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean parse(String[] args) {
|
||||||
|
try {
|
||||||
|
new JCommander(this, args);
|
||||||
|
return true;
|
||||||
|
} catch (ParameterException e) {
|
||||||
|
System.err.println("Arguments parse error: " + e.getMessage());
|
||||||
|
printUsage();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean process() {
|
||||||
|
if (isPrintHelp()) {
|
||||||
|
printUsage();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (threadsCount <= 0) {
|
||||||
|
throw new JadxException("Threads count must be positive");
|
||||||
|
}
|
||||||
|
if (files != null) {
|
||||||
|
for (String fileName : files) {
|
||||||
|
File file = new File(fileName);
|
||||||
|
if (file.exists()) {
|
||||||
|
input.add(file);
|
||||||
|
} else {
|
||||||
|
throw new JadxException("File not found: " + file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (input.size() > 1) {
|
||||||
|
throw new JadxException("Only one input file is supported");
|
||||||
|
}
|
||||||
|
if (outDirName != null) {
|
||||||
|
outputDir = new File(outDirName);
|
||||||
|
}
|
||||||
|
if (isVerbose()) {
|
||||||
|
ch.qos.logback.classic.Logger rootLogger =
|
||||||
|
(ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
|
||||||
|
rootLogger.setLevel(ch.qos.logback.classic.Level.DEBUG);
|
||||||
|
}
|
||||||
|
} catch (JadxException e) {
|
||||||
|
System.err.println("ERROR: " + e.getMessage());
|
||||||
|
printUsage();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void printUsage() {
|
||||||
|
JCommander jc = new JCommander(this);
|
||||||
|
// print usage in not sorted fields order (by default its sorted by description)
|
||||||
|
PrintStream out = System.out;
|
||||||
|
out.println();
|
||||||
|
out.println("jadx - dex to java decompiler, version: " + JadxDecompiler.getVersion());
|
||||||
|
out.println();
|
||||||
|
out.println("usage: jadx [options] " + jc.getMainParameterDescription());
|
||||||
|
out.println("options:");
|
||||||
|
|
||||||
|
List<ParameterDescription> params = jc.getParameters();
|
||||||
|
Map<String, ParameterDescription> paramsMap = new LinkedHashMap<String, ParameterDescription>(params.size());
|
||||||
|
int maxNamesLen = 0;
|
||||||
|
for (ParameterDescription p : params) {
|
||||||
|
paramsMap.put(p.getParameterized().getName(), p);
|
||||||
|
int len = p.getNames().length();
|
||||||
|
if (len > maxNamesLen) {
|
||||||
|
maxNamesLen = len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Field[] fields = JadxCLIArgs.class.getDeclaredFields();
|
||||||
|
for (Field f : fields) {
|
||||||
|
String name = f.getName();
|
||||||
|
ParameterDescription p = paramsMap.get(name);
|
||||||
|
if (p == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
StringBuilder opt = new StringBuilder();
|
||||||
|
opt.append(' ').append(p.getNames());
|
||||||
|
addSpaces(opt, maxNamesLen - opt.length() + 2);
|
||||||
|
opt.append("- ").append(p.getDescription());
|
||||||
|
out.println(opt);
|
||||||
|
}
|
||||||
|
out.println("Example:");
|
||||||
|
out.println(" jadx -d out classes.dex");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addSpaces(StringBuilder str, int count) {
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
str.append(' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<File> getInput() {
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public File getOutDir() {
|
||||||
|
return outputDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOutputDir(File outputDir) {
|
||||||
|
this.outputDir = outputDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPrintHelp() {
|
||||||
|
return printHelp;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSkipResources() {
|
||||||
|
return skipResources;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSkipSources() {
|
||||||
|
return skipSources;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getThreadsCount() {
|
||||||
|
return threadsCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCFGOutput() {
|
||||||
|
return cfgOutput;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isRawCFGOutput() {
|
||||||
|
return rawCfgOutput;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFallbackMode() {
|
||||||
|
return fallbackMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isShowInconsistentCode() {
|
||||||
|
return showInconsistentCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isVerbose() {
|
||||||
|
return verbose;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDeobfuscationOn() {
|
||||||
|
return deobfuscationOn;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getDeobfuscationMinLength() {
|
||||||
|
return deobfuscationMinLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getDeobfuscationMaxLength() {
|
||||||
|
return deobfuscationMaxLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDeobfuscationForceSave() {
|
||||||
|
return deobfuscationForceSave;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<configuration>
|
||||||
|
|
||||||
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<encoder>
|
||||||
|
<pattern>%-5level - %msg%n</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<root level="INFO">
|
||||||
|
<appender-ref ref="STDOUT"/>
|
||||||
|
</root>
|
||||||
|
|
||||||
|
</configuration>
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
ext.jadxClasspath = 'clsp-data/android-5.1.jar'
|
||||||
|
|
||||||
|
apply plugin: "info.solidsoft.pitest"
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
runtime files(jadxClasspath)
|
||||||
|
|
||||||
|
compile files('lib/dx-1.10.jar')
|
||||||
|
compile 'commons-io:commons-io:2.4'
|
||||||
|
compile 'org.ow2.asm:asm:5.0.3'
|
||||||
|
compile 'com.intellij:annotations:12.0'
|
||||||
|
|
||||||
|
testCompile 'org.smali:smali:2.0.3'
|
||||||
|
}
|
||||||
|
|
||||||
|
task packTests(type: Jar) {
|
||||||
|
classifier = 'tests'
|
||||||
|
from sourceSets.test.output
|
||||||
|
}
|
||||||
|
|
||||||
|
pitest {
|
||||||
|
excludedMethods = ['toString']
|
||||||
|
threads = 4
|
||||||
|
enableDefaultIncrementalAnalysis = true
|
||||||
|
outputFormats = ['XML', 'HTML']
|
||||||
|
jvmArgs = ['-Xmx12G']
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,58 @@
|
|||||||
|
package jadx.api;
|
||||||
|
|
||||||
|
public final class CodePosition {
|
||||||
|
|
||||||
|
private final JavaClass cls;
|
||||||
|
private final int line;
|
||||||
|
private final int offset;
|
||||||
|
|
||||||
|
public CodePosition(JavaClass cls, int line, int offset) {
|
||||||
|
this.cls = cls;
|
||||||
|
this.line = line;
|
||||||
|
this.offset = offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CodePosition(int line, int offset) {
|
||||||
|
this.cls = null;
|
||||||
|
this.line = line;
|
||||||
|
this.offset = offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JavaClass getJavaClass() {
|
||||||
|
return cls;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLine() {
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getOffset() {
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSet() {
|
||||||
|
return line != 0 || offset != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
CodePosition that = (CodePosition) o;
|
||||||
|
return line == that.line && offset == that.offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return line + 31 * offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return line + ":" + offset + (cls != null ? " " + cls : "");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
package jadx.api;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
public class DefaultJadxArgs implements IJadxArgs {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public File getOutDir() {
|
||||||
|
return new File("jadx-output");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getThreadsCount() {
|
||||||
|
return Runtime.getRuntime().availableProcessors();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCFGOutput() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isRawCFGOutput() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFallbackMode() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isShowInconsistentCode() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isVerbose() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSkipResources() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSkipSources() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDeobfuscationOn() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getDeobfuscationMinLength() {
|
||||||
|
return Integer.MIN_VALUE + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getDeobfuscationMaxLength() {
|
||||||
|
return Integer.MAX_VALUE - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDeobfuscationForceSave() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package jadx.api;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
public interface IJadxArgs {
|
||||||
|
File getOutDir();
|
||||||
|
|
||||||
|
int getThreadsCount();
|
||||||
|
|
||||||
|
boolean isCFGOutput();
|
||||||
|
|
||||||
|
boolean isRawCFGOutput();
|
||||||
|
|
||||||
|
boolean isFallbackMode();
|
||||||
|
|
||||||
|
boolean isShowInconsistentCode();
|
||||||
|
|
||||||
|
boolean isVerbose();
|
||||||
|
|
||||||
|
boolean isSkipResources();
|
||||||
|
|
||||||
|
boolean isSkipSources();
|
||||||
|
|
||||||
|
boolean isDeobfuscationOn();
|
||||||
|
|
||||||
|
int getDeobfuscationMinLength();
|
||||||
|
|
||||||
|
int getDeobfuscationMaxLength();
|
||||||
|
|
||||||
|
boolean isDeobfuscationForceSave();
|
||||||
|
}
|
||||||
@@ -0,0 +1,315 @@
|
|||||||
|
package jadx.api;
|
||||||
|
|
||||||
|
import jadx.core.Jadx;
|
||||||
|
import jadx.core.ProcessClass;
|
||||||
|
import jadx.core.codegen.CodeGen;
|
||||||
|
import jadx.core.codegen.CodeWriter;
|
||||||
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
|
import jadx.core.dex.nodes.RootNode;
|
||||||
|
import jadx.core.dex.visitors.IDexTreeVisitor;
|
||||||
|
import jadx.core.dex.visitors.SaveCode;
|
||||||
|
import jadx.core.utils.exceptions.DecodeException;
|
||||||
|
import jadx.core.utils.exceptions.JadxException;
|
||||||
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
import jadx.core.utils.files.InputFile;
|
||||||
|
import jadx.core.xmlgen.BinaryXMLParser;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Jadx API usage example:
|
||||||
|
* <pre><code>
|
||||||
|
* JadxDecompiler jadx = new JadxDecompiler();
|
||||||
|
* jadx.loadFile(new File("classes.dex"));
|
||||||
|
* jadx.setOutputDir(new File("out"));
|
||||||
|
* jadx.save();
|
||||||
|
* </code></pre>
|
||||||
|
* <p/>
|
||||||
|
* Instead of 'save()' you can get list of decompiled classes:
|
||||||
|
* <pre><code>
|
||||||
|
* for(JavaClass cls : jadx.getClasses()) {
|
||||||
|
* System.out.println(cls.getCode());
|
||||||
|
* }
|
||||||
|
* </code></pre>
|
||||||
|
*/
|
||||||
|
public final class JadxDecompiler {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(JadxDecompiler.class);
|
||||||
|
|
||||||
|
private final IJadxArgs args;
|
||||||
|
private final List<InputFile> inputFiles = new ArrayList<InputFile>();
|
||||||
|
|
||||||
|
private File outDir;
|
||||||
|
|
||||||
|
private RootNode root;
|
||||||
|
private List<IDexTreeVisitor> passes;
|
||||||
|
private CodeGen codeGen;
|
||||||
|
|
||||||
|
private List<JavaClass> classes;
|
||||||
|
private List<ResourceFile> resources;
|
||||||
|
|
||||||
|
private BinaryXMLParser xmlParser;
|
||||||
|
|
||||||
|
public JadxDecompiler() {
|
||||||
|
this(new DefaultJadxArgs());
|
||||||
|
}
|
||||||
|
|
||||||
|
public JadxDecompiler(IJadxArgs jadxArgs) {
|
||||||
|
this.args = jadxArgs;
|
||||||
|
this.outDir = jadxArgs.getOutDir();
|
||||||
|
reset();
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOutputDir(File outDir) {
|
||||||
|
this.outDir = outDir;
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
void init() {
|
||||||
|
if (outDir == null) {
|
||||||
|
outDir = new DefaultJadxArgs().getOutDir();
|
||||||
|
}
|
||||||
|
this.passes = Jadx.getPassesList(args, outDir);
|
||||||
|
this.codeGen = new CodeGen(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset() {
|
||||||
|
classes = null;
|
||||||
|
resources = null;
|
||||||
|
xmlParser = null;
|
||||||
|
root = null;
|
||||||
|
passes = null;
|
||||||
|
codeGen = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getVersion() {
|
||||||
|
return Jadx.getVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadFile(File file) throws JadxException {
|
||||||
|
loadFiles(Collections.singletonList(file));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadFiles(List<File> files) throws JadxException {
|
||||||
|
if (files.isEmpty()) {
|
||||||
|
throw new JadxException("Empty file list");
|
||||||
|
}
|
||||||
|
inputFiles.clear();
|
||||||
|
for (File file : files) {
|
||||||
|
try {
|
||||||
|
inputFiles.add(new InputFile(file));
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new JadxException("Error load file: " + file, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parse();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void save() {
|
||||||
|
save(!args.isSkipSources(), !args.isSkipResources());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveSources() {
|
||||||
|
save(true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveResources() {
|
||||||
|
save(false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void save(boolean saveSources, boolean saveResources) {
|
||||||
|
try {
|
||||||
|
ExecutorService ex = getSaveExecutor(saveSources, saveResources);
|
||||||
|
ex.shutdown();
|
||||||
|
ex.awaitTermination(1, TimeUnit.DAYS);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new JadxRuntimeException("Save interrupted", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExecutorService getSaveExecutor() {
|
||||||
|
return getSaveExecutor(!args.isSkipSources(), !args.isSkipResources());
|
||||||
|
}
|
||||||
|
|
||||||
|
private ExecutorService getSaveExecutor(boolean saveSources, boolean saveResources) {
|
||||||
|
if (root == null) {
|
||||||
|
throw new JadxRuntimeException("No loaded files");
|
||||||
|
}
|
||||||
|
int threadsCount = args.getThreadsCount();
|
||||||
|
LOG.debug("processing threads count: {}", threadsCount);
|
||||||
|
|
||||||
|
LOG.info("processing ...");
|
||||||
|
ExecutorService executor = Executors.newFixedThreadPool(threadsCount);
|
||||||
|
if (saveSources) {
|
||||||
|
for (final JavaClass cls : getClasses()) {
|
||||||
|
executor.execute(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
cls.decompile();
|
||||||
|
SaveCode.save(outDir, args, cls.getClassNode());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (saveResources) {
|
||||||
|
for (final ResourceFile resourceFile : getResources()) {
|
||||||
|
executor.execute(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (ResourceType.isSupportedForUnpack(resourceFile.getType())) {
|
||||||
|
CodeWriter cw = resourceFile.getContent();
|
||||||
|
if (cw != null) {
|
||||||
|
cw.save(new File(outDir, resourceFile.getName()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return executor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<JavaClass> getClasses() {
|
||||||
|
if (root == null) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
if (classes == null) {
|
||||||
|
List<ClassNode> classNodeList = root.getClasses(false);
|
||||||
|
List<JavaClass> clsList = new ArrayList<JavaClass>(classNodeList.size());
|
||||||
|
for (ClassNode classNode : classNodeList) {
|
||||||
|
clsList.add(new JavaClass(classNode, this));
|
||||||
|
}
|
||||||
|
classes = Collections.unmodifiableList(clsList);
|
||||||
|
}
|
||||||
|
return classes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ResourceFile> getResources() {
|
||||||
|
if (resources == null) {
|
||||||
|
if (root == null) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
resources = new ResourcesLoader(this).load(inputFiles);
|
||||||
|
}
|
||||||
|
return resources;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<JavaPackage> getPackages() {
|
||||||
|
List<JavaClass> classList = getClasses();
|
||||||
|
if (classList.isEmpty()) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
Map<String, List<JavaClass>> map = new HashMap<String, List<JavaClass>>();
|
||||||
|
for (JavaClass javaClass : classList) {
|
||||||
|
String pkg = javaClass.getPackage();
|
||||||
|
List<JavaClass> clsList = map.get(pkg);
|
||||||
|
if (clsList == null) {
|
||||||
|
clsList = new ArrayList<JavaClass>();
|
||||||
|
map.put(pkg, clsList);
|
||||||
|
}
|
||||||
|
clsList.add(javaClass);
|
||||||
|
}
|
||||||
|
List<JavaPackage> packages = new ArrayList<JavaPackage>(map.size());
|
||||||
|
for (Map.Entry<String, List<JavaClass>> entry : map.entrySet()) {
|
||||||
|
packages.add(new JavaPackage(entry.getKey(), entry.getValue()));
|
||||||
|
}
|
||||||
|
Collections.sort(packages);
|
||||||
|
for (JavaPackage pkg : packages) {
|
||||||
|
Collections.sort(pkg.getClasses(), new Comparator<JavaClass>() {
|
||||||
|
@Override
|
||||||
|
public int compare(JavaClass o1, JavaClass o2) {
|
||||||
|
return o1.getName().compareTo(o2.getName());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Collections.unmodifiableList(packages);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getErrorsCount() {
|
||||||
|
if (root == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return root.getErrorsCounter().getErrorCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void printErrorsReport() {
|
||||||
|
if (root == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
root.getErrorsCounter().printReport();
|
||||||
|
}
|
||||||
|
|
||||||
|
void parse() throws DecodeException {
|
||||||
|
reset();
|
||||||
|
init();
|
||||||
|
|
||||||
|
root = new RootNode(args);
|
||||||
|
LOG.info("loading ...");
|
||||||
|
root.load(inputFiles);
|
||||||
|
|
||||||
|
root.initClassPath();
|
||||||
|
root.loadResources(getResources());
|
||||||
|
root.initAppResClass();
|
||||||
|
|
||||||
|
initVisitors();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initVisitors() {
|
||||||
|
for (IDexTreeVisitor pass : passes) {
|
||||||
|
try {
|
||||||
|
pass.init(root);
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.error("Visitor init failed: {}", pass.getClass().getSimpleName(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void processClass(ClassNode cls) {
|
||||||
|
ProcessClass.process(cls, passes, codeGen);
|
||||||
|
}
|
||||||
|
|
||||||
|
RootNode getRoot() {
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
BinaryXMLParser getXmlParser() {
|
||||||
|
if (xmlParser == null) {
|
||||||
|
xmlParser = new BinaryXMLParser(root);
|
||||||
|
}
|
||||||
|
return xmlParser;
|
||||||
|
}
|
||||||
|
|
||||||
|
JavaClass findJavaClass(ClassNode cls) {
|
||||||
|
if (cls == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (JavaClass javaClass : getClasses()) {
|
||||||
|
if (javaClass.getClassNode().equals(cls)) {
|
||||||
|
return javaClass;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IJadxArgs getArgs() {
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "jadx decompiler " + getVersion();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,210 @@
|
|||||||
|
package jadx.api;
|
||||||
|
|
||||||
|
import jadx.core.codegen.CodeWriter;
|
||||||
|
import jadx.core.dex.attributes.AFlag;
|
||||||
|
import jadx.core.dex.attributes.nodes.LineAttrNode;
|
||||||
|
import jadx.core.dex.info.AccessInfo;
|
||||||
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
|
import jadx.core.dex.nodes.FieldNode;
|
||||||
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public final class JavaClass implements JavaNode {
|
||||||
|
|
||||||
|
private final JadxDecompiler decompiler;
|
||||||
|
private final ClassNode cls;
|
||||||
|
private final JavaClass parent;
|
||||||
|
|
||||||
|
private List<JavaClass> innerClasses = Collections.emptyList();
|
||||||
|
private List<JavaField> fields = Collections.emptyList();
|
||||||
|
private List<JavaMethod> methods = Collections.emptyList();
|
||||||
|
|
||||||
|
JavaClass(ClassNode classNode, JadxDecompiler decompiler) {
|
||||||
|
this.decompiler = decompiler;
|
||||||
|
this.cls = classNode;
|
||||||
|
this.parent = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inner classes constructor
|
||||||
|
*/
|
||||||
|
JavaClass(ClassNode classNode, JavaClass parent) {
|
||||||
|
this.decompiler = null;
|
||||||
|
this.cls = classNode;
|
||||||
|
this.parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCode() {
|
||||||
|
CodeWriter code = cls.getCode();
|
||||||
|
if (code == null) {
|
||||||
|
decompile();
|
||||||
|
code = cls.getCode();
|
||||||
|
}
|
||||||
|
if (code == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return code.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void decompile() {
|
||||||
|
if (decompiler == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (cls.getCode() == null) {
|
||||||
|
decompiler.processClass(cls);
|
||||||
|
load();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ClassNode getClassNode() {
|
||||||
|
return cls;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void load() {
|
||||||
|
int inClsCount = cls.getInnerClasses().size();
|
||||||
|
if (inClsCount != 0) {
|
||||||
|
List<JavaClass> list = new ArrayList<JavaClass>(inClsCount);
|
||||||
|
for (ClassNode inner : cls.getInnerClasses()) {
|
||||||
|
if (!inner.contains(AFlag.DONT_GENERATE)) {
|
||||||
|
JavaClass javaClass = new JavaClass(inner, this);
|
||||||
|
javaClass.load();
|
||||||
|
list.add(javaClass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.innerClasses = Collections.unmodifiableList(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
int fieldsCount = cls.getFields().size();
|
||||||
|
if (fieldsCount != 0) {
|
||||||
|
List<JavaField> flds = new ArrayList<JavaField>(fieldsCount);
|
||||||
|
for (FieldNode f : cls.getFields()) {
|
||||||
|
if (!f.contains(AFlag.DONT_GENERATE)) {
|
||||||
|
flds.add(new JavaField(f, this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.fields = Collections.unmodifiableList(flds);
|
||||||
|
}
|
||||||
|
|
||||||
|
int methodsCount = cls.getMethods().size();
|
||||||
|
if (methodsCount != 0) {
|
||||||
|
List<JavaMethod> mths = new ArrayList<JavaMethod>(methodsCount);
|
||||||
|
for (MethodNode m : cls.getMethods()) {
|
||||||
|
if (!m.contains(AFlag.DONT_GENERATE)) {
|
||||||
|
mths.add(new JavaMethod(this, m));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Collections.sort(mths, new Comparator<JavaMethod>() {
|
||||||
|
@Override
|
||||||
|
public int compare(JavaMethod o1, JavaMethod o2) {
|
||||||
|
return o1.getName().compareTo(o2.getName());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.methods = Collections.unmodifiableList(mths);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<CodePosition, Object> getCodeAnnotations() {
|
||||||
|
decompile();
|
||||||
|
return cls.getCode().getAnnotations();
|
||||||
|
}
|
||||||
|
|
||||||
|
public CodePosition getDefinitionPosition(int line, int offset) {
|
||||||
|
Map<CodePosition, Object> map = getCodeAnnotations();
|
||||||
|
if (map.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Object obj = map.get(new CodePosition(line, offset));
|
||||||
|
if (!(obj instanceof LineAttrNode)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
ClassNode clsNode = null;
|
||||||
|
if (obj instanceof ClassNode) {
|
||||||
|
clsNode = (ClassNode) obj;
|
||||||
|
} else if (obj instanceof MethodNode) {
|
||||||
|
clsNode = ((MethodNode) obj).getParentClass();
|
||||||
|
} else if (obj instanceof FieldNode) {
|
||||||
|
clsNode = ((FieldNode) obj).getParentClass();
|
||||||
|
}
|
||||||
|
if (clsNode == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
clsNode = clsNode.getTopParentClass();
|
||||||
|
JavaClass jCls = decompiler.findJavaClass(clsNode);
|
||||||
|
if (jCls == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
jCls.decompile();
|
||||||
|
int defLine = ((LineAttrNode) obj).getDecompiledLine();
|
||||||
|
if (defLine == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new CodePosition(jCls, defLine, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getSourceLine(int decompiledLine) {
|
||||||
|
decompile();
|
||||||
|
return cls.getCode().getLineMapping().get(decompiledLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return cls.getShortName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getFullName() {
|
||||||
|
return cls.getFullName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPackage() {
|
||||||
|
return cls.getPackage();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JavaClass getDeclaringClass() {
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AccessInfo getAccessInfo() {
|
||||||
|
return cls.getAccessFlags();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<JavaClass> getInnerClasses() {
|
||||||
|
decompile();
|
||||||
|
return innerClasses;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<JavaField> getFields() {
|
||||||
|
decompile();
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<JavaMethod> getMethods() {
|
||||||
|
decompile();
|
||||||
|
return methods;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getDecompiledLine() {
|
||||||
|
return cls.getDecompiledLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
return this == o || o instanceof JavaClass && cls.equals(((JavaClass) o).cls);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return cls.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return cls.getFullName() + "[ " + getFullName() + " ]";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package jadx.api;
|
||||||
|
|
||||||
|
import jadx.core.dex.info.AccessInfo;
|
||||||
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
|
import jadx.core.dex.nodes.FieldNode;
|
||||||
|
|
||||||
|
public final class JavaField implements JavaNode {
|
||||||
|
|
||||||
|
private final FieldNode field;
|
||||||
|
private final JavaClass parent;
|
||||||
|
|
||||||
|
JavaField(FieldNode f, JavaClass cls) {
|
||||||
|
this.field = f;
|
||||||
|
this.parent = cls;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return field.getAlias();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getFullName() {
|
||||||
|
return parent.getFullName() + "." + getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JavaClass getDeclaringClass() {
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AccessInfo getAccessFlags() {
|
||||||
|
return field.getAccessFlags();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArgType getType() {
|
||||||
|
return field.getType();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getDecompiledLine() {
|
||||||
|
return field.getDecompiledLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package jadx.api;
|
||||||
|
|
||||||
|
import jadx.core.dex.info.AccessInfo;
|
||||||
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public final class JavaMethod implements JavaNode {
|
||||||
|
private final MethodNode mth;
|
||||||
|
private final JavaClass parent;
|
||||||
|
|
||||||
|
JavaMethod(JavaClass cls, MethodNode m) {
|
||||||
|
this.parent = cls;
|
||||||
|
this.mth = m;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return mth.getAlias();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getFullName() {
|
||||||
|
return mth.getMethodInfo().getFullName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JavaClass getDeclaringClass() {
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AccessInfo getAccessFlags() {
|
||||||
|
return mth.getAccessFlags();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ArgType> getArguments() {
|
||||||
|
return mth.getMethodInfo().getArgumentsTypes();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArgType getReturnType() {
|
||||||
|
return mth.getReturnType();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isConstructor() {
|
||||||
|
return mth.getMethodInfo().isConstructor();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isClassInit() {
|
||||||
|
return mth.getMethodInfo().isClassInit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getDecompiledLine() {
|
||||||
|
return mth.getDecompiledLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package jadx.api;
|
||||||
|
|
||||||
|
public interface JavaNode {
|
||||||
|
|
||||||
|
String getName();
|
||||||
|
|
||||||
|
String getFullName();
|
||||||
|
|
||||||
|
JavaClass getDeclaringClass();
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package jadx.api;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
|
||||||
|
private final String name;
|
||||||
|
private final List<JavaClass> classes;
|
||||||
|
|
||||||
|
JavaPackage(String name, List<JavaClass> classes) {
|
||||||
|
this.name = name;
|
||||||
|
this.classes = classes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getFullName() {
|
||||||
|
// TODO: store full package name
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<JavaClass> getClasses() {
|
||||||
|
return classes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JavaClass getDeclaringClass() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(@NotNull JavaPackage o) {
|
||||||
|
return name.compareTo(o.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
JavaPackage that = (JavaPackage) o;
|
||||||
|
return name.equals(that.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return name.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package jadx.api;
|
||||||
|
|
||||||
|
import jadx.core.codegen.CodeWriter;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
public class ResourceFile {
|
||||||
|
|
||||||
|
public static final class ZipRef {
|
||||||
|
private final File zipFile;
|
||||||
|
private final String entryName;
|
||||||
|
|
||||||
|
public ZipRef(File zipFile, String entryName) {
|
||||||
|
this.zipFile = zipFile;
|
||||||
|
this.entryName = entryName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public File getZipFile() {
|
||||||
|
return zipFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEntryName() {
|
||||||
|
return entryName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "ZipRef{" + zipFile + ", '" + entryName + "'}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final JadxDecompiler decompiler;
|
||||||
|
private final String name;
|
||||||
|
private final ResourceType type;
|
||||||
|
private ZipRef zipRef;
|
||||||
|
|
||||||
|
ResourceFile(JadxDecompiler decompiler, String name, ResourceType type) {
|
||||||
|
this.decompiler = decompiler;
|
||||||
|
this.name = name;
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResourceType getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CodeWriter getContent() {
|
||||||
|
return ResourcesLoader.loadContent(decompiler, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setZipRef(ZipRef zipRef) {
|
||||||
|
this.zipRef = zipRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
ZipRef getZipRef() {
|
||||||
|
return zipRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "ResourceFile{name='" + name + '\'' + ", type=" + type + "}";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package jadx.api;
|
||||||
|
|
||||||
|
public enum ResourceType {
|
||||||
|
CODE(".dex", ".class"),
|
||||||
|
MANIFEST("AndroidManifest.xml"),
|
||||||
|
XML(".xml"), // TODO binary or not?
|
||||||
|
ARSC(".arsc"), // TODO decompile !!!
|
||||||
|
FONT(".ttf"),
|
||||||
|
IMG(".png", ".gif", ".jpg"),
|
||||||
|
LIB(".so"),
|
||||||
|
UNKNOWN;
|
||||||
|
|
||||||
|
private final String[] exts;
|
||||||
|
|
||||||
|
ResourceType(String... exts) {
|
||||||
|
this.exts = exts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String[] getExts() {
|
||||||
|
return exts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ResourceType getFileType(String fileName) {
|
||||||
|
for (ResourceType type : ResourceType.values()) {
|
||||||
|
for (String ext : type.getExts()) {
|
||||||
|
if (fileName.endsWith(ext)) {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isSupportedForUnpack(ResourceType type) {
|
||||||
|
switch (type) {
|
||||||
|
case CODE:
|
||||||
|
case ARSC:
|
||||||
|
case LIB:
|
||||||
|
case FONT:
|
||||||
|
case IMG:
|
||||||
|
case UNKNOWN:
|
||||||
|
return false;
|
||||||
|
|
||||||
|
case MANIFEST:
|
||||||
|
case XML:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,169 @@
|
|||||||
|
package jadx.api;
|
||||||
|
|
||||||
|
import jadx.api.ResourceFile.ZipRef;
|
||||||
|
import jadx.core.codegen.CodeWriter;
|
||||||
|
import jadx.core.utils.Utils;
|
||||||
|
import jadx.core.utils.exceptions.JadxException;
|
||||||
|
import jadx.core.utils.files.InputFile;
|
||||||
|
import jadx.core.xmlgen.ResTableParser;
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipFile;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
// TODO: move to core package
|
||||||
|
public final class ResourcesLoader {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(ResourcesLoader.class);
|
||||||
|
|
||||||
|
private static final int READ_BUFFER_SIZE = 8 * 1024;
|
||||||
|
private static final int LOAD_SIZE_LIMIT = 10 * 1024 * 1024;
|
||||||
|
|
||||||
|
private final JadxDecompiler jadxRef;
|
||||||
|
|
||||||
|
ResourcesLoader(JadxDecompiler jadxRef) {
|
||||||
|
this.jadxRef = jadxRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<ResourceFile> load(List<InputFile> inputFiles) {
|
||||||
|
List<ResourceFile> list = new ArrayList<ResourceFile>(inputFiles.size());
|
||||||
|
for (InputFile file : inputFiles) {
|
||||||
|
loadFile(list, file.getFile());
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ResourceDecoder {
|
||||||
|
Object decode(long size, InputStream is) throws IOException;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Object decodeStream(ResourceFile rf, ResourceDecoder decoder) throws JadxException {
|
||||||
|
ZipRef zipRef = rf.getZipRef();
|
||||||
|
if (zipRef == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
ZipFile zipFile = null;
|
||||||
|
InputStream inputStream = null;
|
||||||
|
try {
|
||||||
|
zipFile = new ZipFile(zipRef.getZipFile());
|
||||||
|
ZipEntry entry = zipFile.getEntry(zipRef.getEntryName());
|
||||||
|
if (entry == null) {
|
||||||
|
throw new IOException("Zip entry not found: " + zipRef);
|
||||||
|
}
|
||||||
|
inputStream = new BufferedInputStream(zipFile.getInputStream(entry));
|
||||||
|
return decoder.decode(entry.getSize(), inputStream);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new JadxException("Error decode: " + zipRef.getEntryName(), e);
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
if (zipFile != null) {
|
||||||
|
zipFile.close();
|
||||||
|
}
|
||||||
|
if (inputStream != null) {
|
||||||
|
inputStream.close();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.debug("Error close zip file: {}", zipRef, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static CodeWriter loadContent(final JadxDecompiler jadxRef, final ResourceFile rf) {
|
||||||
|
try {
|
||||||
|
return (CodeWriter) decodeStream(rf, new ResourceDecoder() {
|
||||||
|
@Override
|
||||||
|
public Object decode(long size, InputStream is) throws IOException {
|
||||||
|
if (size > LOAD_SIZE_LIMIT) {
|
||||||
|
return new CodeWriter().add("File too big, size: "
|
||||||
|
+ String.format("%.2f KB", size / 1024.));
|
||||||
|
}
|
||||||
|
return loadContent(jadxRef, rf.getType(), is);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (JadxException e) {
|
||||||
|
LOG.error("Decode error", e);
|
||||||
|
CodeWriter cw = new CodeWriter();
|
||||||
|
cw.add("Error decode ").add(rf.getType().toString().toLowerCase());
|
||||||
|
cw.startLine(Utils.getStackTrace(e.getCause()));
|
||||||
|
return cw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CodeWriter loadContent(JadxDecompiler jadxRef, ResourceType type,
|
||||||
|
InputStream inputStream) throws IOException {
|
||||||
|
switch (type) {
|
||||||
|
case MANIFEST:
|
||||||
|
case XML:
|
||||||
|
return jadxRef.getXmlParser().parse(inputStream);
|
||||||
|
|
||||||
|
case ARSC:
|
||||||
|
return new ResTableParser().decodeToCodeWriter(inputStream);
|
||||||
|
}
|
||||||
|
return loadToCodeWriter(inputStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadFile(List<ResourceFile> list, File file) {
|
||||||
|
if (file == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ZipFile zip = null;
|
||||||
|
try {
|
||||||
|
zip = new ZipFile(file);
|
||||||
|
Enumeration<? extends ZipEntry> entries = zip.entries();
|
||||||
|
while (entries.hasMoreElements()) {
|
||||||
|
ZipEntry entry = entries.nextElement();
|
||||||
|
addEntry(list, file, entry);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.debug("Not a zip file: {}", file.getAbsolutePath());
|
||||||
|
} finally {
|
||||||
|
if (zip != null) {
|
||||||
|
try {
|
||||||
|
zip.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.error("Zip file close error: {}", file.getAbsolutePath(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addEntry(List<ResourceFile> list, File zipFile, ZipEntry entry) {
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String name = entry.getName();
|
||||||
|
ResourceType type = ResourceType.getFileType(name);
|
||||||
|
ResourceFile rf = new ResourceFile(jadxRef, name, type);
|
||||||
|
rf.setZipRef(new ZipRef(zipFile, name));
|
||||||
|
list.add(rf);
|
||||||
|
// LOG.debug("Add resource entry: {}, size: {}", name, entry.getSize());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CodeWriter loadToCodeWriter(InputStream is) throws IOException {
|
||||||
|
CodeWriter cw = new CodeWriter();
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream(READ_BUFFER_SIZE);
|
||||||
|
byte[] buffer = new byte[READ_BUFFER_SIZE];
|
||||||
|
int count;
|
||||||
|
try {
|
||||||
|
while ((count = is.read(buffer)) != -1) {
|
||||||
|
baos.write(buffer, 0, count);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
is.close();
|
||||||
|
} catch (Exception ignore) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cw.add(baos.toString("UTF-8"));
|
||||||
|
return cw;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package jadx.core;
|
||||||
|
|
||||||
|
public class Consts {
|
||||||
|
public static final boolean DEBUG = false;
|
||||||
|
|
||||||
|
public static final String CLASS_OBJECT = "java.lang.Object";
|
||||||
|
public static final String CLASS_STRING = "java.lang.String";
|
||||||
|
public static final String CLASS_CLASS = "java.lang.Class";
|
||||||
|
public static final String CLASS_THROWABLE = "java.lang.Throwable";
|
||||||
|
public static final String CLASS_ENUM = "java.lang.Enum";
|
||||||
|
|
||||||
|
public static final String CLASS_STRING_BUILDER = "java.lang.StringBuilder";
|
||||||
|
|
||||||
|
public static final String DALVIK_ANNOTATION_PKG = "dalvik.annotation.";
|
||||||
|
public static final String DALVIK_SIGNATURE = "dalvik.annotation.Signature";
|
||||||
|
public static final String DALVIK_INNER_CLASS = "dalvik.annotation.InnerClass";
|
||||||
|
public static final String DALVIK_THROWS = "dalvik.annotation.Throws";
|
||||||
|
public static final String DALVIK_ANNOTATION_DEFAULT = "dalvik.annotation.AnnotationDefault";
|
||||||
|
|
||||||
|
public static final String DEFAULT_PACKAGE_NAME = "defpackage";
|
||||||
|
public static final String ANONYMOUS_CLASS_PREFIX = "AnonymousClass";
|
||||||
|
|
||||||
|
public static final String MTH_TOSTRING_SIGNATURE = "toString()Ljava/lang/String;";
|
||||||
|
}
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
package jadx.core;
|
||||||
|
|
||||||
|
import jadx.api.IJadxArgs;
|
||||||
|
import jadx.core.dex.visitors.ClassModifier;
|
||||||
|
import jadx.core.dex.visitors.CodeShrinker;
|
||||||
|
import jadx.core.dex.visitors.ConstInlineVisitor;
|
||||||
|
import jadx.core.dex.visitors.DebugInfoVisitor;
|
||||||
|
import jadx.core.dex.visitors.DependencyCollector;
|
||||||
|
import jadx.core.dex.visitors.DotGraphVisitor;
|
||||||
|
import jadx.core.dex.visitors.EnumVisitor;
|
||||||
|
import jadx.core.dex.visitors.FallbackModeVisitor;
|
||||||
|
import jadx.core.dex.visitors.IDexTreeVisitor;
|
||||||
|
import jadx.core.dex.visitors.MethodInlineVisitor;
|
||||||
|
import jadx.core.dex.visitors.ModVisitor;
|
||||||
|
import jadx.core.dex.visitors.PrepareForCodeGen;
|
||||||
|
import jadx.core.dex.visitors.ReSugarCode;
|
||||||
|
import jadx.core.dex.visitors.RenameVisitor;
|
||||||
|
import jadx.core.dex.visitors.SimplifyVisitor;
|
||||||
|
import jadx.core.dex.visitors.blocksmaker.BlockExceptionHandler;
|
||||||
|
import jadx.core.dex.visitors.blocksmaker.BlockFinallyExtract;
|
||||||
|
import jadx.core.dex.visitors.blocksmaker.BlockFinish;
|
||||||
|
import jadx.core.dex.visitors.blocksmaker.BlockProcessor;
|
||||||
|
import jadx.core.dex.visitors.blocksmaker.BlockSplitter;
|
||||||
|
import jadx.core.dex.visitors.regions.CheckRegions;
|
||||||
|
import jadx.core.dex.visitors.regions.IfRegionVisitor;
|
||||||
|
import jadx.core.dex.visitors.regions.LoopRegionVisitor;
|
||||||
|
import jadx.core.dex.visitors.regions.ProcessVariables;
|
||||||
|
import jadx.core.dex.visitors.regions.RegionMakerVisitor;
|
||||||
|
import jadx.core.dex.visitors.regions.ReturnVisitor;
|
||||||
|
import jadx.core.dex.visitors.ssa.EliminatePhiNodes;
|
||||||
|
import jadx.core.dex.visitors.ssa.SSATransform;
|
||||||
|
import jadx.core.dex.visitors.typeinference.FinishTypeInference;
|
||||||
|
import jadx.core.dex.visitors.typeinference.TypeInference;
|
||||||
|
import jadx.core.utils.Utils;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.jar.Manifest;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class Jadx {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(Jadx.class);
|
||||||
|
|
||||||
|
static {
|
||||||
|
if (Consts.DEBUG) {
|
||||||
|
LOG.info("debug enabled");
|
||||||
|
}
|
||||||
|
if (Jadx.class.desiredAssertionStatus()) {
|
||||||
|
LOG.info("assertions enabled");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<IDexTreeVisitor> getPassesList(IJadxArgs args, File outDir) {
|
||||||
|
List<IDexTreeVisitor> passes = new ArrayList<IDexTreeVisitor>();
|
||||||
|
if (args.isFallbackMode()) {
|
||||||
|
passes.add(new FallbackModeVisitor());
|
||||||
|
} else {
|
||||||
|
passes.add(new BlockSplitter());
|
||||||
|
passes.add(new BlockProcessor());
|
||||||
|
passes.add(new BlockExceptionHandler());
|
||||||
|
passes.add(new BlockFinallyExtract());
|
||||||
|
passes.add(new BlockFinish());
|
||||||
|
|
||||||
|
passes.add(new SSATransform());
|
||||||
|
passes.add(new DebugInfoVisitor());
|
||||||
|
passes.add(new TypeInference());
|
||||||
|
|
||||||
|
if (args.isRawCFGOutput()) {
|
||||||
|
passes.add(DotGraphVisitor.dumpRaw(outDir));
|
||||||
|
}
|
||||||
|
|
||||||
|
passes.add(new ConstInlineVisitor());
|
||||||
|
passes.add(new FinishTypeInference());
|
||||||
|
passes.add(new EliminatePhiNodes());
|
||||||
|
|
||||||
|
passes.add(new ModVisitor());
|
||||||
|
|
||||||
|
passes.add(new CodeShrinker());
|
||||||
|
passes.add(new ReSugarCode());
|
||||||
|
|
||||||
|
if (args.isCFGOutput()) {
|
||||||
|
passes.add(DotGraphVisitor.dump(outDir));
|
||||||
|
}
|
||||||
|
|
||||||
|
passes.add(new RegionMakerVisitor());
|
||||||
|
passes.add(new IfRegionVisitor());
|
||||||
|
passes.add(new ReturnVisitor());
|
||||||
|
|
||||||
|
passes.add(new CodeShrinker());
|
||||||
|
passes.add(new SimplifyVisitor());
|
||||||
|
passes.add(new CheckRegions());
|
||||||
|
|
||||||
|
if (args.isCFGOutput()) {
|
||||||
|
passes.add(DotGraphVisitor.dumpRegions(outDir));
|
||||||
|
}
|
||||||
|
|
||||||
|
passes.add(new MethodInlineVisitor());
|
||||||
|
passes.add(new ClassModifier());
|
||||||
|
passes.add(new EnumVisitor());
|
||||||
|
passes.add(new PrepareForCodeGen());
|
||||||
|
passes.add(new LoopRegionVisitor());
|
||||||
|
passes.add(new ProcessVariables());
|
||||||
|
|
||||||
|
passes.add(new DependencyCollector());
|
||||||
|
|
||||||
|
passes.add(new RenameVisitor());
|
||||||
|
}
|
||||||
|
return passes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getVersion() {
|
||||||
|
try {
|
||||||
|
ClassLoader classLoader = Utils.class.getClassLoader();
|
||||||
|
if (classLoader != null) {
|
||||||
|
Enumeration<URL> resources = classLoader.getResources("META-INF/MANIFEST.MF");
|
||||||
|
while (resources.hasMoreElements()) {
|
||||||
|
Manifest manifest = new Manifest(resources.nextElement().openStream());
|
||||||
|
String ver = manifest.getMainAttributes().getValue("jadx-version");
|
||||||
|
if (ver != null) {
|
||||||
|
return ver;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.error("Can't get manifest file", e);
|
||||||
|
}
|
||||||
|
return "dev";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package jadx.core;
|
||||||
|
|
||||||
|
import jadx.core.codegen.CodeGen;
|
||||||
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
|
import jadx.core.dex.visitors.DepthTraversal;
|
||||||
|
import jadx.core.dex.visitors.IDexTreeVisitor;
|
||||||
|
import jadx.core.utils.ErrorsCounter;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import static jadx.core.dex.nodes.ProcessState.GENERATED;
|
||||||
|
import static jadx.core.dex.nodes.ProcessState.NOT_LOADED;
|
||||||
|
import static jadx.core.dex.nodes.ProcessState.PROCESSED;
|
||||||
|
import static jadx.core.dex.nodes.ProcessState.STARTED;
|
||||||
|
import static jadx.core.dex.nodes.ProcessState.UNLOADED;
|
||||||
|
|
||||||
|
public final class ProcessClass {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(ProcessClass.class);
|
||||||
|
|
||||||
|
private ProcessClass() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void process(ClassNode cls, List<IDexTreeVisitor> passes, @Nullable CodeGen codeGen) {
|
||||||
|
if (codeGen == null && cls.getState() == PROCESSED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
synchronized (cls) {
|
||||||
|
try {
|
||||||
|
if (cls.getState() == NOT_LOADED) {
|
||||||
|
cls.load();
|
||||||
|
cls.setState(STARTED);
|
||||||
|
for (IDexTreeVisitor visitor : passes) {
|
||||||
|
DepthTraversal.visit(visitor, cls);
|
||||||
|
}
|
||||||
|
cls.setState(PROCESSED);
|
||||||
|
}
|
||||||
|
if (cls.getState() == PROCESSED && codeGen != null) {
|
||||||
|
processDependencies(cls, passes);
|
||||||
|
codeGen.visit(cls);
|
||||||
|
cls.setState(GENERATED);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
ErrorsCounter.classError(cls, e.getClass().getSimpleName(), e);
|
||||||
|
} finally {
|
||||||
|
if (cls.getState() == GENERATED) {
|
||||||
|
cls.unload();
|
||||||
|
cls.setState(UNLOADED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void processDependencies(ClassNode cls, List<IDexTreeVisitor> passes) {
|
||||||
|
for (ClassNode depCls : cls.getDependencies()) {
|
||||||
|
process(depCls, passes, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,250 @@
|
|||||||
|
package jadx.core.clsp;
|
||||||
|
|
||||||
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
|
import jadx.core.dex.nodes.RootNode;
|
||||||
|
import jadx.core.utils.exceptions.DecodeException;
|
||||||
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
import jadx.core.utils.files.FileUtils;
|
||||||
|
|
||||||
|
import java.io.BufferedOutputStream;
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipInputStream;
|
||||||
|
import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Classes list for import into classpath graph
|
||||||
|
*/
|
||||||
|
public class ClsSet {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(ClsSet.class);
|
||||||
|
|
||||||
|
private static final String CLST_EXTENSION = ".jcst";
|
||||||
|
private static final String CLST_FILENAME = "core" + CLST_EXTENSION;
|
||||||
|
private static final String CLST_PKG_PATH = ClsSet.class.getPackage().getName().replace('.', '/');
|
||||||
|
|
||||||
|
private static final String JADX_CLS_SET_HEADER = "jadx-cst";
|
||||||
|
private static final int VERSION = 1;
|
||||||
|
|
||||||
|
private static final String STRING_CHARSET = "US-ASCII";
|
||||||
|
|
||||||
|
private NClass[] classes;
|
||||||
|
|
||||||
|
public void load(RootNode root) {
|
||||||
|
List<ClassNode> list = root.getClasses(true);
|
||||||
|
Map<String, NClass> names = new HashMap<String, NClass>(list.size());
|
||||||
|
int k = 0;
|
||||||
|
for (ClassNode cls : list) {
|
||||||
|
String clsRawName = cls.getRawName();
|
||||||
|
if (cls.getAccessFlags().isPublic()) {
|
||||||
|
NClass nClass = new NClass(clsRawName, k);
|
||||||
|
if (names.put(clsRawName, nClass) != null) {
|
||||||
|
throw new JadxRuntimeException("Duplicate class: " + clsRawName);
|
||||||
|
}
|
||||||
|
k++;
|
||||||
|
} else {
|
||||||
|
names.put(clsRawName, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
classes = new NClass[k];
|
||||||
|
k = 0;
|
||||||
|
for (ClassNode cls : list) {
|
||||||
|
if (cls.getAccessFlags().isPublic()) {
|
||||||
|
NClass nClass = getCls(cls.getRawName(), names);
|
||||||
|
if (nClass == null) {
|
||||||
|
throw new JadxRuntimeException("Missing class: " + cls);
|
||||||
|
}
|
||||||
|
nClass.setParents(makeParentsArray(cls, names));
|
||||||
|
classes[k] = nClass;
|
||||||
|
k++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static NClass[] makeParentsArray(ClassNode cls, Map<String, NClass> names) {
|
||||||
|
List<NClass> parents = new ArrayList<NClass>(1 + cls.getInterfaces().size());
|
||||||
|
ArgType superClass = cls.getSuperClass();
|
||||||
|
if (superClass != null) {
|
||||||
|
NClass c = getCls(superClass.getObject(), names);
|
||||||
|
if (c != null) {
|
||||||
|
parents.add(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (ArgType iface : cls.getInterfaces()) {
|
||||||
|
NClass c = getCls(iface.getObject(), names);
|
||||||
|
if (c != null) {
|
||||||
|
parents.add(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return parents.toArray(new NClass[parents.size()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static NClass getCls(String fullName, Map<String, NClass> names) {
|
||||||
|
NClass id = names.get(fullName);
|
||||||
|
if (id == null && !names.containsKey(fullName)) {
|
||||||
|
LOG.debug("Class not found: {}", fullName);
|
||||||
|
}
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void save(File output) throws IOException {
|
||||||
|
FileUtils.makeDirsForFile(output);
|
||||||
|
|
||||||
|
BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(output));
|
||||||
|
try {
|
||||||
|
String outputName = output.getName();
|
||||||
|
if (outputName.endsWith(CLST_EXTENSION)) {
|
||||||
|
save(outputStream);
|
||||||
|
} else if (outputName.endsWith(".jar")) {
|
||||||
|
ZipOutputStream out = new ZipOutputStream(outputStream);
|
||||||
|
try {
|
||||||
|
out.putNextEntry(new ZipEntry(CLST_PKG_PATH + "/" + CLST_FILENAME));
|
||||||
|
save(out);
|
||||||
|
} finally {
|
||||||
|
out.close();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new JadxRuntimeException("Unknown file format: " + outputName);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
outputStream.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void save(OutputStream output) throws IOException {
|
||||||
|
DataOutputStream out = new DataOutputStream(output);
|
||||||
|
try {
|
||||||
|
out.writeBytes(JADX_CLS_SET_HEADER);
|
||||||
|
out.writeByte(VERSION);
|
||||||
|
|
||||||
|
LOG.info("Classes count: {}", classes.length);
|
||||||
|
out.writeInt(classes.length);
|
||||||
|
for (NClass cls : classes) {
|
||||||
|
writeString(out, cls.getName());
|
||||||
|
}
|
||||||
|
for (NClass cls : classes) {
|
||||||
|
NClass[] parents = cls.getParents();
|
||||||
|
out.writeByte(parents.length);
|
||||||
|
for (NClass parent : parents) {
|
||||||
|
out.writeInt(parent.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
out.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void load() throws IOException, DecodeException {
|
||||||
|
InputStream input = getClass().getResourceAsStream(CLST_FILENAME);
|
||||||
|
if (input == null) {
|
||||||
|
throw new JadxRuntimeException("Can't load classpath file: " + CLST_FILENAME);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
load(input);
|
||||||
|
} finally {
|
||||||
|
input.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void load(File input) throws IOException, DecodeException {
|
||||||
|
String name = input.getName();
|
||||||
|
InputStream inputStream = new FileInputStream(input);
|
||||||
|
try {
|
||||||
|
if (name.endsWith(CLST_EXTENSION)) {
|
||||||
|
load(inputStream);
|
||||||
|
} else if (name.endsWith(".jar")) {
|
||||||
|
ZipInputStream in = new ZipInputStream(inputStream);
|
||||||
|
try {
|
||||||
|
ZipEntry entry = in.getNextEntry();
|
||||||
|
while (entry != null) {
|
||||||
|
if (entry.getName().endsWith(CLST_EXTENSION)) {
|
||||||
|
load(in);
|
||||||
|
}
|
||||||
|
entry = in.getNextEntry();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
in.close();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new JadxRuntimeException("Unknown file format: " + name);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
inputStream.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void load(InputStream input) throws IOException, DecodeException {
|
||||||
|
DataInputStream in = new DataInputStream(input);
|
||||||
|
try {
|
||||||
|
byte[] header = new byte[JADX_CLS_SET_HEADER.length()];
|
||||||
|
int readHeaderLength = in.read(header);
|
||||||
|
int version = in.readByte();
|
||||||
|
if (readHeaderLength != JADX_CLS_SET_HEADER.length()
|
||||||
|
|| !JADX_CLS_SET_HEADER.equals(new String(header, STRING_CHARSET))
|
||||||
|
|| version != VERSION) {
|
||||||
|
throw new DecodeException("Wrong jadx class set header");
|
||||||
|
}
|
||||||
|
int count = in.readInt();
|
||||||
|
classes = new NClass[count];
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
String name = readString(in);
|
||||||
|
classes[i] = new NClass(name, i);
|
||||||
|
}
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
int pCount = in.readByte();
|
||||||
|
NClass[] parents = new NClass[pCount];
|
||||||
|
for (int j = 0; j < pCount; j++) {
|
||||||
|
parents[j] = classes[in.readInt()];
|
||||||
|
}
|
||||||
|
classes[i].setParents(parents);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
in.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeString(DataOutputStream out, String name) throws IOException {
|
||||||
|
byte[] bytes = name.getBytes(STRING_CHARSET);
|
||||||
|
out.writeByte(bytes.length);
|
||||||
|
out.write(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String readString(DataInputStream in) throws IOException {
|
||||||
|
int len = in.readByte();
|
||||||
|
byte[] bytes = new byte[len];
|
||||||
|
int count = in.read(bytes);
|
||||||
|
while (count != len) {
|
||||||
|
int res = in.read(bytes, count, len - count);
|
||||||
|
if (res == -1) {
|
||||||
|
throw new IOException("String read error");
|
||||||
|
} else {
|
||||||
|
count += res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new String(bytes, STRING_CHARSET);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getClassesCount() {
|
||||||
|
return classes.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addToMap(Map<String, NClass> nameMap) {
|
||||||
|
for (NClass cls : classes) {
|
||||||
|
nameMap.put(cls.getName(), cls);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
package jadx.core.clsp;
|
||||||
|
|
||||||
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
|
import jadx.core.utils.exceptions.DecodeException;
|
||||||
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.WeakHashMap;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Classes hierarchy graph
|
||||||
|
*/
|
||||||
|
public class ClspGraph {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(ClspGraph.class);
|
||||||
|
|
||||||
|
private final Map<String, Set<String>> ancestorCache = new WeakHashMap<String, Set<String>>();
|
||||||
|
private Map<String, NClass> nameMap;
|
||||||
|
|
||||||
|
public void load() throws IOException, DecodeException {
|
||||||
|
ClsSet set = new ClsSet();
|
||||||
|
set.load();
|
||||||
|
addClasspath(set);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addClasspath(ClsSet set) {
|
||||||
|
if (nameMap == null) {
|
||||||
|
nameMap = new HashMap<String, NClass>(set.getClassesCount());
|
||||||
|
set.addToMap(nameMap);
|
||||||
|
} else {
|
||||||
|
throw new JadxRuntimeException("Classpath already loaded");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addApp(List<ClassNode> classes) {
|
||||||
|
if (nameMap == null) {
|
||||||
|
throw new JadxRuntimeException("Classpath must be loaded first");
|
||||||
|
}
|
||||||
|
int size = classes.size();
|
||||||
|
NClass[] nClasses = new NClass[size];
|
||||||
|
int k = 0;
|
||||||
|
for (ClassNode cls : classes) {
|
||||||
|
nClasses[k++] = addClass(cls);
|
||||||
|
}
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
nClasses[i].setParents(ClsSet.makeParentsArray(classes.get(i), nameMap));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private NClass addClass(ClassNode cls) {
|
||||||
|
String rawName = cls.getRawName();
|
||||||
|
NClass nClass = new NClass(rawName, -1);
|
||||||
|
nameMap.put(rawName, nClass);
|
||||||
|
return nClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isImplements(String clsName, String implClsName) {
|
||||||
|
Set<String> anc = getAncestors(clsName);
|
||||||
|
return anc.contains(implClsName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCommonAncestor(String clsName, String implClsName) {
|
||||||
|
if (clsName.equals(implClsName)) {
|
||||||
|
return clsName;
|
||||||
|
}
|
||||||
|
NClass cls = nameMap.get(implClsName);
|
||||||
|
if (cls == null) {
|
||||||
|
LOG.debug("Missing class: {}", implClsName);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (isImplements(clsName, implClsName)) {
|
||||||
|
return implClsName;
|
||||||
|
}
|
||||||
|
Set<String> anc = getAncestors(clsName);
|
||||||
|
return searchCommonParent(anc, cls);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String searchCommonParent(Set<String> anc, NClass cls) {
|
||||||
|
for (NClass p : cls.getParents()) {
|
||||||
|
String name = p.getName();
|
||||||
|
if (anc.contains(name)) {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
String r = searchCommonParent(anc, p);
|
||||||
|
if (r != null) {
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<String> getAncestors(String clsName) {
|
||||||
|
Set<String> result = ancestorCache.get(clsName);
|
||||||
|
if (result != null) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
NClass cls = nameMap.get(clsName);
|
||||||
|
if (cls == null) {
|
||||||
|
LOG.debug("Missing class: {}", clsName);
|
||||||
|
return Collections.emptySet();
|
||||||
|
}
|
||||||
|
result = new HashSet<String>();
|
||||||
|
addAncestorsNames(cls, result);
|
||||||
|
if (result.isEmpty()) {
|
||||||
|
result = Collections.emptySet();
|
||||||
|
}
|
||||||
|
ancestorCache.put(clsName, result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addAncestorsNames(NClass cls, Set<String> result) {
|
||||||
|
result.add(cls.getName());
|
||||||
|
for (NClass p : cls.getParents()) {
|
||||||
|
addAncestorsNames(p, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
package jadx.core.clsp;
|
||||||
|
|
||||||
|
import jadx.api.DefaultJadxArgs;
|
||||||
|
import jadx.core.dex.nodes.RootNode;
|
||||||
|
import jadx.core.utils.exceptions.DecodeException;
|
||||||
|
import jadx.core.utils.files.InputFile;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class for convert dex or jar to jadx classes set (.jcst)
|
||||||
|
*/
|
||||||
|
public class ConvertToClsSet {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(ConvertToClsSet.class);
|
||||||
|
|
||||||
|
public static void usage() {
|
||||||
|
LOG.info("<output .jcst or .jar file> <several input dex or jar files> ");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) throws IOException, DecodeException {
|
||||||
|
if (args.length < 2) {
|
||||||
|
usage();
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
File output = new File(args[0]);
|
||||||
|
|
||||||
|
List<InputFile> inputFiles = new ArrayList<InputFile>(args.length - 1);
|
||||||
|
for (int i = 1; i < args.length; i++) {
|
||||||
|
File f = new File(args[i]);
|
||||||
|
if (f.isDirectory()) {
|
||||||
|
addFilesFromDirectory(f, inputFiles);
|
||||||
|
} else {
|
||||||
|
inputFiles.add(new InputFile(f));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (InputFile inputFile : inputFiles) {
|
||||||
|
LOG.info("Loaded: {}", inputFile.getFile());
|
||||||
|
}
|
||||||
|
|
||||||
|
RootNode root = new RootNode(new DefaultJadxArgs());
|
||||||
|
root.load(inputFiles);
|
||||||
|
|
||||||
|
ClsSet set = new ClsSet();
|
||||||
|
set.load(root);
|
||||||
|
set.save(output);
|
||||||
|
LOG.info("Output: {}", output);
|
||||||
|
LOG.info("done");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addFilesFromDirectory(File dir,
|
||||||
|
List<InputFile> inputFiles) throws IOException, DecodeException {
|
||||||
|
File[] files = dir.listFiles();
|
||||||
|
if (files == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (File file : files) {
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
addFilesFromDirectory(file, inputFiles);
|
||||||
|
}
|
||||||
|
String fileName = file.getName();
|
||||||
|
if (fileName.endsWith(".dex")
|
||||||
|
|| fileName.endsWith(".jar")
|
||||||
|
|| fileName.endsWith(".apk")) {
|
||||||
|
inputFiles.add(new InputFile(file));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package jadx.core.clsp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class node in classpath graph
|
||||||
|
*/
|
||||||
|
public class NClass {
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
private NClass[] parents;
|
||||||
|
private int id;
|
||||||
|
|
||||||
|
public NClass(String name, int id) {
|
||||||
|
this.name = name;
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(int id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NClass[] getParents() {
|
||||||
|
return parents;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setParents(NClass[] parents) {
|
||||||
|
this.parents = parents;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return name.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
NClass nClass = (NClass) o;
|
||||||
|
return name.equals(nClass.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
package jadx.core.codegen;
|
||||||
|
|
||||||
|
import jadx.core.Consts;
|
||||||
|
import jadx.core.dex.attributes.AType;
|
||||||
|
import jadx.core.dex.attributes.IAttributeNode;
|
||||||
|
import jadx.core.dex.attributes.annotations.Annotation;
|
||||||
|
import jadx.core.dex.attributes.annotations.AnnotationsList;
|
||||||
|
import jadx.core.dex.attributes.annotations.MethodParameters;
|
||||||
|
import jadx.core.dex.info.FieldInfo;
|
||||||
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
|
import jadx.core.dex.nodes.FieldNode;
|
||||||
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
|
import jadx.core.utils.StringUtils;
|
||||||
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
|
public class AnnotationGen {
|
||||||
|
|
||||||
|
private final ClassNode cls;
|
||||||
|
private final ClassGen classGen;
|
||||||
|
|
||||||
|
public AnnotationGen(ClassNode cls, ClassGen classGen) {
|
||||||
|
this.cls = cls;
|
||||||
|
this.classGen = classGen;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addForClass(CodeWriter code) {
|
||||||
|
add(cls, code);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addForMethod(CodeWriter code, MethodNode mth) {
|
||||||
|
add(mth, code);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addForField(CodeWriter code, FieldNode field) {
|
||||||
|
add(field, code);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addForParameter(CodeWriter code, MethodParameters paramsAnnotations, int n) {
|
||||||
|
List<AnnotationsList> paramList = paramsAnnotations.getParamList();
|
||||||
|
if (n >= paramList.size()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
AnnotationsList aList = paramList.get(n);
|
||||||
|
if (aList == null || aList.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (Annotation a : aList.getAll()) {
|
||||||
|
formatAnnotation(code, a);
|
||||||
|
code.add(' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void add(IAttributeNode node, CodeWriter code) {
|
||||||
|
AnnotationsList aList = node.get(AType.ANNOTATION_LIST);
|
||||||
|
if (aList == null || aList.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (Annotation a : aList.getAll()) {
|
||||||
|
String aCls = a.getAnnotationClass();
|
||||||
|
if (aCls.startsWith(Consts.DALVIK_ANNOTATION_PKG)) {
|
||||||
|
// skip
|
||||||
|
if (Consts.DEBUG) {
|
||||||
|
code.startLine("// " + a);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
code.startLine();
|
||||||
|
formatAnnotation(code, a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void formatAnnotation(CodeWriter code, Annotation a) {
|
||||||
|
code.add('@');
|
||||||
|
classGen.useType(code, a.getType());
|
||||||
|
Map<String, Object> vl = a.getValues();
|
||||||
|
if (!vl.isEmpty()) {
|
||||||
|
code.add('(');
|
||||||
|
if (vl.size() == 1 && vl.containsKey("value")) {
|
||||||
|
encodeValue(code, vl.get("value"));
|
||||||
|
} else {
|
||||||
|
for (Iterator<Entry<String, Object>> it = vl.entrySet().iterator(); it.hasNext(); ) {
|
||||||
|
Entry<String, Object> e = it.next();
|
||||||
|
code.add(e.getKey());
|
||||||
|
code.add(" = ");
|
||||||
|
encodeValue(code, e.getValue());
|
||||||
|
if (it.hasNext()) {
|
||||||
|
code.add(", ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
code.add(')');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public void addThrows(MethodNode mth, CodeWriter code) {
|
||||||
|
Annotation an = mth.getAnnotation(Consts.DALVIK_THROWS);
|
||||||
|
if (an != null) {
|
||||||
|
Object exs = an.getDefaultValue();
|
||||||
|
code.add(" throws ");
|
||||||
|
for (Iterator<ArgType> it = ((List<ArgType>) exs).iterator(); it.hasNext(); ) {
|
||||||
|
ArgType ex = it.next();
|
||||||
|
classGen.useType(code, ex);
|
||||||
|
if (it.hasNext()) {
|
||||||
|
code.add(", ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getAnnotationDefaultValue(String name) {
|
||||||
|
Annotation an = cls.getAnnotation(Consts.DALVIK_ANNOTATION_DEFAULT);
|
||||||
|
if (an != null) {
|
||||||
|
Annotation defAnnotation = (Annotation) an.getDefaultValue();
|
||||||
|
return defAnnotation.getValues().get(name);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: refactor this boilerplate code
|
||||||
|
public void encodeValue(CodeWriter code, Object val) {
|
||||||
|
if (val == null) {
|
||||||
|
code.add("null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (val instanceof String) {
|
||||||
|
code.add(StringUtils.unescapeString((String) val));
|
||||||
|
} else if (val instanceof Integer) {
|
||||||
|
code.add(TypeGen.formatInteger((Integer) val));
|
||||||
|
} else if (val instanceof Character) {
|
||||||
|
code.add(StringUtils.unescapeChar((Character) val));
|
||||||
|
} else if (val instanceof Boolean) {
|
||||||
|
code.add(Boolean.TRUE.equals(val) ? "true" : "false");
|
||||||
|
} else if (val instanceof Float) {
|
||||||
|
code.add(TypeGen.formatFloat((Float) val));
|
||||||
|
} else if (val instanceof Double) {
|
||||||
|
code.add(TypeGen.formatDouble((Double) val));
|
||||||
|
} else if (val instanceof Long) {
|
||||||
|
code.add(TypeGen.formatLong((Long) val));
|
||||||
|
} else if (val instanceof Short) {
|
||||||
|
code.add(TypeGen.formatShort((Short) val));
|
||||||
|
} else if (val instanceof Byte) {
|
||||||
|
code.add(TypeGen.formatByte((Byte) val));
|
||||||
|
} else if (val instanceof ArgType) {
|
||||||
|
classGen.useType(code, (ArgType) val);
|
||||||
|
code.add(".class");
|
||||||
|
} else if (val instanceof FieldInfo) {
|
||||||
|
// must be a static field
|
||||||
|
FieldInfo field = (FieldInfo) val;
|
||||||
|
InsnGen.makeStaticFieldAccess(code, field, classGen);
|
||||||
|
} else if (val instanceof Iterable) {
|
||||||
|
code.add('{');
|
||||||
|
Iterator<?> it = ((Iterable) val).iterator();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
Object obj = it.next();
|
||||||
|
encodeValue(code, obj);
|
||||||
|
if (it.hasNext()) {
|
||||||
|
code.add(", ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
code.add('}');
|
||||||
|
} else if (val instanceof Annotation) {
|
||||||
|
formatAnnotation(code, (Annotation) val);
|
||||||
|
} else {
|
||||||
|
// TODO: also can be method values
|
||||||
|
throw new JadxRuntimeException("Can't decode value: " + val + " (" + val.getClass() + ")");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,580 @@
|
|||||||
|
package jadx.core.codegen;
|
||||||
|
|
||||||
|
import jadx.api.IJadxArgs;
|
||||||
|
import jadx.core.dex.attributes.AFlag;
|
||||||
|
import jadx.core.dex.attributes.AType;
|
||||||
|
import jadx.core.dex.attributes.AttrNode;
|
||||||
|
import jadx.core.dex.attributes.nodes.EnumClassAttr;
|
||||||
|
import jadx.core.dex.attributes.nodes.EnumClassAttr.EnumField;
|
||||||
|
import jadx.core.dex.attributes.nodes.SourceFileAttr;
|
||||||
|
import jadx.core.dex.info.AccessInfo;
|
||||||
|
import jadx.core.dex.info.ClassInfo;
|
||||||
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
|
import jadx.core.dex.instructions.args.PrimitiveType;
|
||||||
|
import jadx.core.dex.instructions.mods.ConstructorInsn;
|
||||||
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
|
import jadx.core.dex.nodes.DexNode;
|
||||||
|
import jadx.core.dex.nodes.FieldNode;
|
||||||
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
|
import jadx.core.dex.nodes.parser.FieldValueAttr;
|
||||||
|
import jadx.core.utils.ErrorsCounter;
|
||||||
|
import jadx.core.utils.Utils;
|
||||||
|
import jadx.core.utils.exceptions.CodegenException;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.android.dx.rop.code.AccessFlags;
|
||||||
|
|
||||||
|
public class ClassGen {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(ClassGen.class);
|
||||||
|
|
||||||
|
public static final Comparator<MethodNode> METHOD_LINE_COMPARATOR = new Comparator<MethodNode>() {
|
||||||
|
@Override
|
||||||
|
public int compare(MethodNode a, MethodNode b) {
|
||||||
|
return Utils.compare(a.getSourceLine(), b.getSourceLine());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private final ClassNode cls;
|
||||||
|
private final ClassGen parentGen;
|
||||||
|
private final AnnotationGen annotationGen;
|
||||||
|
private final boolean fallback;
|
||||||
|
private final boolean showInconsistentCode;
|
||||||
|
|
||||||
|
private final Set<ClassInfo> imports = new HashSet<ClassInfo>();
|
||||||
|
private int clsDeclLine;
|
||||||
|
|
||||||
|
public ClassGen(ClassNode cls, IJadxArgs jadxArgs) {
|
||||||
|
this(cls, null, jadxArgs.isFallbackMode(), jadxArgs.isShowInconsistentCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClassGen(ClassNode cls, ClassGen parentClsGen) {
|
||||||
|
this(cls, parentClsGen, parentClsGen.fallback, parentClsGen.showInconsistentCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClassGen(ClassNode cls, ClassGen parentClsGen, boolean fallback, boolean showBadCode) {
|
||||||
|
this.cls = cls;
|
||||||
|
this.parentGen = parentClsGen;
|
||||||
|
this.fallback = fallback;
|
||||||
|
this.showInconsistentCode = showBadCode;
|
||||||
|
|
||||||
|
this.annotationGen = new AnnotationGen(cls, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClassNode getClassNode() {
|
||||||
|
return cls;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CodeWriter makeClass() throws CodegenException {
|
||||||
|
CodeWriter clsBody = new CodeWriter();
|
||||||
|
addClassCode(clsBody);
|
||||||
|
|
||||||
|
CodeWriter clsCode = new CodeWriter();
|
||||||
|
if (!"".equals(cls.getPackage())) {
|
||||||
|
clsCode.add("package ").add(cls.getPackage()).add(';');
|
||||||
|
clsCode.newLine();
|
||||||
|
}
|
||||||
|
int importsCount = imports.size();
|
||||||
|
if (importsCount != 0) {
|
||||||
|
List<String> sortImports = new ArrayList<String>(importsCount);
|
||||||
|
for (ClassInfo ic : imports) {
|
||||||
|
sortImports.add(ic.getAlias().getFullName());
|
||||||
|
}
|
||||||
|
Collections.sort(sortImports);
|
||||||
|
|
||||||
|
for (String imp : sortImports) {
|
||||||
|
clsCode.startLine("import ").add(imp).add(';');
|
||||||
|
}
|
||||||
|
clsCode.newLine();
|
||||||
|
|
||||||
|
sortImports.clear();
|
||||||
|
imports.clear();
|
||||||
|
}
|
||||||
|
clsCode.add(clsBody);
|
||||||
|
return clsCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addClassCode(CodeWriter code) throws CodegenException {
|
||||||
|
if (cls.contains(AFlag.DONT_GENERATE)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (cls.contains(AFlag.INCONSISTENT_CODE)) {
|
||||||
|
code.startLine("// jadx: inconsistent code");
|
||||||
|
}
|
||||||
|
addClassDeclaration(code);
|
||||||
|
addClassBody(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addClassDeclaration(CodeWriter clsCode) {
|
||||||
|
AccessInfo af = cls.getAccessFlags();
|
||||||
|
if (af.isInterface()) {
|
||||||
|
af = af.remove(AccessFlags.ACC_ABSTRACT)
|
||||||
|
.remove(AccessFlags.ACC_STATIC);
|
||||||
|
} else if (af.isEnum()) {
|
||||||
|
af = af.remove(AccessFlags.ACC_FINAL)
|
||||||
|
.remove(AccessFlags.ACC_ABSTRACT)
|
||||||
|
.remove(AccessFlags.ACC_STATIC);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 'static' and 'private' modifier not allowed for top classes (not inner)
|
||||||
|
if (!cls.getAlias().isInner()) {
|
||||||
|
af = af.remove(AccessFlags.ACC_STATIC).remove(AccessFlags.ACC_PRIVATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
annotationGen.addForClass(clsCode);
|
||||||
|
insertSourceFileInfo(clsCode, cls);
|
||||||
|
insertRenameInfo(clsCode, cls);
|
||||||
|
clsCode.startLine(af.makeString());
|
||||||
|
if (af.isInterface()) {
|
||||||
|
if (af.isAnnotation()) {
|
||||||
|
clsCode.add('@');
|
||||||
|
}
|
||||||
|
clsCode.add("interface ");
|
||||||
|
} else if (af.isEnum()) {
|
||||||
|
clsCode.add("enum ");
|
||||||
|
} else {
|
||||||
|
clsCode.add("class ");
|
||||||
|
}
|
||||||
|
clsCode.add(cls.getShortName());
|
||||||
|
|
||||||
|
addGenericMap(clsCode, cls.getGenericMap());
|
||||||
|
clsCode.add(' ');
|
||||||
|
|
||||||
|
ArgType sup = cls.getSuperClass();
|
||||||
|
if (sup != null
|
||||||
|
&& !sup.equals(ArgType.OBJECT)
|
||||||
|
&& !sup.getObject().equals(ArgType.ENUM.getObject())) {
|
||||||
|
clsCode.add("extends ");
|
||||||
|
useClass(clsCode, sup);
|
||||||
|
clsCode.add(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cls.getInterfaces().isEmpty() && !af.isAnnotation()) {
|
||||||
|
if (cls.getAccessFlags().isInterface()) {
|
||||||
|
clsCode.add("extends ");
|
||||||
|
} else {
|
||||||
|
clsCode.add("implements ");
|
||||||
|
}
|
||||||
|
for (Iterator<ArgType> it = cls.getInterfaces().iterator(); it.hasNext(); ) {
|
||||||
|
ArgType interf = it.next();
|
||||||
|
useClass(clsCode, interf);
|
||||||
|
if (it.hasNext()) {
|
||||||
|
clsCode.add(", ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!cls.getInterfaces().isEmpty()) {
|
||||||
|
clsCode.add(' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
clsCode.attachDefinition(cls);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean addGenericMap(CodeWriter code, Map<ArgType, List<ArgType>> gmap) {
|
||||||
|
if (gmap == null || gmap.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
code.add('<');
|
||||||
|
int i = 0;
|
||||||
|
for (Entry<ArgType, List<ArgType>> e : gmap.entrySet()) {
|
||||||
|
ArgType type = e.getKey();
|
||||||
|
List<ArgType> list = e.getValue();
|
||||||
|
if (i != 0) {
|
||||||
|
code.add(", ");
|
||||||
|
}
|
||||||
|
if (type.isGenericType()) {
|
||||||
|
code.add(type.getObject());
|
||||||
|
} else {
|
||||||
|
useClass(code, type);
|
||||||
|
}
|
||||||
|
if (list != null && !list.isEmpty()) {
|
||||||
|
code.add(" extends ");
|
||||||
|
for (Iterator<ArgType> it = list.iterator(); it.hasNext(); ) {
|
||||||
|
ArgType g = it.next();
|
||||||
|
if (g.isGenericType()) {
|
||||||
|
code.add(g.getObject());
|
||||||
|
} else {
|
||||||
|
useClass(code, g);
|
||||||
|
}
|
||||||
|
if (it.hasNext()) {
|
||||||
|
code.add(" & ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
code.add('>');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addClassBody(CodeWriter clsCode) throws CodegenException {
|
||||||
|
clsCode.add('{');
|
||||||
|
clsDeclLine = clsCode.getLine();
|
||||||
|
clsCode.incIndent();
|
||||||
|
addFields(clsCode);
|
||||||
|
addInnerClasses(clsCode, cls);
|
||||||
|
addMethods(clsCode);
|
||||||
|
clsCode.decIndent();
|
||||||
|
clsCode.startLine('}');
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addInnerClasses(CodeWriter code, ClassNode cls) throws CodegenException {
|
||||||
|
for (ClassNode innerCls : cls.getInnerClasses()) {
|
||||||
|
if (innerCls.contains(AFlag.DONT_GENERATE)
|
||||||
|
|| innerCls.contains(AFlag.ANONYMOUS_CLASS)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ClassGen inClGen = new ClassGen(innerCls, getParentGen());
|
||||||
|
code.newLine();
|
||||||
|
inClGen.addClassCode(code);
|
||||||
|
imports.addAll(inClGen.getImports());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isInnerClassesPresents() {
|
||||||
|
for (ClassNode innerCls : cls.getInnerClasses()) {
|
||||||
|
if (!innerCls.contains(AFlag.ANONYMOUS_CLASS)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addMethods(CodeWriter code) {
|
||||||
|
List<MethodNode> methods = sortMethodsByLine(cls.getMethods());
|
||||||
|
for (MethodNode mth : methods) {
|
||||||
|
if (mth.contains(AFlag.DONT_GENERATE)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (code.getLine() != clsDeclLine) {
|
||||||
|
code.newLine();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
addMethod(code, mth);
|
||||||
|
} catch (Exception e) {
|
||||||
|
String msg = ErrorsCounter.methodError(mth, "Method generation error", e);
|
||||||
|
code.startLine("/* " + msg + CodeWriter.NL + Utils.getStackTrace(e) + " */");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<MethodNode> sortMethodsByLine(List<MethodNode> methods) {
|
||||||
|
List<MethodNode> out = new ArrayList<MethodNode>(methods);
|
||||||
|
Collections.sort(out, METHOD_LINE_COMPARATOR);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isMethodsPresents() {
|
||||||
|
for (MethodNode mth : cls.getMethods()) {
|
||||||
|
if (!mth.contains(AFlag.DONT_GENERATE)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addMethod(CodeWriter code, MethodNode mth) throws CodegenException {
|
||||||
|
if (mth.getAccessFlags().isAbstract() || mth.getAccessFlags().isNative()) {
|
||||||
|
MethodGen mthGen = new MethodGen(this, mth);
|
||||||
|
mthGen.addDefinition(code);
|
||||||
|
if (cls.getAccessFlags().isAnnotation()) {
|
||||||
|
Object def = annotationGen.getAnnotationDefaultValue(mth.getName());
|
||||||
|
if (def != null) {
|
||||||
|
code.add(" default ");
|
||||||
|
annotationGen.encodeValue(code, def);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
code.add(';');
|
||||||
|
} else {
|
||||||
|
boolean badCode = mth.contains(AFlag.INCONSISTENT_CODE);
|
||||||
|
if (badCode) {
|
||||||
|
code.startLine("/* JADX WARNING: inconsistent code. */");
|
||||||
|
code.startLine("/* Code decompiled incorrectly, please refer to instructions dump. */");
|
||||||
|
ErrorsCounter.methodError(mth, "Inconsistent code");
|
||||||
|
if (showInconsistentCode) {
|
||||||
|
mth.remove(AFlag.INCONSISTENT_CODE);
|
||||||
|
badCode = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MethodGen mthGen;
|
||||||
|
if (badCode || mth.contains(AType.JADX_ERROR) || fallback) {
|
||||||
|
mthGen = MethodGen.getFallbackMethodGen(mth);
|
||||||
|
} else {
|
||||||
|
mthGen = new MethodGen(this, mth);
|
||||||
|
}
|
||||||
|
if (mthGen.addDefinition(code)) {
|
||||||
|
code.add(' ');
|
||||||
|
}
|
||||||
|
code.add('{');
|
||||||
|
code.incIndent();
|
||||||
|
insertSourceFileInfo(code, mth);
|
||||||
|
if (fallback) {
|
||||||
|
mthGen.addFallbackMethodCode(code);
|
||||||
|
} else {
|
||||||
|
mthGen.addInstructions(code);
|
||||||
|
}
|
||||||
|
code.decIndent();
|
||||||
|
code.startLine('}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addFields(CodeWriter code) throws CodegenException {
|
||||||
|
addEnumFields(code);
|
||||||
|
for (FieldNode f : cls.getFields()) {
|
||||||
|
if (f.contains(AFlag.DONT_GENERATE)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
annotationGen.addForField(code, f);
|
||||||
|
code.startLine(f.getAccessFlags().makeString());
|
||||||
|
useType(code, f.getType());
|
||||||
|
code.add(' ');
|
||||||
|
code.add(f.getAlias());
|
||||||
|
FieldValueAttr fv = f.get(AType.FIELD_VALUE);
|
||||||
|
if (fv != null) {
|
||||||
|
code.add(" = ");
|
||||||
|
if (fv.getValue() == null) {
|
||||||
|
code.add(TypeGen.literalToString(0, f.getType()));
|
||||||
|
} else {
|
||||||
|
annotationGen.encodeValue(code, fv.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
code.add(';');
|
||||||
|
code.attachDefinition(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isFieldsPresents() {
|
||||||
|
for (FieldNode field : cls.getFields()) {
|
||||||
|
if (!field.contains(AFlag.DONT_GENERATE)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addEnumFields(CodeWriter code) throws CodegenException {
|
||||||
|
EnumClassAttr enumFields = cls.get(AType.ENUM_CLASS);
|
||||||
|
if (enumFields == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
InsnGen igen = null;
|
||||||
|
for (Iterator<EnumField> it = enumFields.getFields().iterator(); it.hasNext(); ) {
|
||||||
|
EnumField f = it.next();
|
||||||
|
code.startLine(f.getField().getAlias());
|
||||||
|
ConstructorInsn constrInsn = f.getConstrInsn();
|
||||||
|
if (constrInsn.getArgsCount() > f.getStartArg()) {
|
||||||
|
if (igen == null) {
|
||||||
|
MethodGen mthGen = new MethodGen(this, enumFields.getStaticMethod());
|
||||||
|
igen = new InsnGen(mthGen, false);
|
||||||
|
}
|
||||||
|
MethodNode callMth = cls.dex().resolveMethod(constrInsn.getCallMth());
|
||||||
|
igen.generateMethodArguments(code, constrInsn, f.getStartArg(), callMth);
|
||||||
|
}
|
||||||
|
if (f.getCls() != null) {
|
||||||
|
code.add(' ');
|
||||||
|
new ClassGen(f.getCls(), this).addClassBody(code);
|
||||||
|
}
|
||||||
|
if (it.hasNext()) {
|
||||||
|
code.add(',');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isMethodsPresents() || isFieldsPresents() || isInnerClassesPresents()) {
|
||||||
|
if (enumFields.getFields().isEmpty()) {
|
||||||
|
code.startLine();
|
||||||
|
}
|
||||||
|
code.add(';');
|
||||||
|
if (isFieldsPresents()) {
|
||||||
|
code.startLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void useType(CodeWriter code, ArgType type) {
|
||||||
|
PrimitiveType stype = type.getPrimitiveType();
|
||||||
|
if (stype == null) {
|
||||||
|
code.add(type.toString());
|
||||||
|
} else if (stype == PrimitiveType.OBJECT) {
|
||||||
|
if (type.isGenericType()) {
|
||||||
|
code.add(type.getObject());
|
||||||
|
} else {
|
||||||
|
useClass(code, type);
|
||||||
|
}
|
||||||
|
} else if (stype == PrimitiveType.ARRAY) {
|
||||||
|
useType(code, type.getArrayElement());
|
||||||
|
code.add("[]");
|
||||||
|
} else {
|
||||||
|
code.add(stype.getLongName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void useClass(CodeWriter code, ArgType type) {
|
||||||
|
useClass(code, ClassInfo.extCls(cls.dex(), type));
|
||||||
|
ArgType[] generics = type.getGenericTypes();
|
||||||
|
if (generics != null) {
|
||||||
|
code.add('<');
|
||||||
|
int len = generics.length;
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
if (i != 0) {
|
||||||
|
code.add(", ");
|
||||||
|
}
|
||||||
|
ArgType gt = generics[i];
|
||||||
|
ArgType wt = gt.getWildcardType();
|
||||||
|
if (wt != null) {
|
||||||
|
code.add('?');
|
||||||
|
int bounds = gt.getWildcardBounds();
|
||||||
|
if (bounds != 0) {
|
||||||
|
code.add(bounds == -1 ? " super " : " extends ");
|
||||||
|
useType(code, wt);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
useType(code, gt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
code.add('>');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void useClass(CodeWriter code, ClassInfo classInfo) {
|
||||||
|
ClassNode classNode = cls.dex().resolveClass(classInfo);
|
||||||
|
if (classNode != null) {
|
||||||
|
code.attachAnnotation(classNode);
|
||||||
|
}
|
||||||
|
String baseClass = useClassInternal(cls.getAlias(), classInfo.getAlias());
|
||||||
|
code.add(baseClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String useClassInternal(ClassInfo useCls, ClassInfo extClsInfo) {
|
||||||
|
String fullName = extClsInfo.getFullName();
|
||||||
|
if (fallback) {
|
||||||
|
return fullName;
|
||||||
|
}
|
||||||
|
String shortName = extClsInfo.getShortName();
|
||||||
|
if (extClsInfo.getPackage().equals("java.lang") && extClsInfo.getParentClass() == null) {
|
||||||
|
return shortName;
|
||||||
|
}
|
||||||
|
if (isClassInnerFor(useCls, extClsInfo)) {
|
||||||
|
return shortName;
|
||||||
|
}
|
||||||
|
if (isBothClassesInOneTopClass(useCls, extClsInfo)) {
|
||||||
|
return shortName;
|
||||||
|
}
|
||||||
|
// don't add import if this class from same package
|
||||||
|
if (extClsInfo.getPackage().equals(useCls.getPackage()) && !extClsInfo.isInner()) {
|
||||||
|
return shortName;
|
||||||
|
}
|
||||||
|
// don't add import if class not public (must be accessed using inheritance)
|
||||||
|
ClassNode classNode = cls.dex().resolveClass(extClsInfo);
|
||||||
|
if (classNode != null && !classNode.getAccessFlags().isPublic()) {
|
||||||
|
return shortName;
|
||||||
|
}
|
||||||
|
if (searchCollision(cls.dex(), useCls, extClsInfo)) {
|
||||||
|
return fullName;
|
||||||
|
}
|
||||||
|
if (extClsInfo.getPackage().equals(useCls.getPackage())) {
|
||||||
|
fullName = extClsInfo.getNameWithoutPackage();
|
||||||
|
}
|
||||||
|
for (ClassInfo importCls : getImports()) {
|
||||||
|
if (!importCls.equals(extClsInfo)
|
||||||
|
&& importCls.getShortName().equals(shortName)) {
|
||||||
|
if (extClsInfo.isInner()) {
|
||||||
|
String parent = useClassInternal(useCls, extClsInfo.getParentClass().getAlias());
|
||||||
|
return parent + "." + shortName;
|
||||||
|
} else {
|
||||||
|
return fullName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addImport(extClsInfo);
|
||||||
|
return shortName;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addImport(ClassInfo classInfo) {
|
||||||
|
if (parentGen != null) {
|
||||||
|
parentGen.addImport(classInfo.getAlias());
|
||||||
|
} else {
|
||||||
|
imports.add(classInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<ClassInfo> getImports() {
|
||||||
|
if (parentGen != null) {
|
||||||
|
return parentGen.getImports();
|
||||||
|
} else {
|
||||||
|
return imports;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isBothClassesInOneTopClass(ClassInfo useCls, ClassInfo extClsInfo) {
|
||||||
|
ClassInfo a = useCls.getTopParentClass();
|
||||||
|
ClassInfo b = extClsInfo.getTopParentClass();
|
||||||
|
if (a != null) {
|
||||||
|
return a.equals(b);
|
||||||
|
}
|
||||||
|
// useCls - is a top class
|
||||||
|
return useCls.equals(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isClassInnerFor(ClassInfo inner, ClassInfo parent) {
|
||||||
|
if (inner.isInner()) {
|
||||||
|
ClassInfo p = inner.getParentClass();
|
||||||
|
return p.equals(parent) || isClassInnerFor(p, parent);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean searchCollision(DexNode dex, ClassInfo useCls, ClassInfo searchCls) {
|
||||||
|
if (useCls == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String shortName = searchCls.getShortName();
|
||||||
|
if (useCls.getShortName().equals(shortName)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
ClassNode classNode = dex.resolveClass(useCls);
|
||||||
|
if (classNode != null) {
|
||||||
|
for (ClassNode inner : classNode.getInnerClasses()) {
|
||||||
|
if (inner.getShortName().equals(shortName)
|
||||||
|
&& !inner.getAlias().equals(searchCls)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return searchCollision(dex, useCls.getParentClass(), searchCls);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void insertSourceFileInfo(CodeWriter code, AttrNode node) {
|
||||||
|
SourceFileAttr sourceFileAttr = node.get(AType.SOURCE_FILE);
|
||||||
|
if (sourceFileAttr != null) {
|
||||||
|
code.startLine("/* compiled from: ").add(sourceFileAttr.getFileName()).add(" */");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void insertRenameInfo(CodeWriter code, ClassNode cls) {
|
||||||
|
ClassInfo classInfo = cls.getClassInfo();
|
||||||
|
if (classInfo.isRenamed()) {
|
||||||
|
code.startLine("/* renamed from: ").add(classInfo.getFullName()).add(" */");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClassGen getParentGen() {
|
||||||
|
return parentGen == null ? this : parentGen;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AnnotationGen getAnnotationGen() {
|
||||||
|
return annotationGen;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFallbackMode() {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package jadx.core.codegen;
|
||||||
|
|
||||||
|
import jadx.api.IJadxArgs;
|
||||||
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
|
import jadx.core.dex.visitors.AbstractVisitor;
|
||||||
|
import jadx.core.utils.exceptions.CodegenException;
|
||||||
|
|
||||||
|
public class CodeGen extends AbstractVisitor {
|
||||||
|
|
||||||
|
private final IJadxArgs args;
|
||||||
|
|
||||||
|
public CodeGen(IJadxArgs args) {
|
||||||
|
this.args = args;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean visit(ClassNode cls) throws CodegenException {
|
||||||
|
ClassGen clsGen = new ClassGen(cls, args);
|
||||||
|
CodeWriter clsCode = clsGen.makeClass();
|
||||||
|
clsCode.finish();
|
||||||
|
cls.setCode(clsCode);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,325 @@
|
|||||||
|
package jadx.core.codegen;
|
||||||
|
|
||||||
|
import jadx.api.CodePosition;
|
||||||
|
import jadx.core.dex.attributes.nodes.LineAttrNode;
|
||||||
|
import jadx.core.utils.files.FileUtils;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class CodeWriter {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(CodeWriter.class);
|
||||||
|
private static final int MAX_FILENAME_LENGTH = 128;
|
||||||
|
|
||||||
|
public static final String NL = System.getProperty("line.separator");
|
||||||
|
public static final String INDENT = " ";
|
||||||
|
|
||||||
|
private static final boolean ADD_LINE_NUMBERS = false;
|
||||||
|
|
||||||
|
private static final String[] INDENT_CACHE = {
|
||||||
|
"",
|
||||||
|
INDENT,
|
||||||
|
INDENT + INDENT,
|
||||||
|
INDENT + INDENT + INDENT,
|
||||||
|
INDENT + INDENT + INDENT + INDENT,
|
||||||
|
INDENT + INDENT + INDENT + INDENT + INDENT,
|
||||||
|
};
|
||||||
|
|
||||||
|
private final StringBuilder buf = new StringBuilder();
|
||||||
|
private String indentStr;
|
||||||
|
private int indent;
|
||||||
|
|
||||||
|
private int line = 1;
|
||||||
|
private int offset = 0;
|
||||||
|
private Map<CodePosition, Object> annotations = Collections.emptyMap();
|
||||||
|
private Map<Integer, Integer> lineMap = Collections.emptyMap();
|
||||||
|
|
||||||
|
public CodeWriter() {
|
||||||
|
this.indent = 0;
|
||||||
|
this.indentStr = "";
|
||||||
|
if (ADD_LINE_NUMBERS) {
|
||||||
|
incIndent(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public CodeWriter startLine() {
|
||||||
|
addLine();
|
||||||
|
addLineIndent();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CodeWriter startLine(char c) {
|
||||||
|
addLine();
|
||||||
|
addLineIndent();
|
||||||
|
add(c);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CodeWriter startLine(String str) {
|
||||||
|
addLine();
|
||||||
|
addLineIndent();
|
||||||
|
add(str);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CodeWriter startLineWithNum(int sourceLine) {
|
||||||
|
if (sourceLine == 0) {
|
||||||
|
startLine();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
if (ADD_LINE_NUMBERS) {
|
||||||
|
newLine();
|
||||||
|
attachSourceLine(sourceLine);
|
||||||
|
String ln = "/* " + sourceLine + " */ ";
|
||||||
|
add(ln);
|
||||||
|
if (indentStr.length() > ln.length()) {
|
||||||
|
add(indentStr.substring(ln.length()));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
startLine();
|
||||||
|
attachSourceLine(sourceLine);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CodeWriter add(String str) {
|
||||||
|
buf.append(str);
|
||||||
|
offset += str.length();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CodeWriter add(char c) {
|
||||||
|
buf.append(c);
|
||||||
|
offset++;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
CodeWriter add(CodeWriter code) {
|
||||||
|
line--;
|
||||||
|
for (Map.Entry<CodePosition, Object> entry : code.annotations.entrySet()) {
|
||||||
|
CodePosition pos = entry.getKey();
|
||||||
|
attachAnnotation(entry.getValue(), new CodePosition(line + pos.getLine(), pos.getOffset()));
|
||||||
|
}
|
||||||
|
for (Map.Entry<Integer, Integer> entry : code.lineMap.entrySet()) {
|
||||||
|
attachSourceLine(line + entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
line += code.line;
|
||||||
|
offset = code.offset;
|
||||||
|
buf.append(code);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CodeWriter newLine() {
|
||||||
|
addLine();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CodeWriter addIndent() {
|
||||||
|
add(INDENT);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addLine() {
|
||||||
|
buf.append(NL);
|
||||||
|
line++;
|
||||||
|
offset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private CodeWriter addLineIndent() {
|
||||||
|
buf.append(indentStr);
|
||||||
|
offset += indentStr.length();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateIndent() {
|
||||||
|
int curIndent = indent;
|
||||||
|
if (curIndent < INDENT_CACHE.length) {
|
||||||
|
this.indentStr = INDENT_CACHE[curIndent];
|
||||||
|
} else {
|
||||||
|
StringBuilder s = new StringBuilder(curIndent * INDENT.length());
|
||||||
|
for (int i = 0; i < curIndent; i++) {
|
||||||
|
s.append(INDENT);
|
||||||
|
}
|
||||||
|
this.indentStr = s.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void incIndent() {
|
||||||
|
incIndent(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void decIndent() {
|
||||||
|
decIndent(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void incIndent(int c) {
|
||||||
|
this.indent += c;
|
||||||
|
updateIndent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void decIndent(int c) {
|
||||||
|
this.indent -= c;
|
||||||
|
if (this.indent < 0) {
|
||||||
|
LOG.warn("Indent < 0");
|
||||||
|
this.indent = 0;
|
||||||
|
}
|
||||||
|
updateIndent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getIndent() {
|
||||||
|
return indent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLine() {
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class DefinitionWrapper {
|
||||||
|
private final LineAttrNode node;
|
||||||
|
|
||||||
|
private DefinitionWrapper(LineAttrNode node) {
|
||||||
|
this.node = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LineAttrNode getNode() {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object attachDefinition(LineAttrNode obj) {
|
||||||
|
return attachAnnotation(new DefinitionWrapper(obj), new CodePosition(line, offset));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object attachAnnotation(Object obj) {
|
||||||
|
return attachAnnotation(obj, new CodePosition(line, offset + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object attachAnnotation(Object obj, CodePosition pos) {
|
||||||
|
if (annotations.isEmpty()) {
|
||||||
|
annotations = new HashMap<CodePosition, Object>();
|
||||||
|
}
|
||||||
|
return annotations.put(pos, obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<CodePosition, Object> getAnnotations() {
|
||||||
|
return annotations;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void attachSourceLine(int sourceLine) {
|
||||||
|
if (sourceLine == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
attachSourceLine(line, sourceLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void attachSourceLine(int decompiledLine, int sourceLine) {
|
||||||
|
if (lineMap.isEmpty()) {
|
||||||
|
lineMap = new TreeMap<Integer, Integer>();
|
||||||
|
}
|
||||||
|
lineMap.put(decompiledLine, sourceLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<Integer, Integer> getLineMapping() {
|
||||||
|
return lineMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void finish() {
|
||||||
|
buf.trimToSize();
|
||||||
|
Iterator<Map.Entry<CodePosition, Object>> it = annotations.entrySet().iterator();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
Map.Entry<CodePosition, Object> entry = it.next();
|
||||||
|
Object v = entry.getValue();
|
||||||
|
if (v instanceof DefinitionWrapper) {
|
||||||
|
LineAttrNode l = ((DefinitionWrapper) v).getNode();
|
||||||
|
l.setDecompiledLine(entry.getKey().getLine());
|
||||||
|
it.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String removeFirstEmptyLine(String str) {
|
||||||
|
if (str.startsWith(NL)) {
|
||||||
|
return str.substring(NL.length());
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int length() {
|
||||||
|
return buf.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return buf.length() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean notEmpty() {
|
||||||
|
return buf.length() != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return buf.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void save(File dir, String subDir, String fileName) {
|
||||||
|
save(dir, new File(subDir, fileName).getPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void save(File dir, String fileName) {
|
||||||
|
save(new File(dir, fileName));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void save(File file) {
|
||||||
|
String name = file.getName();
|
||||||
|
if (name.length() > MAX_FILENAME_LENGTH) {
|
||||||
|
int dotIndex = name.indexOf('.');
|
||||||
|
int cutAt = MAX_FILENAME_LENGTH - name.length() + dotIndex - 1;
|
||||||
|
if (cutAt <= 0) {
|
||||||
|
name = name.substring(0, MAX_FILENAME_LENGTH - 1);
|
||||||
|
} else {
|
||||||
|
name = name.substring(0, cutAt) + name.substring(dotIndex);
|
||||||
|
}
|
||||||
|
file = new File(file.getParentFile(), name);
|
||||||
|
}
|
||||||
|
|
||||||
|
PrintWriter out = null;
|
||||||
|
try {
|
||||||
|
FileUtils.makeDirsForFile(file);
|
||||||
|
out = new PrintWriter(file, "UTF-8");
|
||||||
|
String code = buf.toString();
|
||||||
|
code = removeFirstEmptyLine(code);
|
||||||
|
out.println(code);
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.error("Save file error", e);
|
||||||
|
} finally {
|
||||||
|
if (out != null) {
|
||||||
|
out.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return buf.toString().hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(o instanceof CodeWriter)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
CodeWriter that = (CodeWriter) o;
|
||||||
|
return buf.toString().equals(that.buf.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,198 @@
|
|||||||
|
package jadx.core.codegen;
|
||||||
|
|
||||||
|
import jadx.core.dex.instructions.ArithNode;
|
||||||
|
import jadx.core.dex.instructions.IfOp;
|
||||||
|
import jadx.core.dex.instructions.InsnType;
|
||||||
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
|
import jadx.core.dex.instructions.args.InsnArg;
|
||||||
|
import jadx.core.dex.instructions.args.InsnWrapArg;
|
||||||
|
import jadx.core.dex.instructions.args.LiteralArg;
|
||||||
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
|
import jadx.core.dex.regions.conditions.Compare;
|
||||||
|
import jadx.core.dex.regions.conditions.IfCondition;
|
||||||
|
import jadx.core.dex.regions.conditions.IfCondition.Mode;
|
||||||
|
import jadx.core.utils.ErrorsCounter;
|
||||||
|
import jadx.core.utils.exceptions.CodegenException;
|
||||||
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.Queue;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class ConditionGen extends InsnGen {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(ConditionGen.class);
|
||||||
|
|
||||||
|
private static class CondStack {
|
||||||
|
private final Queue<IfCondition> stack = new LinkedList<IfCondition>();
|
||||||
|
|
||||||
|
public Queue<IfCondition> getStack() {
|
||||||
|
return stack;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void push(IfCondition cond) {
|
||||||
|
stack.add(cond);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IfCondition pop() {
|
||||||
|
return stack.poll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConditionGen(InsnGen insnGen) {
|
||||||
|
super(insnGen.mgen, insnGen.fallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
void add(CodeWriter code, IfCondition condition) throws CodegenException {
|
||||||
|
add(code, new CondStack(), condition);
|
||||||
|
}
|
||||||
|
|
||||||
|
void wrap(CodeWriter code, IfCondition condition) throws CodegenException {
|
||||||
|
wrap(code, new CondStack(), condition);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void add(CodeWriter code, CondStack stack, IfCondition condition) throws CodegenException {
|
||||||
|
stack.push(condition);
|
||||||
|
switch (condition.getMode()) {
|
||||||
|
case COMPARE:
|
||||||
|
addCompare(code, stack, condition.getCompare());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TERNARY:
|
||||||
|
addTernary(code, stack, condition);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NOT:
|
||||||
|
addNot(code, stack, condition);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AND:
|
||||||
|
case OR:
|
||||||
|
addAndOr(code, stack, condition);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new JadxRuntimeException("Unknown condition mode: " + condition.getMode());
|
||||||
|
}
|
||||||
|
stack.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void wrap(CodeWriter code, CondStack stack, IfCondition cond) throws CodegenException {
|
||||||
|
boolean wrap = isWrapNeeded(cond);
|
||||||
|
if (wrap) {
|
||||||
|
code.add('(');
|
||||||
|
}
|
||||||
|
add(code, stack, cond);
|
||||||
|
if (wrap) {
|
||||||
|
code.add(')');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void wrap(CodeWriter code, InsnArg firstArg) throws CodegenException {
|
||||||
|
boolean wrap = isArgWrapNeeded(firstArg);
|
||||||
|
if (wrap) {
|
||||||
|
code.add('(');
|
||||||
|
}
|
||||||
|
addArg(code, firstArg, false);
|
||||||
|
if (wrap) {
|
||||||
|
code.add(')');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addCompare(CodeWriter code, CondStack stack, Compare compare) throws CodegenException {
|
||||||
|
IfOp op = compare.getOp();
|
||||||
|
InsnArg firstArg = compare.getA();
|
||||||
|
InsnArg secondArg = compare.getB();
|
||||||
|
if (firstArg.getType().equals(ArgType.BOOLEAN)
|
||||||
|
&& secondArg.isLiteral()
|
||||||
|
&& secondArg.getType().equals(ArgType.BOOLEAN)) {
|
||||||
|
LiteralArg lit = (LiteralArg) secondArg;
|
||||||
|
if (lit.getLiteral() == 0) {
|
||||||
|
op = op.invert();
|
||||||
|
}
|
||||||
|
if (op == IfOp.EQ) {
|
||||||
|
// == true
|
||||||
|
if (stack.getStack().size() == 1) {
|
||||||
|
addArg(code, firstArg, false);
|
||||||
|
} else {
|
||||||
|
wrap(code, firstArg);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} else if (op == IfOp.NE) {
|
||||||
|
// != true
|
||||||
|
code.add('!');
|
||||||
|
wrap(code, firstArg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LOG.warn(ErrorsCounter.formatErrorMsg(mth, "Unsupported boolean condition " + op.getSymbol()));
|
||||||
|
}
|
||||||
|
|
||||||
|
addArg(code, firstArg, isArgWrapNeeded(firstArg));
|
||||||
|
code.add(' ').add(op.getSymbol()).add(' ');
|
||||||
|
addArg(code, secondArg, isArgWrapNeeded(secondArg));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addTernary(CodeWriter code, CondStack stack, IfCondition condition) throws CodegenException {
|
||||||
|
add(code, stack, condition.first());
|
||||||
|
code.add(" ? ");
|
||||||
|
add(code, stack, condition.second());
|
||||||
|
code.add(" : ");
|
||||||
|
add(code, stack, condition.third());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addNot(CodeWriter code, CondStack stack, IfCondition condition) throws CodegenException {
|
||||||
|
code.add('!');
|
||||||
|
wrap(code, stack, condition.getArgs().get(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addAndOr(CodeWriter code, CondStack stack, IfCondition condition) throws CodegenException {
|
||||||
|
String mode = condition.getMode() == Mode.AND ? " && " : " || ";
|
||||||
|
Iterator<IfCondition> it = condition.getArgs().iterator();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
wrap(code, stack, it.next());
|
||||||
|
if (it.hasNext()) {
|
||||||
|
code.add(mode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isWrapNeeded(IfCondition condition) {
|
||||||
|
if (condition.isCompare()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return condition.getMode() != Mode.NOT;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isArgWrapNeeded(InsnArg arg) {
|
||||||
|
if (!arg.isInsnWrap()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
InsnNode insn = ((InsnWrapArg) arg).getWrapInsn();
|
||||||
|
InsnType insnType = insn.getType();
|
||||||
|
if (insnType == InsnType.ARITH) {
|
||||||
|
switch (((ArithNode) insn).getOp()) {
|
||||||
|
case ADD:
|
||||||
|
case SUB:
|
||||||
|
case MUL:
|
||||||
|
case DIV:
|
||||||
|
case REM:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch (insnType) {
|
||||||
|
case INVOKE:
|
||||||
|
case SGET:
|
||||||
|
case IGET:
|
||||||
|
case AGET:
|
||||||
|
case CONST:
|
||||||
|
case ARRAY_LENGTH:
|
||||||
|
return false;
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,790 @@
|
|||||||
|
package jadx.core.codegen;
|
||||||
|
|
||||||
|
import jadx.core.dex.attributes.AFlag;
|
||||||
|
import jadx.core.dex.attributes.AType;
|
||||||
|
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
|
||||||
|
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
||||||
|
import jadx.core.dex.attributes.nodes.MethodInlineAttr;
|
||||||
|
import jadx.core.dex.info.ClassInfo;
|
||||||
|
import jadx.core.dex.info.FieldInfo;
|
||||||
|
import jadx.core.dex.info.MethodInfo;
|
||||||
|
import jadx.core.dex.instructions.ArithNode;
|
||||||
|
import jadx.core.dex.instructions.ArithOp;
|
||||||
|
import jadx.core.dex.instructions.ConstClassNode;
|
||||||
|
import jadx.core.dex.instructions.ConstStringNode;
|
||||||
|
import jadx.core.dex.instructions.FillArrayNode;
|
||||||
|
import jadx.core.dex.instructions.FilledNewArrayNode;
|
||||||
|
import jadx.core.dex.instructions.GotoNode;
|
||||||
|
import jadx.core.dex.instructions.IfNode;
|
||||||
|
import jadx.core.dex.instructions.IndexInsnNode;
|
||||||
|
import jadx.core.dex.instructions.InsnType;
|
||||||
|
import jadx.core.dex.instructions.InvokeNode;
|
||||||
|
import jadx.core.dex.instructions.InvokeType;
|
||||||
|
import jadx.core.dex.instructions.NewArrayNode;
|
||||||
|
import jadx.core.dex.instructions.SwitchNode;
|
||||||
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
|
import jadx.core.dex.instructions.args.FieldArg;
|
||||||
|
import jadx.core.dex.instructions.args.InsnArg;
|
||||||
|
import jadx.core.dex.instructions.args.InsnWrapArg;
|
||||||
|
import jadx.core.dex.instructions.args.LiteralArg;
|
||||||
|
import jadx.core.dex.instructions.args.Named;
|
||||||
|
import jadx.core.dex.instructions.args.RegisterArg;
|
||||||
|
import jadx.core.dex.instructions.mods.ConstructorInsn;
|
||||||
|
import jadx.core.dex.instructions.mods.TernaryInsn;
|
||||||
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
|
import jadx.core.dex.nodes.FieldNode;
|
||||||
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
|
import jadx.core.dex.nodes.RootNode;
|
||||||
|
import jadx.core.utils.RegionUtils;
|
||||||
|
import jadx.core.utils.StringUtils;
|
||||||
|
import jadx.core.utils.exceptions.CodegenException;
|
||||||
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class InsnGen {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(InsnGen.class);
|
||||||
|
|
||||||
|
protected final MethodGen mgen;
|
||||||
|
protected final MethodNode mth;
|
||||||
|
protected final RootNode root;
|
||||||
|
protected final boolean fallback;
|
||||||
|
|
||||||
|
protected enum Flags {
|
||||||
|
BODY_ONLY,
|
||||||
|
BODY_ONLY_NOWRAP,
|
||||||
|
INLINE
|
||||||
|
}
|
||||||
|
|
||||||
|
public InsnGen(MethodGen mgen, boolean fallback) {
|
||||||
|
this.mgen = mgen;
|
||||||
|
this.mth = mgen.getMethodNode();
|
||||||
|
this.root = mth.dex().root();
|
||||||
|
this.fallback = fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isFallback() {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addArgDot(CodeWriter code, InsnArg arg) throws CodegenException {
|
||||||
|
int len = code.length();
|
||||||
|
addArg(code, arg, true);
|
||||||
|
if (len != code.length()) {
|
||||||
|
code.add('.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addArg(CodeWriter code, InsnArg arg) throws CodegenException {
|
||||||
|
addArg(code, arg, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addArg(CodeWriter code, InsnArg arg, boolean wrap) throws CodegenException {
|
||||||
|
if (arg.isRegister()) {
|
||||||
|
code.add(mgen.getNameGen().useArg((RegisterArg) arg));
|
||||||
|
} else if (arg.isLiteral()) {
|
||||||
|
code.add(lit((LiteralArg) arg));
|
||||||
|
} else if (arg.isInsnWrap()) {
|
||||||
|
Flags flag = wrap ? Flags.BODY_ONLY : Flags.BODY_ONLY_NOWRAP;
|
||||||
|
makeInsn(((InsnWrapArg) arg).getWrapInsn(), code, flag);
|
||||||
|
} else if (arg.isNamed()) {
|
||||||
|
code.add(((Named) arg).getName());
|
||||||
|
} else if (arg.isField()) {
|
||||||
|
FieldArg f = (FieldArg) arg;
|
||||||
|
if (f.isStatic()) {
|
||||||
|
staticField(code, f.getField());
|
||||||
|
} else {
|
||||||
|
instanceField(code, f.getField(), f.getInstanceArg());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new CodegenException("Unknown arg type " + arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void assignVar(CodeWriter code, InsnNode insn) throws CodegenException {
|
||||||
|
RegisterArg arg = insn.getResult();
|
||||||
|
if (insn.contains(AFlag.DECLARE_VAR)) {
|
||||||
|
declareVar(code, arg);
|
||||||
|
} else {
|
||||||
|
addArg(code, arg, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void declareVar(CodeWriter code, RegisterArg arg) {
|
||||||
|
useType(code, arg.getType());
|
||||||
|
code.add(' ');
|
||||||
|
code.add(mgen.getNameGen().assignArg(arg));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String lit(LiteralArg arg) {
|
||||||
|
return TypeGen.literalToString(arg.getLiteral(), arg.getType());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void instanceField(CodeWriter code, FieldInfo field, InsnArg arg) throws CodegenException {
|
||||||
|
ClassNode pCls = mth.getParentClass();
|
||||||
|
FieldNode fieldNode = pCls.searchField(field);
|
||||||
|
while (fieldNode == null
|
||||||
|
&& pCls.getParentClass() != pCls
|
||||||
|
&& pCls.getParentClass() != null) {
|
||||||
|
pCls = pCls.getParentClass();
|
||||||
|
fieldNode = pCls.searchField(field);
|
||||||
|
}
|
||||||
|
if (fieldNode != null) {
|
||||||
|
FieldReplaceAttr replace = fieldNode.get(AType.FIELD_REPLACE);
|
||||||
|
if (replace != null) {
|
||||||
|
FieldInfo info = replace.getFieldInfo();
|
||||||
|
if (replace.isOuterClass()) {
|
||||||
|
useClass(code, info.getDeclClass());
|
||||||
|
code.add(".this");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addArgDot(code, arg);
|
||||||
|
fieldNode = mth.dex().resolveField(field);
|
||||||
|
if (fieldNode != null) {
|
||||||
|
code.attachAnnotation(fieldNode);
|
||||||
|
}
|
||||||
|
code.add(field.getAlias());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void makeStaticFieldAccess(CodeWriter code, FieldInfo field, ClassGen clsGen) {
|
||||||
|
ClassInfo declClass = field.getDeclClass();
|
||||||
|
boolean fieldFromThisClass = clsGen.getClassNode().getClassInfo().equals(declClass);
|
||||||
|
if (!fieldFromThisClass) {
|
||||||
|
// Android specific resources class handler
|
||||||
|
ClassInfo parentClass = declClass.getParentClass();
|
||||||
|
if (parentClass != null && parentClass.getShortName().equals("R")) {
|
||||||
|
clsGen.useClass(code, parentClass);
|
||||||
|
code.add('.');
|
||||||
|
code.add(declClass.getAlias().getShortName());
|
||||||
|
} else {
|
||||||
|
clsGen.useClass(code, declClass);
|
||||||
|
}
|
||||||
|
code.add('.');
|
||||||
|
}
|
||||||
|
FieldNode fieldNode = clsGen.getClassNode().dex().resolveField(field);
|
||||||
|
if (fieldNode != null) {
|
||||||
|
code.attachAnnotation(fieldNode);
|
||||||
|
}
|
||||||
|
code.add(field.getAlias());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void staticField(CodeWriter code, FieldInfo field) {
|
||||||
|
makeStaticFieldAccess(code, field, mgen.getClassGen());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void useClass(CodeWriter code, ArgType type) {
|
||||||
|
mgen.getClassGen().useClass(code, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void useClass(CodeWriter code, ClassInfo cls) {
|
||||||
|
mgen.getClassGen().useClass(code, cls);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void useType(CodeWriter code, ArgType type) {
|
||||||
|
mgen.getClassGen().useType(code, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean makeInsn(InsnNode insn, CodeWriter code) throws CodegenException {
|
||||||
|
return makeInsn(insn, code, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean makeInsn(InsnNode insn, CodeWriter code, Flags flag) throws CodegenException {
|
||||||
|
try {
|
||||||
|
Set<Flags> state = EnumSet.noneOf(Flags.class);
|
||||||
|
if (flag == Flags.BODY_ONLY || flag == Flags.BODY_ONLY_NOWRAP) {
|
||||||
|
state.add(flag);
|
||||||
|
makeInsnBody(code, insn, state);
|
||||||
|
} else {
|
||||||
|
if (flag != Flags.INLINE) {
|
||||||
|
code.startLineWithNum(insn.getSourceLine());
|
||||||
|
}
|
||||||
|
if (insn.getResult() != null && !insn.contains(AFlag.ARITH_ONEARG)) {
|
||||||
|
assignVar(code, insn);
|
||||||
|
code.add(" = ");
|
||||||
|
}
|
||||||
|
makeInsnBody(code, insn, state);
|
||||||
|
if (flag != Flags.INLINE) {
|
||||||
|
code.add(';');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable th) {
|
||||||
|
throw new CodegenException(mth, "Error generate insn: " + insn, th);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void makeInsnBody(CodeWriter code, InsnNode insn, Set<Flags> state) throws CodegenException {
|
||||||
|
switch (insn.getType()) {
|
||||||
|
case CONST_STR:
|
||||||
|
String str = ((ConstStringNode) insn).getString();
|
||||||
|
code.add(StringUtils.unescapeString(str));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CONST_CLASS:
|
||||||
|
ArgType clsType = ((ConstClassNode) insn).getClsType();
|
||||||
|
useType(code, clsType);
|
||||||
|
code.add(".class");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CONST:
|
||||||
|
LiteralArg arg = (LiteralArg) insn.getArg(0);
|
||||||
|
code.add(lit(arg));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MOVE:
|
||||||
|
addArg(code, insn.getArg(0), false);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CHECK_CAST:
|
||||||
|
case CAST: {
|
||||||
|
boolean wrap = state.contains(Flags.BODY_ONLY);
|
||||||
|
if (wrap) {
|
||||||
|
code.add('(');
|
||||||
|
}
|
||||||
|
code.add('(');
|
||||||
|
useType(code, (ArgType) ((IndexInsnNode) insn).getIndex());
|
||||||
|
code.add(") ");
|
||||||
|
addArg(code, insn.getArg(0), true);
|
||||||
|
if (wrap) {
|
||||||
|
code.add(')');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case ARITH:
|
||||||
|
makeArith((ArithNode) insn, code, state);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NEG: {
|
||||||
|
boolean wrap = state.contains(Flags.BODY_ONLY);
|
||||||
|
if (wrap) {
|
||||||
|
code.add('(');
|
||||||
|
}
|
||||||
|
code.add('-');
|
||||||
|
addArg(code, insn.getArg(0));
|
||||||
|
if (wrap) {
|
||||||
|
code.add(')');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case RETURN:
|
||||||
|
if (insn.getArgsCount() != 0) {
|
||||||
|
code.add("return ");
|
||||||
|
addArg(code, insn.getArg(0), false);
|
||||||
|
} else {
|
||||||
|
code.add("return");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BREAK:
|
||||||
|
code.add("break");
|
||||||
|
LoopLabelAttr labelAttr = insn.get(AType.LOOP_LABEL);
|
||||||
|
if (labelAttr != null) {
|
||||||
|
code.add(' ').add(mgen.getNameGen().getLoopLabel(labelAttr));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CONTINUE:
|
||||||
|
code.add("continue");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case THROW:
|
||||||
|
code.add("throw ");
|
||||||
|
addArg(code, insn.getArg(0), true);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CMP_L:
|
||||||
|
case CMP_G:
|
||||||
|
code.add('(');
|
||||||
|
addArg(code, insn.getArg(0));
|
||||||
|
code.add(" > ");
|
||||||
|
addArg(code, insn.getArg(1));
|
||||||
|
code.add(" ? 1 : (");
|
||||||
|
addArg(code, insn.getArg(0));
|
||||||
|
code.add(" == ");
|
||||||
|
addArg(code, insn.getArg(1));
|
||||||
|
code.add(" ? 0 : -1))");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case INSTANCE_OF: {
|
||||||
|
boolean wrap = state.contains(Flags.BODY_ONLY);
|
||||||
|
if (wrap) {
|
||||||
|
code.add('(');
|
||||||
|
}
|
||||||
|
addArg(code, insn.getArg(0));
|
||||||
|
code.add(" instanceof ");
|
||||||
|
useType(code, (ArgType) ((IndexInsnNode) insn).getIndex());
|
||||||
|
if (wrap) {
|
||||||
|
code.add(')');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CONSTRUCTOR:
|
||||||
|
makeConstructor((ConstructorInsn) insn, code);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case INVOKE:
|
||||||
|
makeInvoke((InvokeNode) insn, code);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NEW_ARRAY: {
|
||||||
|
ArgType arrayType = ((NewArrayNode) insn).getArrayType();
|
||||||
|
code.add("new ");
|
||||||
|
useType(code, arrayType.getArrayRootElement());
|
||||||
|
code.add('[');
|
||||||
|
addArg(code, insn.getArg(0));
|
||||||
|
code.add(']');
|
||||||
|
int dim = arrayType.getArrayDimension();
|
||||||
|
for (int i = 0; i < dim - 1; i++) {
|
||||||
|
code.add("[]");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case ARRAY_LENGTH:
|
||||||
|
addArg(code, insn.getArg(0));
|
||||||
|
code.add(".length");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FILLED_NEW_ARRAY:
|
||||||
|
filledNewArray((FilledNewArrayNode) insn, code);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AGET:
|
||||||
|
addArg(code, insn.getArg(0));
|
||||||
|
code.add('[');
|
||||||
|
addArg(code, insn.getArg(1), false);
|
||||||
|
code.add(']');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case APUT:
|
||||||
|
addArg(code, insn.getArg(0));
|
||||||
|
code.add('[');
|
||||||
|
addArg(code, insn.getArg(1), false);
|
||||||
|
code.add("] = ");
|
||||||
|
addArg(code, insn.getArg(2), false);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case IGET: {
|
||||||
|
FieldInfo fieldInfo = (FieldInfo) ((IndexInsnNode) insn).getIndex();
|
||||||
|
instanceField(code, fieldInfo, insn.getArg(0));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case IPUT: {
|
||||||
|
FieldInfo fieldInfo = (FieldInfo) ((IndexInsnNode) insn).getIndex();
|
||||||
|
instanceField(code, fieldInfo, insn.getArg(1));
|
||||||
|
code.add(" = ");
|
||||||
|
addArg(code, insn.getArg(0), false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case SGET:
|
||||||
|
staticField(code, (FieldInfo) ((IndexInsnNode) insn).getIndex());
|
||||||
|
break;
|
||||||
|
case SPUT:
|
||||||
|
FieldInfo field = (FieldInfo) ((IndexInsnNode) insn).getIndex();
|
||||||
|
staticField(code, field);
|
||||||
|
code.add(" = ");
|
||||||
|
addArg(code, insn.getArg(0), false);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case STR_CONCAT:
|
||||||
|
boolean wrap = state.contains(Flags.BODY_ONLY);
|
||||||
|
if (wrap) {
|
||||||
|
code.add('(');
|
||||||
|
}
|
||||||
|
for (Iterator<InsnArg> it = insn.getArguments().iterator(); it.hasNext(); ) {
|
||||||
|
addArg(code, it.next());
|
||||||
|
if (it.hasNext()) {
|
||||||
|
code.add(" + ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (wrap) {
|
||||||
|
code.add(')');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MONITOR_ENTER:
|
||||||
|
if (isFallback()) {
|
||||||
|
code.add("monitor-enter(");
|
||||||
|
addArg(code, insn.getArg(0));
|
||||||
|
code.add(')');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MONITOR_EXIT:
|
||||||
|
if (isFallback()) {
|
||||||
|
code.add("monitor-exit(");
|
||||||
|
addArg(code, insn.getArg(0));
|
||||||
|
code.add(')');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TERNARY:
|
||||||
|
makeTernary((TernaryInsn) insn, code, state);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ONE_ARG:
|
||||||
|
addArg(code, insn.getArg(0));
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* fallback mode instructions */
|
||||||
|
case IF:
|
||||||
|
assert isFallback() : "if insn in not fallback mode";
|
||||||
|
IfNode ifInsn = (IfNode) insn;
|
||||||
|
code.add("if (");
|
||||||
|
addArg(code, insn.getArg(0));
|
||||||
|
code.add(' ');
|
||||||
|
code.add(ifInsn.getOp().getSymbol()).add(' ');
|
||||||
|
addArg(code, insn.getArg(1));
|
||||||
|
code.add(") goto ").add(MethodGen.getLabelName(ifInsn.getTarget()));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GOTO:
|
||||||
|
assert isFallback();
|
||||||
|
code.add("goto ").add(MethodGen.getLabelName(((GotoNode) insn).getTarget()));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MOVE_EXCEPTION:
|
||||||
|
assert isFallback();
|
||||||
|
code.add("move-exception");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SWITCH:
|
||||||
|
assert isFallback();
|
||||||
|
SwitchNode sw = (SwitchNode) insn;
|
||||||
|
code.add("switch(");
|
||||||
|
addArg(code, insn.getArg(0));
|
||||||
|
code.add(") {");
|
||||||
|
code.incIndent();
|
||||||
|
for (int i = 0; i < sw.getCasesCount(); i++) {
|
||||||
|
String key = sw.getKeys()[i].toString();
|
||||||
|
code.startLine("case ").add(key).add(": goto ");
|
||||||
|
code.add(MethodGen.getLabelName(sw.getTargets()[i])).add(';');
|
||||||
|
}
|
||||||
|
code.startLine("default: goto ");
|
||||||
|
code.add(MethodGen.getLabelName(sw.getDefaultCaseOffset())).add(';');
|
||||||
|
code.decIndent();
|
||||||
|
code.startLine('}');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FILL_ARRAY:
|
||||||
|
assert isFallback();
|
||||||
|
FillArrayNode arrayNode = (FillArrayNode) insn;
|
||||||
|
Object data = arrayNode.getData();
|
||||||
|
String arrStr;
|
||||||
|
if (data instanceof int[]) {
|
||||||
|
arrStr = Arrays.toString((int[]) data);
|
||||||
|
} else if (data instanceof short[]) {
|
||||||
|
arrStr = Arrays.toString((short[]) data);
|
||||||
|
} else if (data instanceof byte[]) {
|
||||||
|
arrStr = Arrays.toString((byte[]) data);
|
||||||
|
} else if (data instanceof long[]) {
|
||||||
|
arrStr = Arrays.toString((long[]) data);
|
||||||
|
} else {
|
||||||
|
arrStr = "?";
|
||||||
|
}
|
||||||
|
code.add('{').add(arrStr.substring(1, arrStr.length() - 1)).add('}');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NEW_INSTANCE:
|
||||||
|
// only fallback - make new instance in constructor invoke
|
||||||
|
assert isFallback();
|
||||||
|
code.add("new " + insn.getResult().getType());
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new CodegenException(mth, "Unknown instruction: " + insn.getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void filledNewArray(FilledNewArrayNode insn, CodeWriter code) throws CodegenException {
|
||||||
|
code.add("new ");
|
||||||
|
useType(code, insn.getArrayType());
|
||||||
|
code.add('{');
|
||||||
|
int c = insn.getArgsCount();
|
||||||
|
for (int i = 0; i < c; i++) {
|
||||||
|
addArg(code, insn.getArg(i), false);
|
||||||
|
if (i + 1 < c) {
|
||||||
|
code.add(", ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
code.add('}');
|
||||||
|
}
|
||||||
|
|
||||||
|
private void makeConstructor(ConstructorInsn insn, CodeWriter code)
|
||||||
|
throws CodegenException {
|
||||||
|
ClassNode cls = mth.dex().resolveClass(insn.getClassType());
|
||||||
|
if (cls != null && cls.contains(AFlag.ANONYMOUS_CLASS) && !fallback) {
|
||||||
|
// anonymous class construction
|
||||||
|
ArgType parent;
|
||||||
|
if (cls.getInterfaces().size() == 1) {
|
||||||
|
parent = cls.getInterfaces().get(0);
|
||||||
|
} else {
|
||||||
|
parent = cls.getSuperClass();
|
||||||
|
}
|
||||||
|
cls.add(AFlag.DONT_GENERATE);
|
||||||
|
MethodNode defCtr = cls.getDefaultConstructor();
|
||||||
|
if (defCtr != null) {
|
||||||
|
if (RegionUtils.notEmpty(defCtr.getRegion())) {
|
||||||
|
defCtr.add(AFlag.ANONYMOUS_CONSTRUCTOR);
|
||||||
|
} else {
|
||||||
|
defCtr.add(AFlag.DONT_GENERATE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
code.add("new ");
|
||||||
|
if (parent == null) {
|
||||||
|
code.add("Object");
|
||||||
|
} else {
|
||||||
|
useClass(code, parent);
|
||||||
|
}
|
||||||
|
code.add("() ");
|
||||||
|
new ClassGen(cls, mgen.getClassGen().getParentGen()).addClassBody(code);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (insn.isSelf()) {
|
||||||
|
throw new JadxRuntimeException("Constructor 'self' invoke must be removed!");
|
||||||
|
}
|
||||||
|
if (insn.isSuper()) {
|
||||||
|
code.add("super");
|
||||||
|
} else if (insn.isThis()) {
|
||||||
|
code.add("this");
|
||||||
|
} else {
|
||||||
|
code.add("new ");
|
||||||
|
useClass(code, insn.getClassType());
|
||||||
|
}
|
||||||
|
generateMethodArguments(code, insn, 0, mth.dex().resolveMethod(insn.getCallMth()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void makeInvoke(InvokeNode insn, CodeWriter code) throws CodegenException {
|
||||||
|
MethodInfo callMth = insn.getCallMth();
|
||||||
|
|
||||||
|
// inline method
|
||||||
|
MethodNode callMthNode = mth.dex().deepResolveMethod(callMth);
|
||||||
|
if (callMthNode != null) {
|
||||||
|
if (inlineMethod(callMthNode, insn, code)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
callMth = callMthNode.getMethodInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
int k = 0;
|
||||||
|
InvokeType type = insn.getInvokeType();
|
||||||
|
switch (type) {
|
||||||
|
case DIRECT:
|
||||||
|
case VIRTUAL:
|
||||||
|
case INTERFACE:
|
||||||
|
InsnArg arg = insn.getArg(0);
|
||||||
|
// FIXME: add 'this' for equals methods in scope
|
||||||
|
if (!arg.isThis()) {
|
||||||
|
addArgDot(code, arg);
|
||||||
|
}
|
||||||
|
k++;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SUPER:
|
||||||
|
// use 'super' instead 'this' in 0 arg
|
||||||
|
code.add("super").add('.');
|
||||||
|
k++;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case STATIC:
|
||||||
|
ClassInfo insnCls = mth.getParentClass().getAlias();
|
||||||
|
ClassInfo declClass = callMth.getDeclClass();
|
||||||
|
if (!insnCls.equals(declClass)) {
|
||||||
|
useClass(code, declClass);
|
||||||
|
code.add('.');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (callMthNode != null) {
|
||||||
|
code.attachAnnotation(callMthNode);
|
||||||
|
}
|
||||||
|
code.add(callMth.getAlias());
|
||||||
|
generateMethodArguments(code, insn, k, callMthNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
void generateMethodArguments(CodeWriter code, InsnNode insn, int startArgNum,
|
||||||
|
@Nullable MethodNode callMth) throws CodegenException {
|
||||||
|
int k = startArgNum;
|
||||||
|
if (callMth != null && callMth.contains(AFlag.SKIP_FIRST_ARG)) {
|
||||||
|
k++;
|
||||||
|
}
|
||||||
|
int argsCount = insn.getArgsCount();
|
||||||
|
code.add('(');
|
||||||
|
if (k < argsCount) {
|
||||||
|
boolean overloaded = callMth != null && callMth.isArgsOverload();
|
||||||
|
for (int i = k; i < argsCount; i++) {
|
||||||
|
InsnArg arg = insn.getArg(i);
|
||||||
|
boolean cast = overloaded && processOverloadedArg(code, callMth, arg, i - startArgNum);
|
||||||
|
if (!cast && i == argsCount - 1 && processVarArg(code, callMth, arg)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
addArg(code, arg, false);
|
||||||
|
if (i < argsCount - 1) {
|
||||||
|
code.add(", ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
code.add(')');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add additional cast for overloaded method argument.
|
||||||
|
*/
|
||||||
|
private boolean processOverloadedArg(CodeWriter code, MethodNode callMth, InsnArg arg, int origPos) {
|
||||||
|
ArgType origType = callMth.getMethodInfo().getArgumentsTypes().get(origPos);
|
||||||
|
if (!arg.getType().equals(origType)) {
|
||||||
|
code.add('(');
|
||||||
|
useType(code, origType);
|
||||||
|
code.add(") ");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expand varArgs from filled array.
|
||||||
|
*/
|
||||||
|
private boolean processVarArg(CodeWriter code, MethodNode callMth, InsnArg lastArg) throws CodegenException {
|
||||||
|
if (callMth == null || !callMth.getAccessFlags().isVarArgs()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!lastArg.getType().isArray() || !lastArg.isInsnWrap()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
InsnNode insn = ((InsnWrapArg) lastArg).getWrapInsn();
|
||||||
|
if (insn.getType() == InsnType.FILLED_NEW_ARRAY) {
|
||||||
|
int count = insn.getArgsCount();
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
InsnArg elemArg = insn.getArg(i);
|
||||||
|
addArg(code, elemArg, false);
|
||||||
|
if (i < count - 1) {
|
||||||
|
code.add(", ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean inlineMethod(MethodNode callMthNode, InvokeNode insn, CodeWriter code) throws CodegenException {
|
||||||
|
MethodInlineAttr mia = callMthNode.get(AType.METHOD_INLINE);
|
||||||
|
if (mia == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
InsnNode inl = mia.getInsn();
|
||||||
|
if (callMthNode.getMethodInfo().getArgumentsTypes().isEmpty()) {
|
||||||
|
makeInsn(inl, code, Flags.BODY_ONLY);
|
||||||
|
} else {
|
||||||
|
// remap args
|
||||||
|
InsnArg[] regs = new InsnArg[callMthNode.getRegsCount()];
|
||||||
|
List<RegisterArg> callArgs = callMthNode.getArguments(true);
|
||||||
|
for (int i = 0; i < callArgs.size(); i++) {
|
||||||
|
InsnArg arg = insn.getArg(i);
|
||||||
|
RegisterArg callArg = callArgs.get(i);
|
||||||
|
regs[callArg.getRegNum()] = arg;
|
||||||
|
}
|
||||||
|
// replace args
|
||||||
|
List<RegisterArg> inlArgs = new ArrayList<RegisterArg>();
|
||||||
|
inl.getRegisterArgs(inlArgs);
|
||||||
|
Map<RegisterArg, InsnArg> toRevert = new HashMap<RegisterArg, InsnArg>();
|
||||||
|
for (RegisterArg r : inlArgs) {
|
||||||
|
int regNum = r.getRegNum();
|
||||||
|
if (regNum >= regs.length) {
|
||||||
|
LOG.warn("Unknown register number {} in method call: {} from {}", r, callMthNode, mth);
|
||||||
|
} else {
|
||||||
|
InsnArg repl = regs[regNum];
|
||||||
|
if (repl == null) {
|
||||||
|
LOG.warn("Not passed register {} in method call: {} from {}", r, callMthNode, mth);
|
||||||
|
} else {
|
||||||
|
inl.replaceArg(r, repl);
|
||||||
|
toRevert.put(r, repl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
makeInsn(inl, code, Flags.BODY_ONLY);
|
||||||
|
// revert changes in 'MethodInlineAttr'
|
||||||
|
for (Map.Entry<RegisterArg, InsnArg> e : toRevert.entrySet()) {
|
||||||
|
inl.replaceArg(e.getValue(), e.getKey());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void makeTernary(TernaryInsn insn, CodeWriter code, Set<Flags> state) throws CodegenException {
|
||||||
|
boolean wrap = state.contains(Flags.BODY_ONLY);
|
||||||
|
if (wrap) {
|
||||||
|
code.add('(');
|
||||||
|
}
|
||||||
|
InsnArg first = insn.getArg(0);
|
||||||
|
InsnArg second = insn.getArg(1);
|
||||||
|
ConditionGen condGen = new ConditionGen(this);
|
||||||
|
if (first.equals(LiteralArg.TRUE) && second.equals(LiteralArg.FALSE)) {
|
||||||
|
condGen.add(code, insn.getCondition());
|
||||||
|
} else {
|
||||||
|
condGen.wrap(code, insn.getCondition());
|
||||||
|
code.add(" ? ");
|
||||||
|
addArg(code, first, false);
|
||||||
|
code.add(" : ");
|
||||||
|
addArg(code, second, false);
|
||||||
|
}
|
||||||
|
if (wrap) {
|
||||||
|
code.add(')');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void makeArith(ArithNode insn, CodeWriter code, Set<Flags> state) throws CodegenException {
|
||||||
|
if (insn.contains(AFlag.ARITH_ONEARG)) {
|
||||||
|
makeArithOneArg(insn, code);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// wrap insn in brackets for save correct operation order
|
||||||
|
boolean wrap = state.contains(Flags.BODY_ONLY) && !insn.contains(AFlag.DONT_WRAP);
|
||||||
|
if (wrap) {
|
||||||
|
code.add('(');
|
||||||
|
}
|
||||||
|
addArg(code, insn.getArg(0));
|
||||||
|
code.add(' ');
|
||||||
|
code.add(insn.getOp().getSymbol());
|
||||||
|
code.add(' ');
|
||||||
|
addArg(code, insn.getArg(1));
|
||||||
|
if (wrap) {
|
||||||
|
code.add(')');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void makeArithOneArg(ArithNode insn, CodeWriter code) throws CodegenException {
|
||||||
|
ArithOp op = insn.getOp();
|
||||||
|
InsnArg arg = insn.getArg(1);
|
||||||
|
// "++" or "--"
|
||||||
|
if (arg.isLiteral() && (op == ArithOp.ADD || op == ArithOp.SUB)) {
|
||||||
|
LiteralArg lit = (LiteralArg) arg;
|
||||||
|
if (lit.isInteger() && lit.getLiteral() == 1) {
|
||||||
|
assignVar(code, insn);
|
||||||
|
String opSymbol = op.getSymbol();
|
||||||
|
code.add(opSymbol).add(opSymbol);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// +=, -= ...
|
||||||
|
assignVar(code, insn);
|
||||||
|
code.add(' ').add(op.getSymbol()).add("= ");
|
||||||
|
addArg(code, arg, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,246 @@
|
|||||||
|
package jadx.core.codegen;
|
||||||
|
|
||||||
|
import jadx.core.dex.attributes.AFlag;
|
||||||
|
import jadx.core.dex.attributes.AType;
|
||||||
|
import jadx.core.dex.attributes.annotations.MethodParameters;
|
||||||
|
import jadx.core.dex.attributes.nodes.JadxErrorAttr;
|
||||||
|
import jadx.core.dex.info.AccessInfo;
|
||||||
|
import jadx.core.dex.instructions.InsnType;
|
||||||
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
|
import jadx.core.dex.instructions.args.RegisterArg;
|
||||||
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
|
import jadx.core.dex.trycatch.CatchAttr;
|
||||||
|
import jadx.core.dex.visitors.DepthTraversal;
|
||||||
|
import jadx.core.dex.visitors.FallbackModeVisitor;
|
||||||
|
import jadx.core.utils.ErrorsCounter;
|
||||||
|
import jadx.core.utils.InsnUtils;
|
||||||
|
import jadx.core.utils.Utils;
|
||||||
|
import jadx.core.utils.exceptions.CodegenException;
|
||||||
|
import jadx.core.utils.exceptions.DecodeException;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.android.dx.rop.code.AccessFlags;
|
||||||
|
|
||||||
|
public class MethodGen {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(MethodGen.class);
|
||||||
|
|
||||||
|
private final MethodNode mth;
|
||||||
|
private final ClassGen classGen;
|
||||||
|
private final AnnotationGen annotationGen;
|
||||||
|
private final NameGen nameGen;
|
||||||
|
|
||||||
|
public MethodGen(ClassGen classGen, MethodNode mth) {
|
||||||
|
this.mth = mth;
|
||||||
|
this.classGen = classGen;
|
||||||
|
this.annotationGen = classGen.getAnnotationGen();
|
||||||
|
this.nameGen = new NameGen(mth, classGen.isFallbackMode());
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClassGen getClassGen() {
|
||||||
|
return classGen;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NameGen getNameGen() {
|
||||||
|
return nameGen;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MethodNode getMethodNode() {
|
||||||
|
return mth;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean addDefinition(CodeWriter code) {
|
||||||
|
if (mth.getMethodInfo().isClassInit()) {
|
||||||
|
code.startLine("static");
|
||||||
|
code.attachDefinition(mth);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (mth.contains(AFlag.ANONYMOUS_CONSTRUCTOR)) {
|
||||||
|
// don't add method name and arguments
|
||||||
|
code.startLine();
|
||||||
|
code.attachDefinition(mth);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
annotationGen.addForMethod(code, mth);
|
||||||
|
|
||||||
|
AccessInfo clsAccFlags = mth.getParentClass().getAccessFlags();
|
||||||
|
AccessInfo ai = mth.getAccessFlags();
|
||||||
|
// don't add 'abstract' and 'public' to methods in interface
|
||||||
|
if (clsAccFlags.isInterface()) {
|
||||||
|
ai = ai.remove(AccessFlags.ACC_ABSTRACT);
|
||||||
|
ai = ai.remove(AccessFlags.ACC_PUBLIC);
|
||||||
|
}
|
||||||
|
// don't add 'public' for annotations
|
||||||
|
if (clsAccFlags.isAnnotation()) {
|
||||||
|
ai = ai.remove(AccessFlags.ACC_PUBLIC);
|
||||||
|
}
|
||||||
|
code.startLineWithNum(mth.getSourceLine());
|
||||||
|
code.add(ai.makeString());
|
||||||
|
|
||||||
|
if (classGen.addGenericMap(code, mth.getGenericMap())) {
|
||||||
|
code.add(' ');
|
||||||
|
}
|
||||||
|
if (mth.getAccessFlags().isConstructor()) {
|
||||||
|
code.add(classGen.getClassNode().getShortName()); // constructor
|
||||||
|
} else {
|
||||||
|
classGen.useType(code, mth.getReturnType());
|
||||||
|
code.add(' ');
|
||||||
|
code.add(mth.getAlias());
|
||||||
|
}
|
||||||
|
code.add('(');
|
||||||
|
|
||||||
|
List<RegisterArg> args = mth.getArguments(false);
|
||||||
|
if (mth.getMethodInfo().isConstructor()
|
||||||
|
&& mth.getParentClass().contains(AType.ENUM_CLASS)) {
|
||||||
|
if (args.size() == 2) {
|
||||||
|
args.clear();
|
||||||
|
} else if (args.size() > 2) {
|
||||||
|
args = args.subList(2, args.size());
|
||||||
|
} else {
|
||||||
|
LOG.warn(ErrorsCounter.formatErrorMsg(mth,
|
||||||
|
"Incorrect number of args for enum constructor: " + args.size()
|
||||||
|
+ " (expected >= 2)"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addMethodArguments(code, args);
|
||||||
|
code.add(')');
|
||||||
|
|
||||||
|
annotationGen.addThrows(mth, code);
|
||||||
|
code.attachDefinition(mth);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addMethodArguments(CodeWriter argsCode, List<RegisterArg> args) {
|
||||||
|
MethodParameters paramsAnnotation = mth.get(AType.ANNOTATION_MTH_PARAMETERS);
|
||||||
|
int i = 0;
|
||||||
|
for (Iterator<RegisterArg> it = args.iterator(); it.hasNext(); ) {
|
||||||
|
RegisterArg arg = it.next();
|
||||||
|
|
||||||
|
// add argument annotation
|
||||||
|
if (paramsAnnotation != null) {
|
||||||
|
annotationGen.addForParameter(argsCode, paramsAnnotation, i);
|
||||||
|
}
|
||||||
|
if (!it.hasNext() && mth.getAccessFlags().isVarArgs()) {
|
||||||
|
// change last array argument to varargs
|
||||||
|
ArgType type = arg.getType();
|
||||||
|
if (type.isArray()) {
|
||||||
|
ArgType elType = type.getArrayElement();
|
||||||
|
classGen.useType(argsCode, elType);
|
||||||
|
argsCode.add("...");
|
||||||
|
} else {
|
||||||
|
LOG.warn(ErrorsCounter.formatErrorMsg(mth, "Last argument in varargs method not array"));
|
||||||
|
classGen.useType(argsCode, arg.getType());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
classGen.useType(argsCode, arg.getType());
|
||||||
|
}
|
||||||
|
argsCode.add(' ');
|
||||||
|
argsCode.add(nameGen.assignArg(arg));
|
||||||
|
|
||||||
|
i++;
|
||||||
|
if (it.hasNext()) {
|
||||||
|
argsCode.add(", ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addInstructions(CodeWriter code) throws CodegenException {
|
||||||
|
if (mth.contains(AType.JADX_ERROR)
|
||||||
|
|| mth.contains(AFlag.INCONSISTENT_CODE)
|
||||||
|
|| mth.getRegion() == null) {
|
||||||
|
JadxErrorAttr err = mth.get(AType.JADX_ERROR);
|
||||||
|
if (err != null) {
|
||||||
|
code.startLine("/* JADX: method processing error */");
|
||||||
|
Throwable cause = err.getCause();
|
||||||
|
if (cause != null) {
|
||||||
|
code.newLine();
|
||||||
|
code.add("/*");
|
||||||
|
code.startLine("Error: ").add(Utils.getStackTrace(cause));
|
||||||
|
code.add("*/");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
code.startLine("/*");
|
||||||
|
addFallbackMethodCode(code);
|
||||||
|
code.startLine("*/");
|
||||||
|
|
||||||
|
code.startLine("throw new UnsupportedOperationException(\"Method not decompiled: ")
|
||||||
|
.add(mth.toString())
|
||||||
|
.add("\");");
|
||||||
|
} else {
|
||||||
|
RegionGen regionGen = new RegionGen(this);
|
||||||
|
regionGen.makeRegion(code, mth.getRegion());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addFallbackMethodCode(CodeWriter code) {
|
||||||
|
if (mth.getInstructions() == null) {
|
||||||
|
JadxErrorAttr errorAttr = mth.get(AType.JADX_ERROR);
|
||||||
|
if (errorAttr == null
|
||||||
|
|| errorAttr.getCause() == null
|
||||||
|
|| !errorAttr.getCause().getClass().equals(DecodeException.class)) {
|
||||||
|
// load original instructions
|
||||||
|
try {
|
||||||
|
mth.load();
|
||||||
|
DepthTraversal.visit(new FallbackModeVisitor(), mth);
|
||||||
|
} catch (DecodeException e) {
|
||||||
|
LOG.error("Error reload instructions in fallback mode:", e);
|
||||||
|
code.startLine("// Can't load method instructions: " + e.getMessage());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
InsnNode[] insnArr = mth.getInstructions();
|
||||||
|
if (insnArr == null) {
|
||||||
|
code.startLine("// Can't load method instructions.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mth.getThisArg() != null) {
|
||||||
|
code.startLine(nameGen.useArg(mth.getThisArg())).add(" = this;");
|
||||||
|
}
|
||||||
|
addFallbackInsns(code, mth, insnArr, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void addFallbackInsns(CodeWriter code, MethodNode mth, InsnNode[] insnArr, boolean addLabels) {
|
||||||
|
InsnGen insnGen = new InsnGen(getFallbackMethodGen(mth), true);
|
||||||
|
for (InsnNode insn : insnArr) {
|
||||||
|
if (insn == null || insn.getType() == InsnType.NOP) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (addLabels && (insn.contains(AType.JUMP) || insn.contains(AType.EXC_HANDLER))) {
|
||||||
|
code.decIndent();
|
||||||
|
code.startLine(getLabelName(insn.getOffset()) + ":");
|
||||||
|
code.incIndent();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (insnGen.makeInsn(insn, code)) {
|
||||||
|
CatchAttr catchAttr = insn.get(AType.CATCH_BLOCK);
|
||||||
|
if (catchAttr != null) {
|
||||||
|
code.add("\t " + catchAttr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (CodegenException e) {
|
||||||
|
LOG.debug("Error generate fallback instruction: ", e.getCause());
|
||||||
|
code.startLine("// error: " + insn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return fallback variant of method codegen
|
||||||
|
*/
|
||||||
|
public static MethodGen getFallbackMethodGen(MethodNode mth) {
|
||||||
|
ClassGen clsGen = new ClassGen(mth.getParentClass(), null, true, true);
|
||||||
|
return new MethodGen(clsGen, mth);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getLabelName(int offset) {
|
||||||
|
return "L_" + InsnUtils.formatOffset(offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,243 @@
|
|||||||
|
package jadx.core.codegen;
|
||||||
|
|
||||||
|
import jadx.core.Consts;
|
||||||
|
import jadx.core.deobf.NameMapper;
|
||||||
|
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
||||||
|
import jadx.core.dex.info.ClassInfo;
|
||||||
|
import jadx.core.dex.info.MethodInfo;
|
||||||
|
import jadx.core.dex.instructions.InvokeNode;
|
||||||
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
|
import jadx.core.dex.instructions.args.InsnArg;
|
||||||
|
import jadx.core.dex.instructions.args.InsnWrapArg;
|
||||||
|
import jadx.core.dex.instructions.args.NamedArg;
|
||||||
|
import jadx.core.dex.instructions.args.RegisterArg;
|
||||||
|
import jadx.core.dex.instructions.args.SSAVar;
|
||||||
|
import jadx.core.dex.instructions.mods.ConstructorInsn;
|
||||||
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
|
import jadx.core.utils.StringUtils;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class NameGen {
|
||||||
|
|
||||||
|
private static final Map<String, String> OBJ_ALIAS;
|
||||||
|
|
||||||
|
private final Set<String> varNames = new HashSet<String>();
|
||||||
|
private final MethodNode mth;
|
||||||
|
private final boolean fallback;
|
||||||
|
|
||||||
|
static {
|
||||||
|
OBJ_ALIAS = new HashMap<String, String>();
|
||||||
|
OBJ_ALIAS.put(Consts.CLASS_STRING, "str");
|
||||||
|
OBJ_ALIAS.put(Consts.CLASS_CLASS, "cls");
|
||||||
|
OBJ_ALIAS.put(Consts.CLASS_THROWABLE, "th");
|
||||||
|
OBJ_ALIAS.put(Consts.CLASS_OBJECT, "obj");
|
||||||
|
OBJ_ALIAS.put("java.util.Iterator", "it");
|
||||||
|
OBJ_ALIAS.put("java.lang.Boolean", "bool");
|
||||||
|
OBJ_ALIAS.put("java.lang.Short", "sh");
|
||||||
|
OBJ_ALIAS.put("java.lang.Integer", "num");
|
||||||
|
OBJ_ALIAS.put("java.lang.Character", "ch");
|
||||||
|
OBJ_ALIAS.put("java.lang.Byte", "b");
|
||||||
|
OBJ_ALIAS.put("java.lang.Float", "f");
|
||||||
|
OBJ_ALIAS.put("java.lang.Long", "l");
|
||||||
|
OBJ_ALIAS.put("java.lang.Double", "d");
|
||||||
|
}
|
||||||
|
|
||||||
|
public NameGen(MethodNode mth, boolean fallback) {
|
||||||
|
this.mth = mth;
|
||||||
|
this.fallback = fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String assignArg(RegisterArg arg) {
|
||||||
|
String name = makeArgName(arg);
|
||||||
|
if (fallback) {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
name = getUniqueVarName(name);
|
||||||
|
arg.setName(name);
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String assignNamedArg(NamedArg arg) {
|
||||||
|
String name = arg.getName();
|
||||||
|
if (fallback) {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
name = getUniqueVarName(name);
|
||||||
|
arg.setName(name);
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String useArg(RegisterArg arg) {
|
||||||
|
String name = arg.getName();
|
||||||
|
if (name == null || fallback) {
|
||||||
|
return getFallbackName(arg);
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: avoid name collision with variables names
|
||||||
|
public String getLoopLabel(LoopLabelAttr attr) {
|
||||||
|
String name = "loop" + attr.getLoop().getId();
|
||||||
|
varNames.add(name);
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getUniqueVarName(String name) {
|
||||||
|
String r = name;
|
||||||
|
int i = 2;
|
||||||
|
while (varNames.contains(r)) {
|
||||||
|
r = name + i;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
varNames.add(r);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String makeArgName(RegisterArg arg) {
|
||||||
|
if (fallback) {
|
||||||
|
return getFallbackName(arg);
|
||||||
|
}
|
||||||
|
String name = arg.getName();
|
||||||
|
String varName;
|
||||||
|
if (name != null) {
|
||||||
|
if ("this".equals(name)) {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
varName = name;
|
||||||
|
} else {
|
||||||
|
varName = guessName(arg);
|
||||||
|
}
|
||||||
|
if (NameMapper.isReserved(varName)) {
|
||||||
|
return varName + "R";
|
||||||
|
}
|
||||||
|
return varName;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getFallbackName(RegisterArg arg) {
|
||||||
|
return "r" + arg.getRegNum();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String guessName(RegisterArg arg) {
|
||||||
|
SSAVar sVar = arg.getSVar();
|
||||||
|
if (sVar != null && sVar.getName() == null) {
|
||||||
|
RegisterArg assignArg = sVar.getAssign();
|
||||||
|
InsnNode assignInsn = assignArg.getParentInsn();
|
||||||
|
if (assignInsn != null) {
|
||||||
|
String name = makeNameFromInsn(assignInsn);
|
||||||
|
if (name != null && !NameMapper.isReserved(name)) {
|
||||||
|
assignArg.setName(name);
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return makeNameForType(arg.getType());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String makeNameForType(ArgType type) {
|
||||||
|
if (type.isPrimitive()) {
|
||||||
|
return makeNameForPrimitive(type);
|
||||||
|
} else if (type.isArray()) {
|
||||||
|
return makeNameForType(type.getArrayRootElement()) + "Arr";
|
||||||
|
} else {
|
||||||
|
return makeNameForObject(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String makeNameForPrimitive(ArgType type) {
|
||||||
|
return type.getPrimitiveType().getShortName().toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String makeNameForObject(ArgType type) {
|
||||||
|
if (type.isObject()) {
|
||||||
|
String alias = getAliasForObject(type.getObject());
|
||||||
|
if (alias != null) {
|
||||||
|
return alias;
|
||||||
|
}
|
||||||
|
ClassInfo extClsInfo = ClassInfo.extCls(mth.dex(), type);
|
||||||
|
String shortName = extClsInfo.getShortName();
|
||||||
|
String vName = fromName(shortName);
|
||||||
|
if (vName != null) {
|
||||||
|
return vName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return StringUtils.escape(type.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String fromName(String name) {
|
||||||
|
if (name == null || name.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (name.toUpperCase().equals(name)) {
|
||||||
|
// all characters are upper case
|
||||||
|
return name.toLowerCase();
|
||||||
|
}
|
||||||
|
String v1 = Character.toLowerCase(name.charAt(0)) + name.substring(1);
|
||||||
|
if (!v1.equals(name)) {
|
||||||
|
return v1;
|
||||||
|
}
|
||||||
|
if (name.length() < 3) {
|
||||||
|
return name + "Var";
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getAliasForObject(String name) {
|
||||||
|
return OBJ_ALIAS.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String makeNameFromInsn(InsnNode insn) {
|
||||||
|
switch (insn.getType()) {
|
||||||
|
case INVOKE:
|
||||||
|
InvokeNode inv = (InvokeNode) insn;
|
||||||
|
return makeNameFromInvoke(inv.getCallMth());
|
||||||
|
|
||||||
|
case CONSTRUCTOR:
|
||||||
|
ConstructorInsn co = (ConstructorInsn) insn;
|
||||||
|
return makeNameForObject(co.getClassType().getType());
|
||||||
|
|
||||||
|
case ARRAY_LENGTH:
|
||||||
|
return "length";
|
||||||
|
|
||||||
|
case ARITH:
|
||||||
|
case TERNARY:
|
||||||
|
case CAST:
|
||||||
|
for (InsnArg arg : insn.getArguments()) {
|
||||||
|
if (arg.isInsnWrap()) {
|
||||||
|
InsnNode wrapInsn = ((InsnWrapArg) arg).getWrapInsn();
|
||||||
|
String wName = makeNameFromInsn(wrapInsn);
|
||||||
|
if (wName != null) {
|
||||||
|
return wName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String makeNameFromInvoke(MethodInfo callMth) {
|
||||||
|
String name = callMth.getName();
|
||||||
|
if (name.startsWith("get") || name.startsWith("set")) {
|
||||||
|
return fromName(name.substring(3));
|
||||||
|
}
|
||||||
|
ArgType declType = callMth.getDeclClass().getAlias().getType();
|
||||||
|
if ("iterator".equals(name)) {
|
||||||
|
return "it";
|
||||||
|
}
|
||||||
|
if ("toString".equals(name)) {
|
||||||
|
return makeNameForType(declType);
|
||||||
|
}
|
||||||
|
if ("forName".equals(name) && declType.equals(ArgType.CLASS)) {
|
||||||
|
return OBJ_ALIAS.get(Consts.CLASS_CLASS);
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,323 @@
|
|||||||
|
package jadx.core.codegen;
|
||||||
|
|
||||||
|
import jadx.core.dex.attributes.AFlag;
|
||||||
|
import jadx.core.dex.attributes.AType;
|
||||||
|
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
|
||||||
|
import jadx.core.dex.attributes.nodes.ForceReturnAttr;
|
||||||
|
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
||||||
|
import jadx.core.dex.instructions.SwitchNode;
|
||||||
|
import jadx.core.dex.instructions.args.InsnArg;
|
||||||
|
import jadx.core.dex.instructions.args.NamedArg;
|
||||||
|
import jadx.core.dex.instructions.args.RegisterArg;
|
||||||
|
import jadx.core.dex.nodes.BlockNode;
|
||||||
|
import jadx.core.dex.nodes.FieldNode;
|
||||||
|
import jadx.core.dex.nodes.IBlock;
|
||||||
|
import jadx.core.dex.nodes.IContainer;
|
||||||
|
import jadx.core.dex.nodes.IRegion;
|
||||||
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
|
import jadx.core.dex.nodes.parser.FieldValueAttr;
|
||||||
|
import jadx.core.dex.regions.Region;
|
||||||
|
import jadx.core.dex.regions.SwitchRegion;
|
||||||
|
import jadx.core.dex.regions.SynchronizedRegion;
|
||||||
|
import jadx.core.dex.regions.TryCatchRegion;
|
||||||
|
import jadx.core.dex.regions.conditions.IfCondition;
|
||||||
|
import jadx.core.dex.regions.conditions.IfRegion;
|
||||||
|
import jadx.core.dex.regions.loops.ForEachLoop;
|
||||||
|
import jadx.core.dex.regions.loops.ForLoop;
|
||||||
|
import jadx.core.dex.regions.loops.LoopRegion;
|
||||||
|
import jadx.core.dex.regions.loops.LoopType;
|
||||||
|
import jadx.core.dex.trycatch.ExceptionHandler;
|
||||||
|
import jadx.core.utils.ErrorsCounter;
|
||||||
|
import jadx.core.utils.RegionUtils;
|
||||||
|
import jadx.core.utils.exceptions.CodegenException;
|
||||||
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class RegionGen extends InsnGen {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(RegionGen.class);
|
||||||
|
|
||||||
|
public RegionGen(MethodGen mgen) {
|
||||||
|
super(mgen, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void makeRegion(CodeWriter code, IContainer cont) throws CodegenException {
|
||||||
|
if (cont instanceof IBlock) {
|
||||||
|
makeSimpleBlock((IBlock) cont, code);
|
||||||
|
} else if (cont instanceof IRegion) {
|
||||||
|
if (cont instanceof Region) {
|
||||||
|
makeSimpleRegion(code, (Region) cont);
|
||||||
|
} else {
|
||||||
|
declareVars(code, cont);
|
||||||
|
if (cont instanceof IfRegion) {
|
||||||
|
makeIf((IfRegion) cont, code, true);
|
||||||
|
} else if (cont instanceof SwitchRegion) {
|
||||||
|
makeSwitch((SwitchRegion) cont, code);
|
||||||
|
} else if (cont instanceof LoopRegion) {
|
||||||
|
makeLoop((LoopRegion) cont, code);
|
||||||
|
} else if (cont instanceof TryCatchRegion) {
|
||||||
|
makeTryCatch((TryCatchRegion) cont, code);
|
||||||
|
} else if (cont instanceof SynchronizedRegion) {
|
||||||
|
makeSynchronizedRegion((SynchronizedRegion) cont, code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new CodegenException("Not processed container: " + cont);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void declareVars(CodeWriter code, IContainer cont) {
|
||||||
|
DeclareVariablesAttr declVars = cont.get(AType.DECLARE_VARIABLES);
|
||||||
|
if (declVars != null) {
|
||||||
|
for (RegisterArg v : declVars.getVars()) {
|
||||||
|
code.startLine();
|
||||||
|
declareVar(code, v);
|
||||||
|
code.add(';');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void makeSimpleRegion(CodeWriter code, Region region) throws CodegenException {
|
||||||
|
declareVars(code, region);
|
||||||
|
for (IContainer c : region.getSubBlocks()) {
|
||||||
|
makeRegion(code, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void makeRegionIndent(CodeWriter code, IContainer region) throws CodegenException {
|
||||||
|
code.incIndent();
|
||||||
|
makeRegion(code, region);
|
||||||
|
code.decIndent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void makeSimpleBlock(IBlock block, CodeWriter code) throws CodegenException {
|
||||||
|
for (InsnNode insn : block.getInstructions()) {
|
||||||
|
if (!insn.contains(AFlag.SKIP)) {
|
||||||
|
makeInsn(insn, code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ForceReturnAttr retAttr = block.get(AType.FORCE_RETURN);
|
||||||
|
if (retAttr != null) {
|
||||||
|
makeInsn(retAttr.getReturnInsn(), code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void makeIf(IfRegion region, CodeWriter code, boolean newLine) throws CodegenException {
|
||||||
|
if (newLine) {
|
||||||
|
code.startLineWithNum(region.getSourceLine());
|
||||||
|
} else {
|
||||||
|
code.attachSourceLine(region.getSourceLine());
|
||||||
|
}
|
||||||
|
code.add("if (");
|
||||||
|
new ConditionGen(this).add(code, region.getCondition());
|
||||||
|
code.add(") {");
|
||||||
|
makeRegionIndent(code, region.getThenRegion());
|
||||||
|
code.startLine('}');
|
||||||
|
|
||||||
|
IContainer els = region.getElseRegion();
|
||||||
|
if (els != null && RegionUtils.notEmpty(els)) {
|
||||||
|
code.add(" else ");
|
||||||
|
if (connectElseIf(code, els)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
code.add('{');
|
||||||
|
makeRegionIndent(code, els);
|
||||||
|
code.startLine('}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connect if-else-if block
|
||||||
|
*/
|
||||||
|
private boolean connectElseIf(CodeWriter code, IContainer els) throws CodegenException {
|
||||||
|
if (!els.contains(AFlag.ELSE_IF_CHAIN)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!(els instanceof Region)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
List<IContainer> subBlocks = ((Region) els).getSubBlocks();
|
||||||
|
if (subBlocks.size() == 1
|
||||||
|
&& subBlocks.get(0) instanceof IfRegion) {
|
||||||
|
makeIf((IfRegion) subBlocks.get(0), code, false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private CodeWriter makeLoop(LoopRegion region, CodeWriter code) throws CodegenException {
|
||||||
|
BlockNode header = region.getHeader();
|
||||||
|
if (header != null) {
|
||||||
|
List<InsnNode> headerInsns = header.getInstructions();
|
||||||
|
if (headerInsns.size() > 1) {
|
||||||
|
ErrorsCounter.methodError(mth, "Found not inlined instructions from loop header");
|
||||||
|
int last = headerInsns.size() - 1;
|
||||||
|
for (int i = 0; i < last; i++) {
|
||||||
|
InsnNode insn = headerInsns.get(i);
|
||||||
|
makeInsn(insn, code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LoopLabelAttr labelAttr = region.getInfo().getStart().get(AType.LOOP_LABEL);
|
||||||
|
if (labelAttr != null) {
|
||||||
|
code.startLine(mgen.getNameGen().getLoopLabel(labelAttr)).add(':');
|
||||||
|
}
|
||||||
|
|
||||||
|
IfCondition condition = region.getCondition();
|
||||||
|
if (condition == null) {
|
||||||
|
// infinite loop
|
||||||
|
code.startLine("while (true) {");
|
||||||
|
makeRegionIndent(code, region.getBody());
|
||||||
|
code.startLine('}');
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
ConditionGen conditionGen = new ConditionGen(this);
|
||||||
|
LoopType type = region.getType();
|
||||||
|
if (type != null) {
|
||||||
|
if (type instanceof ForLoop) {
|
||||||
|
ForLoop forLoop = (ForLoop) type;
|
||||||
|
code.startLine("for (");
|
||||||
|
makeInsn(forLoop.getInitInsn(), code, Flags.INLINE);
|
||||||
|
code.add("; ");
|
||||||
|
conditionGen.add(code, condition);
|
||||||
|
code.add("; ");
|
||||||
|
makeInsn(forLoop.getIncrInsn(), code, Flags.INLINE);
|
||||||
|
code.add(") {");
|
||||||
|
makeRegionIndent(code, region.getBody());
|
||||||
|
code.startLine('}');
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
if (type instanceof ForEachLoop) {
|
||||||
|
ForEachLoop forEachLoop = (ForEachLoop) type;
|
||||||
|
code.startLine("for (");
|
||||||
|
declareVar(code, forEachLoop.getVarArg());
|
||||||
|
code.add(" : ");
|
||||||
|
addArg(code, forEachLoop.getIterableArg(), false);
|
||||||
|
code.add(") {");
|
||||||
|
makeRegionIndent(code, region.getBody());
|
||||||
|
code.startLine('}');
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
throw new JadxRuntimeException("Unknown loop type: " + type.getClass());
|
||||||
|
}
|
||||||
|
if (region.isConditionAtEnd()) {
|
||||||
|
code.startLine("do {");
|
||||||
|
makeRegionIndent(code, region.getBody());
|
||||||
|
code.startLine("} while (");
|
||||||
|
conditionGen.add(code, condition);
|
||||||
|
code.add(");");
|
||||||
|
} else {
|
||||||
|
code.startLine("while (");
|
||||||
|
conditionGen.add(code, condition);
|
||||||
|
code.add(") {");
|
||||||
|
makeRegionIndent(code, region.getBody());
|
||||||
|
code.startLine('}');
|
||||||
|
}
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void makeSynchronizedRegion(SynchronizedRegion cont, CodeWriter code) throws CodegenException {
|
||||||
|
code.startLine("synchronized (");
|
||||||
|
addArg(code, cont.getEnterInsn().getArg(0));
|
||||||
|
code.add(") {");
|
||||||
|
makeRegionIndent(code, cont.getRegion());
|
||||||
|
code.startLine('}');
|
||||||
|
}
|
||||||
|
|
||||||
|
private CodeWriter makeSwitch(SwitchRegion sw, CodeWriter code) throws CodegenException {
|
||||||
|
SwitchNode insn = (SwitchNode) sw.getHeader().getInstructions().get(0);
|
||||||
|
InsnArg arg = insn.getArg(0);
|
||||||
|
code.startLine("switch (");
|
||||||
|
addArg(code, arg, false);
|
||||||
|
code.add(") {");
|
||||||
|
code.incIndent();
|
||||||
|
|
||||||
|
int size = sw.getKeys().size();
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
List<Object> keys = sw.getKeys().get(i);
|
||||||
|
IContainer c = sw.getCases().get(i);
|
||||||
|
for (Object k : keys) {
|
||||||
|
code.startLine("case ");
|
||||||
|
if (k instanceof FieldNode) {
|
||||||
|
FieldNode fn = (FieldNode) k;
|
||||||
|
if (fn.getParentClass().isEnum()) {
|
||||||
|
code.add(fn.getAlias());
|
||||||
|
} else {
|
||||||
|
staticField(code, fn.getFieldInfo());
|
||||||
|
// print original value, sometimes replace with incorrect field
|
||||||
|
FieldValueAttr valueAttr = fn.get(AType.FIELD_VALUE);
|
||||||
|
if (valueAttr != null && valueAttr.getValue() != null) {
|
||||||
|
code.add(" /*").add(valueAttr.getValue().toString()).add("*/");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (k instanceof Integer) {
|
||||||
|
code.add(TypeGen.literalToString((Integer) k, arg.getType()));
|
||||||
|
} else {
|
||||||
|
throw new JadxRuntimeException("Unexpected key in switch: " + (k != null ? k.getClass() : null));
|
||||||
|
}
|
||||||
|
code.add(':');
|
||||||
|
}
|
||||||
|
makeRegionIndent(code, c);
|
||||||
|
}
|
||||||
|
if (sw.getDefaultCase() != null) {
|
||||||
|
code.startLine("default:");
|
||||||
|
makeRegionIndent(code, sw.getDefaultCase());
|
||||||
|
}
|
||||||
|
code.decIndent();
|
||||||
|
code.startLine('}');
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void makeTryCatch(TryCatchRegion region, CodeWriter code) throws CodegenException {
|
||||||
|
code.startLine("try {");
|
||||||
|
makeRegionIndent(code, region.getTryRegion());
|
||||||
|
// TODO: move search of 'allHandler' to 'TryCatchRegion'
|
||||||
|
ExceptionHandler allHandler = null;
|
||||||
|
for (Map.Entry<ExceptionHandler, IContainer> entry : region.getCatchRegions().entrySet()) {
|
||||||
|
ExceptionHandler handler = entry.getKey();
|
||||||
|
if (handler.isCatchAll()) {
|
||||||
|
if (allHandler != null) {
|
||||||
|
LOG.warn("Several 'all' handlers in try/catch block in {}", mth);
|
||||||
|
}
|
||||||
|
allHandler = handler;
|
||||||
|
} else {
|
||||||
|
makeCatchBlock(code, handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (allHandler != null) {
|
||||||
|
makeCatchBlock(code, allHandler);
|
||||||
|
}
|
||||||
|
IContainer finallyRegion = region.getFinallyRegion();
|
||||||
|
if (finallyRegion != null) {
|
||||||
|
code.startLine("} finally {");
|
||||||
|
makeRegionIndent(code, finallyRegion);
|
||||||
|
}
|
||||||
|
code.startLine('}');
|
||||||
|
}
|
||||||
|
|
||||||
|
private void makeCatchBlock(CodeWriter code, ExceptionHandler handler) throws CodegenException {
|
||||||
|
IContainer region = handler.getHandlerRegion();
|
||||||
|
if (region == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
code.startLine("} catch (");
|
||||||
|
InsnArg arg = handler.getArg();
|
||||||
|
if (arg instanceof RegisterArg) {
|
||||||
|
declareVar(code, (RegisterArg) arg);
|
||||||
|
} else if (arg instanceof NamedArg) {
|
||||||
|
if (handler.isCatchAll()) {
|
||||||
|
code.add("Throwable");
|
||||||
|
} else {
|
||||||
|
useClass(code, handler.getCatchType());
|
||||||
|
}
|
||||||
|
code.add(' ');
|
||||||
|
code.add(mgen.getNameGen().assignNamedArg((NamedArg) arg));
|
||||||
|
}
|
||||||
|
code.add(") {");
|
||||||
|
makeRegionIndent(code, region);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
package jadx.core.codegen;
|
||||||
|
|
||||||
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
|
import jadx.core.dex.instructions.args.PrimitiveType;
|
||||||
|
import jadx.core.utils.StringUtils;
|
||||||
|
import jadx.core.utils.Utils;
|
||||||
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class TypeGen {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(TypeGen.class);
|
||||||
|
|
||||||
|
private TypeGen() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String signature(ArgType type) {
|
||||||
|
PrimitiveType stype = type.getPrimitiveType();
|
||||||
|
if (stype == PrimitiveType.OBJECT) {
|
||||||
|
return Utils.makeQualifiedObjectName(type.getObject());
|
||||||
|
}
|
||||||
|
if (stype == PrimitiveType.ARRAY) {
|
||||||
|
return '[' + signature(type.getArrayElement());
|
||||||
|
}
|
||||||
|
return stype.getShortName();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert literal value to string according to value type
|
||||||
|
*
|
||||||
|
* @throws JadxRuntimeException for incorrect type or literal value
|
||||||
|
*/
|
||||||
|
public static String literalToString(long lit, ArgType type) {
|
||||||
|
if (type == null || !type.isTypeKnown()) {
|
||||||
|
String n = Long.toString(lit);
|
||||||
|
if (Math.abs(lit) > 100) {
|
||||||
|
n += "; // 0x" + Long.toHexString(lit)
|
||||||
|
+ " float:" + Float.intBitsToFloat((int) lit)
|
||||||
|
+ " double:" + Double.longBitsToDouble(lit);
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (type.getPrimitiveType()) {
|
||||||
|
case BOOLEAN:
|
||||||
|
return lit == 0 ? "false" : "true";
|
||||||
|
case CHAR:
|
||||||
|
return StringUtils.unescapeChar((char) lit);
|
||||||
|
case BYTE:
|
||||||
|
return formatByte((byte) lit);
|
||||||
|
case SHORT:
|
||||||
|
return formatShort((short) lit);
|
||||||
|
case INT:
|
||||||
|
return formatInteger((int) lit);
|
||||||
|
case LONG:
|
||||||
|
return formatLong(lit);
|
||||||
|
case FLOAT:
|
||||||
|
return formatFloat(Float.intBitsToFloat((int) lit));
|
||||||
|
case DOUBLE:
|
||||||
|
return formatDouble(Double.longBitsToDouble(lit));
|
||||||
|
|
||||||
|
case OBJECT:
|
||||||
|
case ARRAY:
|
||||||
|
if (lit != 0) {
|
||||||
|
LOG.warn("Wrong object literal: {} for type: {}", lit, type);
|
||||||
|
return Long.toString(lit);
|
||||||
|
}
|
||||||
|
return "null";
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new JadxRuntimeException("Unknown type in literalToString: " + type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String formatShort(short s) {
|
||||||
|
if (s == Short.MAX_VALUE) {
|
||||||
|
return "Short.MAX_VALUE";
|
||||||
|
}
|
||||||
|
if (s == Short.MIN_VALUE) {
|
||||||
|
return "Short.MIN_VALUE";
|
||||||
|
}
|
||||||
|
return "(short) " + Short.toString(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String formatByte(byte b) {
|
||||||
|
if (b == Byte.MAX_VALUE) {
|
||||||
|
return "Byte.MAX_VALUE";
|
||||||
|
}
|
||||||
|
if (b == Byte.MIN_VALUE) {
|
||||||
|
return "Byte.MIN_VALUE";
|
||||||
|
}
|
||||||
|
return "(byte) " + Byte.toString(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String formatInteger(int i) {
|
||||||
|
if (i == Integer.MAX_VALUE) {
|
||||||
|
return "Integer.MAX_VALUE";
|
||||||
|
}
|
||||||
|
if (i == Integer.MIN_VALUE) {
|
||||||
|
return "Integer.MIN_VALUE";
|
||||||
|
}
|
||||||
|
return Integer.toString(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String formatLong(long l) {
|
||||||
|
if (l == Long.MAX_VALUE) {
|
||||||
|
return "Long.MAX_VALUE";
|
||||||
|
}
|
||||||
|
if (l == Long.MIN_VALUE) {
|
||||||
|
return "Long.MIN_VALUE";
|
||||||
|
}
|
||||||
|
String str = Long.toString(l);
|
||||||
|
if (Math.abs(l) >= Integer.MAX_VALUE) {
|
||||||
|
str += "L";
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String formatDouble(double d) {
|
||||||
|
if (Double.isNaN(d)) {
|
||||||
|
return "Double.NaN";
|
||||||
|
}
|
||||||
|
if (d == Double.NEGATIVE_INFINITY) {
|
||||||
|
return "Double.NEGATIVE_INFINITY";
|
||||||
|
}
|
||||||
|
if (d == Double.POSITIVE_INFINITY) {
|
||||||
|
return "Double.POSITIVE_INFINITY";
|
||||||
|
}
|
||||||
|
if (d == Double.MIN_VALUE) {
|
||||||
|
return "Double.MIN_VALUE";
|
||||||
|
}
|
||||||
|
if (d == Double.MAX_VALUE) {
|
||||||
|
return "Double.MAX_VALUE";
|
||||||
|
}
|
||||||
|
if (d == Double.MIN_NORMAL) {
|
||||||
|
return "Double.MIN_NORMAL";
|
||||||
|
}
|
||||||
|
return Double.toString(d) + "d";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String formatFloat(float f) {
|
||||||
|
if (Float.isNaN(f)) {
|
||||||
|
return "Float.NaN";
|
||||||
|
}
|
||||||
|
if (f == Float.NEGATIVE_INFINITY) {
|
||||||
|
return "Float.NEGATIVE_INFINITY";
|
||||||
|
}
|
||||||
|
if (f == Float.POSITIVE_INFINITY) {
|
||||||
|
return "Float.POSITIVE_INFINITY";
|
||||||
|
}
|
||||||
|
if (f == Float.MIN_VALUE) {
|
||||||
|
return "Float.MIN_VALUE";
|
||||||
|
}
|
||||||
|
if (f == Float.MAX_VALUE) {
|
||||||
|
return "Float.MAX_VALUE";
|
||||||
|
}
|
||||||
|
if (f == Float.MIN_NORMAL) {
|
||||||
|
return "Float.MIN_NORMAL";
|
||||||
|
}
|
||||||
|
return Float.toString(f) + "f";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package jadx.core.deobf;
|
||||||
|
|
||||||
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
|
|
||||||
|
class DeobfClsInfo {
|
||||||
|
private final Deobfuscator deobfuscator;
|
||||||
|
private final ClassNode cls;
|
||||||
|
private final PackageNode pkg;
|
||||||
|
private final String alias;
|
||||||
|
|
||||||
|
public DeobfClsInfo(Deobfuscator deobfuscator, ClassNode cls, PackageNode pkg, String alias) {
|
||||||
|
this.deobfuscator = deobfuscator;
|
||||||
|
this.cls = cls;
|
||||||
|
this.pkg = pkg;
|
||||||
|
this.alias = alias;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String makeNameWithoutPkg() {
|
||||||
|
String prefix;
|
||||||
|
ClassNode parentClass = cls.getParentClass();
|
||||||
|
if (parentClass != cls) {
|
||||||
|
DeobfClsInfo parentDeobfClsInfo = deobfuscator.getClsMap().get(parentClass.getClassInfo());
|
||||||
|
if (parentDeobfClsInfo != null) {
|
||||||
|
prefix = parentDeobfClsInfo.makeNameWithoutPkg();
|
||||||
|
} else {
|
||||||
|
prefix = deobfuscator.getNameWithoutPackage(parentClass.getClassInfo());
|
||||||
|
}
|
||||||
|
prefix += Deobfuscator.INNER_CLASS_SEPARATOR;
|
||||||
|
} else {
|
||||||
|
prefix = "";
|
||||||
|
}
|
||||||
|
return prefix + (this.alias != null ? this.alias : this.cls.getShortName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFullName() {
|
||||||
|
return pkg.getFullAlias() + Deobfuscator.CLASS_NAME_SEPARATOR + makeNameWithoutPkg();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClassNode getCls() {
|
||||||
|
return cls;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PackageNode getPkg() {
|
||||||
|
return pkg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAlias() {
|
||||||
|
return alias;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
package jadx.core.deobf;
|
||||||
|
|
||||||
|
import jadx.core.dex.info.ClassInfo;
|
||||||
|
import jadx.core.dex.info.FieldInfo;
|
||||||
|
import jadx.core.dex.info.MethodInfo;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.apache.commons.io.FileUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
class DeobfPresets {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(DeobfPresets.class);
|
||||||
|
|
||||||
|
private static final String MAP_FILE_CHARSET = "UTF-8";
|
||||||
|
|
||||||
|
private final Deobfuscator deobfuscator;
|
||||||
|
private final File deobfMapFile;
|
||||||
|
|
||||||
|
private final Map<String, String> clsPresetMap = new HashMap<String, String>();
|
||||||
|
private final Map<String, String> fldPresetMap = new HashMap<String, String>();
|
||||||
|
private final Map<String, String> mthPresetMap = new HashMap<String, String>();
|
||||||
|
|
||||||
|
public DeobfPresets(Deobfuscator deobfuscator, File deobfMapFile) {
|
||||||
|
this.deobfuscator = deobfuscator;
|
||||||
|
this.deobfMapFile = deobfMapFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads deobfuscator presets
|
||||||
|
*/
|
||||||
|
public void load() {
|
||||||
|
if (!deobfMapFile.exists()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LOG.info("Loading obfuscation map from: {}", deobfMapFile.getAbsoluteFile());
|
||||||
|
try {
|
||||||
|
List<String> lines = FileUtils.readLines(deobfMapFile, MAP_FILE_CHARSET);
|
||||||
|
for (String l : lines) {
|
||||||
|
l = l.trim();
|
||||||
|
if (l.isEmpty() || l.startsWith("#")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String[] va = splitAndTrim(l);
|
||||||
|
if (va.length != 2) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String origName = va[0];
|
||||||
|
String alias = va[1];
|
||||||
|
if (l.startsWith("p ")) {
|
||||||
|
deobfuscator.addPackagePreset(origName, alias);
|
||||||
|
} else if (l.startsWith("c ")) {
|
||||||
|
clsPresetMap.put(origName, alias);
|
||||||
|
} else if (l.startsWith("f ")) {
|
||||||
|
fldPresetMap.put(origName, alias);
|
||||||
|
} else if (l.startsWith("m ")) {
|
||||||
|
mthPresetMap.put(origName, alias);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.error("Failed to load deobfuscation map file '{}'", deobfMapFile.getAbsolutePath(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String[] splitAndTrim(String str) {
|
||||||
|
String[] v = str.substring(2).split("=");
|
||||||
|
for (int i = 0; i < v.length; i++) {
|
||||||
|
v[i] = v[i].trim();
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void save(boolean forceSave) {
|
||||||
|
try {
|
||||||
|
if (deobfMapFile.exists()) {
|
||||||
|
if (forceSave) {
|
||||||
|
dumpMapping();
|
||||||
|
} else {
|
||||||
|
LOG.warn("Deobfuscation map file '{}' exists. Use command line option '--deobf-rewrite-cfg' to rewrite it",
|
||||||
|
deobfMapFile.getAbsolutePath());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dumpMapping();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.error("Failed to load deobfuscation map file '{}'", deobfMapFile.getAbsolutePath(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves DefaultDeobfuscator presets
|
||||||
|
*/
|
||||||
|
private void dumpMapping() throws IOException {
|
||||||
|
List<String> list = new ArrayList<String>();
|
||||||
|
// packages
|
||||||
|
for (PackageNode p : deobfuscator.getRootPackage().getInnerPackages()) {
|
||||||
|
for (PackageNode pp : p.getInnerPackages()) {
|
||||||
|
dfsPackageName(list, p.getName(), pp);
|
||||||
|
}
|
||||||
|
if (p.hasAlias()) {
|
||||||
|
list.add(String.format("p %s = %s", p.getName(), p.getAlias()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// classes
|
||||||
|
for (DeobfClsInfo deobfClsInfo : deobfuscator.getClsMap().values()) {
|
||||||
|
if (deobfClsInfo.getAlias() != null) {
|
||||||
|
list.add(String.format("c %s = %s",
|
||||||
|
deobfClsInfo.getCls().getClassInfo().getFullName(), deobfClsInfo.getAlias()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (FieldInfo fld : deobfuscator.getFldMap().keySet()) {
|
||||||
|
list.add(String.format("f %s = %s", fld.getFullId(), fld.getAlias()));
|
||||||
|
}
|
||||||
|
for (MethodInfo mth : deobfuscator.getMthMap().keySet()) {
|
||||||
|
list.add(String.format("m %s = %s", mth.getFullId(), mth.getAlias()));
|
||||||
|
}
|
||||||
|
Collections.sort(list);
|
||||||
|
FileUtils.writeLines(deobfMapFile, MAP_FILE_CHARSET, list);
|
||||||
|
list.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void dfsPackageName(List<String> list, String prefix, PackageNode node) {
|
||||||
|
for (PackageNode pp : node.getInnerPackages()) {
|
||||||
|
dfsPackageName(list, prefix + '.' + node.getName(), pp);
|
||||||
|
}
|
||||||
|
if (node.hasAlias()) {
|
||||||
|
list.add(String.format("p %s.%s = %s", prefix, node.getName(), node.getAlias()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getForCls(ClassInfo cls) {
|
||||||
|
return clsPresetMap.get(cls.getFullName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getForFld(FieldInfo fld) {
|
||||||
|
return fldPresetMap.get(fld.getFullId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getForMth(MethodInfo mth) {
|
||||||
|
return mthPresetMap.get(mth.getFullId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
clsPresetMap.clear();
|
||||||
|
fldPresetMap.clear();
|
||||||
|
mthPresetMap.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getClsPresetMap() {
|
||||||
|
return clsPresetMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getFldPresetMap() {
|
||||||
|
return fldPresetMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getMthPresetMap() {
|
||||||
|
return mthPresetMap;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,408 @@
|
|||||||
|
package jadx.core.deobf;
|
||||||
|
|
||||||
|
import jadx.api.IJadxArgs;
|
||||||
|
import jadx.core.dex.attributes.AType;
|
||||||
|
import jadx.core.dex.attributes.nodes.SourceFileAttr;
|
||||||
|
import jadx.core.dex.info.ClassInfo;
|
||||||
|
import jadx.core.dex.info.FieldInfo;
|
||||||
|
import jadx.core.dex.info.MethodInfo;
|
||||||
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
|
import jadx.core.dex.nodes.DexNode;
|
||||||
|
import jadx.core.dex.nodes.FieldNode;
|
||||||
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class Deobfuscator {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(Deobfuscator.class);
|
||||||
|
|
||||||
|
private static final boolean DEBUG = false;
|
||||||
|
|
||||||
|
public static final String CLASS_NAME_SEPARATOR = ".";
|
||||||
|
public static final String INNER_CLASS_SEPARATOR = "$";
|
||||||
|
|
||||||
|
private final IJadxArgs args;
|
||||||
|
@NotNull
|
||||||
|
private final List<DexNode> dexNodes;
|
||||||
|
private final DeobfPresets deobfPresets;
|
||||||
|
|
||||||
|
private final Map<ClassInfo, DeobfClsInfo> clsMap = new HashMap<ClassInfo, DeobfClsInfo>();
|
||||||
|
private final Map<FieldInfo, String> fldMap = new HashMap<FieldInfo, String>();
|
||||||
|
private final Map<MethodInfo, String> mthMap = new HashMap<MethodInfo, String>();
|
||||||
|
|
||||||
|
private final PackageNode rootPackage = new PackageNode("");
|
||||||
|
private final Set<String> pkgSet = new TreeSet<String>();
|
||||||
|
|
||||||
|
private final int maxLength;
|
||||||
|
private final int minLength;
|
||||||
|
private int pkgIndex = 0;
|
||||||
|
private int clsIndex = 0;
|
||||||
|
private int fldIndex = 0;
|
||||||
|
private int mthIndex = 0;
|
||||||
|
|
||||||
|
public Deobfuscator(IJadxArgs args, @NotNull List<DexNode> dexNodes, File deobfMapFile) {
|
||||||
|
this.args = args;
|
||||||
|
this.dexNodes = dexNodes;
|
||||||
|
|
||||||
|
this.minLength = args.getDeobfuscationMinLength();
|
||||||
|
this.maxLength = args.getDeobfuscationMaxLength();
|
||||||
|
|
||||||
|
this.deobfPresets = new DeobfPresets(this, deobfMapFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void execute() {
|
||||||
|
if (!args.isDeobfuscationForceSave()) {
|
||||||
|
deobfPresets.load();
|
||||||
|
initIndexes();
|
||||||
|
}
|
||||||
|
process();
|
||||||
|
deobfPresets.save(args.isDeobfuscationForceSave());
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initIndexes() {
|
||||||
|
pkgIndex = pkgSet.size();
|
||||||
|
clsIndex = deobfPresets.getClsPresetMap().size();
|
||||||
|
fldIndex = deobfPresets.getFldPresetMap().size();
|
||||||
|
mthIndex = deobfPresets.getMthPresetMap().size();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void preProcess() {
|
||||||
|
for (DexNode dexNode : dexNodes) {
|
||||||
|
for (ClassNode cls : dexNode.getClasses()) {
|
||||||
|
doClass(cls);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void process() {
|
||||||
|
preProcess();
|
||||||
|
if (DEBUG) {
|
||||||
|
dumpAlias();
|
||||||
|
}
|
||||||
|
for (DexNode dexNode : dexNodes) {
|
||||||
|
for (ClassNode cls : dexNode.getClasses()) {
|
||||||
|
processClass(dexNode, cls);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear() {
|
||||||
|
deobfPresets.clear();
|
||||||
|
clsMap.clear();
|
||||||
|
fldMap.clear();
|
||||||
|
mthMap.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processClass(DexNode dex, ClassNode cls) {
|
||||||
|
ClassInfo clsInfo = cls.getClassInfo();
|
||||||
|
String fullName = getClassFullName(clsInfo);
|
||||||
|
if (!fullName.equals(clsInfo.getFullName())) {
|
||||||
|
clsInfo.rename(dex, fullName);
|
||||||
|
}
|
||||||
|
for (FieldNode field : cls.getFields()) {
|
||||||
|
FieldInfo fieldInfo = field.getFieldInfo();
|
||||||
|
String alias = getFieldAlias(field);
|
||||||
|
if (alias != null) {
|
||||||
|
fieldInfo.setAlias(alias);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (MethodNode mth : cls.getMethods()) {
|
||||||
|
MethodInfo methodInfo = mth.getMethodInfo();
|
||||||
|
String alias = getMethodAlias(mth);
|
||||||
|
if (alias != null) {
|
||||||
|
methodInfo.setAlias(alias);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addPackagePreset(String origPkgName, String pkgAlias) {
|
||||||
|
PackageNode pkg = getPackageNode(origPkgName, true);
|
||||||
|
pkg.setAlias(pkgAlias);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets package node for full package name
|
||||||
|
*
|
||||||
|
* @param fullPkgName full package name
|
||||||
|
* @param create if {@code true} then will create all absent objects
|
||||||
|
* @return package node object or {@code null} if no package found and <b>create</b> set to {@code false}
|
||||||
|
*/
|
||||||
|
private PackageNode getPackageNode(String fullPkgName, boolean create) {
|
||||||
|
if (fullPkgName.isEmpty() || fullPkgName.equals(CLASS_NAME_SEPARATOR)) {
|
||||||
|
return rootPackage;
|
||||||
|
}
|
||||||
|
PackageNode result = rootPackage;
|
||||||
|
PackageNode parentNode;
|
||||||
|
do {
|
||||||
|
String pkgName;
|
||||||
|
int idx = fullPkgName.indexOf(CLASS_NAME_SEPARATOR);
|
||||||
|
|
||||||
|
if (idx > -1) {
|
||||||
|
pkgName = fullPkgName.substring(0, idx);
|
||||||
|
fullPkgName = fullPkgName.substring(idx + 1);
|
||||||
|
} else {
|
||||||
|
pkgName = fullPkgName;
|
||||||
|
fullPkgName = "";
|
||||||
|
}
|
||||||
|
parentNode = result;
|
||||||
|
result = result.getInnerPackageByName(pkgName);
|
||||||
|
if (result == null && create) {
|
||||||
|
result = new PackageNode(pkgName);
|
||||||
|
parentNode.addInnerPackage(result);
|
||||||
|
}
|
||||||
|
} while (!fullPkgName.isEmpty() && result != null);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
String getNameWithoutPackage(ClassInfo clsInfo) {
|
||||||
|
String prefix;
|
||||||
|
ClassInfo parentClsInfo = clsInfo.getParentClass();
|
||||||
|
if (parentClsInfo != null) {
|
||||||
|
DeobfClsInfo parentDeobfClsInfo = clsMap.get(parentClsInfo);
|
||||||
|
if (parentDeobfClsInfo != null) {
|
||||||
|
prefix = parentDeobfClsInfo.makeNameWithoutPkg();
|
||||||
|
} else {
|
||||||
|
prefix = getNameWithoutPackage(parentClsInfo);
|
||||||
|
}
|
||||||
|
prefix += INNER_CLASS_SEPARATOR;
|
||||||
|
} else {
|
||||||
|
prefix = "";
|
||||||
|
}
|
||||||
|
return prefix + clsInfo.getShortName();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doClass(ClassNode cls) {
|
||||||
|
ClassInfo classInfo = cls.getClassInfo();
|
||||||
|
String pkgFullName = classInfo.getPackage();
|
||||||
|
PackageNode pkg = getPackageNode(pkgFullName, true);
|
||||||
|
doPkg(pkg, pkgFullName);
|
||||||
|
|
||||||
|
String alias = deobfPresets.getForCls(classInfo);
|
||||||
|
if (alias != null) {
|
||||||
|
clsMap.put(classInfo, new DeobfClsInfo(this, cls, pkg, alias));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (clsMap.containsKey(classInfo)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (shouldRename(classInfo.getShortName())) {
|
||||||
|
makeClsAlias(cls);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getClsAlias(ClassNode cls) {
|
||||||
|
DeobfClsInfo deobfClsInfo = clsMap.get(cls.getClassInfo());
|
||||||
|
if (deobfClsInfo != null) {
|
||||||
|
return deobfClsInfo.getAlias();
|
||||||
|
}
|
||||||
|
return makeClsAlias(cls);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String makeClsAlias(ClassNode cls) {
|
||||||
|
ClassInfo classInfo = cls.getClassInfo();
|
||||||
|
String alias = getAliasFromSourceFile(cls);
|
||||||
|
if (alias == null) {
|
||||||
|
String clsName = classInfo.getShortName();
|
||||||
|
alias = String.format("C%04d%s", clsIndex++, makeName(clsName));
|
||||||
|
}
|
||||||
|
PackageNode pkg = getPackageNode(classInfo.getPackage(), true);
|
||||||
|
clsMap.put(classInfo, new DeobfClsInfo(this, cls, pkg, alias));
|
||||||
|
return alias;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private String getAliasFromSourceFile(ClassNode cls) {
|
||||||
|
SourceFileAttr sourceFileAttr = cls.get(AType.SOURCE_FILE);
|
||||||
|
if (sourceFileAttr == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String name = sourceFileAttr.getFileName();
|
||||||
|
if (name.endsWith(".java")) {
|
||||||
|
name = name.substring(0, name.length() - ".java".length());
|
||||||
|
}
|
||||||
|
if (NameMapper.isValidIdentifier(name)
|
||||||
|
&& !NameMapper.isReserved(name)) {
|
||||||
|
// TODO: check if no class with this name exists or already renamed
|
||||||
|
cls.remove(AType.SOURCE_FILE);
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String getFieldAlias(FieldNode field) {
|
||||||
|
FieldInfo fieldInfo = field.getFieldInfo();
|
||||||
|
String alias = fldMap.get(fieldInfo);
|
||||||
|
if (alias != null) {
|
||||||
|
return alias;
|
||||||
|
}
|
||||||
|
alias = deobfPresets.getForFld(fieldInfo);
|
||||||
|
if (alias != null) {
|
||||||
|
fldMap.put(fieldInfo, alias);
|
||||||
|
return alias;
|
||||||
|
}
|
||||||
|
if (shouldRename(field.getName())) {
|
||||||
|
return makeFieldAlias(field);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String getMethodAlias(MethodNode mth) {
|
||||||
|
MethodInfo methodInfo = mth.getMethodInfo();
|
||||||
|
String alias = mthMap.get(methodInfo);
|
||||||
|
if (alias != null) {
|
||||||
|
return alias;
|
||||||
|
}
|
||||||
|
alias = deobfPresets.getForMth(methodInfo);
|
||||||
|
if (alias != null) {
|
||||||
|
mthMap.put(methodInfo, alias);
|
||||||
|
return alias;
|
||||||
|
}
|
||||||
|
if (shouldRename(mth.getName())) {
|
||||||
|
return makeMethodAlias(mth);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String makeFieldAlias(FieldNode field) {
|
||||||
|
String alias = String.format("f%d%s", fldIndex++, makeName(field.getName()));
|
||||||
|
fldMap.put(field.getFieldInfo(), alias);
|
||||||
|
return alias;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String makeMethodAlias(MethodNode mth) {
|
||||||
|
String alias = String.format("m%d%s", mthIndex++, makeName(mth.getName()));
|
||||||
|
mthMap.put(mth.getMethodInfo(), alias);
|
||||||
|
return alias;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doPkg(PackageNode pkg, String fullName) {
|
||||||
|
if (pkgSet.contains(fullName)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pkgSet.add(fullName);
|
||||||
|
|
||||||
|
// doPkg for all parent packages except root that not hasAliases
|
||||||
|
PackageNode parentPkg = pkg.getParentPackage();
|
||||||
|
while (!parentPkg.getName().isEmpty()) {
|
||||||
|
if (!parentPkg.hasAlias()) {
|
||||||
|
doPkg(parentPkg, parentPkg.getFullName());
|
||||||
|
}
|
||||||
|
parentPkg = parentPkg.getParentPackage();
|
||||||
|
}
|
||||||
|
|
||||||
|
final String pkgName = pkg.getName();
|
||||||
|
if (!pkg.hasAlias() && shouldRename(pkgName)) {
|
||||||
|
final String pkgAlias = String.format("p%03d%s", pkgIndex++, makeName(pkgName));
|
||||||
|
pkg.setAlias(pkgAlias);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean shouldRename(String s) {
|
||||||
|
return s.length() > maxLength
|
||||||
|
|| s.length() < minLength
|
||||||
|
|| NameMapper.isReserved(s)
|
||||||
|
|| !NameMapper.isAllCharsPrintable(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String makeName(String name) {
|
||||||
|
if (name.length() > maxLength) {
|
||||||
|
return "x" + Integer.toHexString(name.hashCode());
|
||||||
|
}
|
||||||
|
if (NameMapper.isReserved(name)) {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
if (!NameMapper.isAllCharsPrintable(name)) {
|
||||||
|
return removeInvalidChars(name);
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String removeInvalidChars(String name) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (int i = 0; i < name.length(); i++) {
|
||||||
|
int ch = name.charAt(i);
|
||||||
|
if (NameMapper.isPrintableChar(ch)) {
|
||||||
|
sb.append((char) ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void dumpClassAlias(ClassNode cls) {
|
||||||
|
PackageNode pkg = getPackageNode(cls.getPackage(), false);
|
||||||
|
|
||||||
|
if (pkg != null) {
|
||||||
|
if (!cls.getFullName().equals(getClassFullName(cls))) {
|
||||||
|
LOG.info("Alias name for class '{}' is '{}'", cls.getFullName(), getClassFullName(cls));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LOG.error("Can't find package node for '{}'", cls.getPackage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void dumpAlias() {
|
||||||
|
for (DexNode dexNode : dexNodes) {
|
||||||
|
for (ClassNode cls : dexNode.getClasses()) {
|
||||||
|
dumpClassAlias(cls);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getPackageName(String packageName) {
|
||||||
|
final PackageNode pkg = getPackageNode(packageName, false);
|
||||||
|
if (pkg != null) {
|
||||||
|
return pkg.getFullAlias();
|
||||||
|
}
|
||||||
|
return packageName;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getClassName(ClassInfo clsInfo) {
|
||||||
|
final DeobfClsInfo deobfClsInfo = clsMap.get(clsInfo);
|
||||||
|
if (deobfClsInfo != null) {
|
||||||
|
return deobfClsInfo.makeNameWithoutPkg();
|
||||||
|
}
|
||||||
|
return getNameWithoutPackage(clsInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getClassFullName(ClassNode cls) {
|
||||||
|
return getClassFullName(cls.getClassInfo());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getClassFullName(ClassInfo clsInfo) {
|
||||||
|
final DeobfClsInfo deobfClsInfo = clsMap.get(clsInfo);
|
||||||
|
if (deobfClsInfo != null) {
|
||||||
|
return deobfClsInfo.getFullName();
|
||||||
|
}
|
||||||
|
return getPackageName(clsInfo.getPackage()) + CLASS_NAME_SEPARATOR + getClassName(clsInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<ClassInfo, DeobfClsInfo> getClsMap() {
|
||||||
|
return clsMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<FieldInfo, String> getFldMap() {
|
||||||
|
return fldMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<MethodInfo, String> getMthMap() {
|
||||||
|
return mthMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PackageNode getRootPackage() {
|
||||||
|
return rootPackage;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
package jadx.core.deobf;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class NameMapper {
|
||||||
|
|
||||||
|
private static final Pattern VALID_JAVA_IDENTIFIER = Pattern.compile(
|
||||||
|
"\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*");
|
||||||
|
|
||||||
|
private static final Pattern VALID_JAVA_FULL_IDENTIFIER = Pattern.compile(
|
||||||
|
"(" + VALID_JAVA_IDENTIFIER + "\\.)*" + VALID_JAVA_IDENTIFIER);
|
||||||
|
|
||||||
|
private static final Set<String> RESERVED_NAMES = new HashSet<String>(
|
||||||
|
Arrays.asList(new String[]{
|
||||||
|
"abstract",
|
||||||
|
"assert",
|
||||||
|
"boolean",
|
||||||
|
"break",
|
||||||
|
"byte",
|
||||||
|
"case",
|
||||||
|
"catch",
|
||||||
|
"char",
|
||||||
|
"class",
|
||||||
|
"const",
|
||||||
|
"continue",
|
||||||
|
"default",
|
||||||
|
"do",
|
||||||
|
"double",
|
||||||
|
"else",
|
||||||
|
"enum",
|
||||||
|
"extends",
|
||||||
|
"false",
|
||||||
|
"final",
|
||||||
|
"finally",
|
||||||
|
"float",
|
||||||
|
"for",
|
||||||
|
"goto",
|
||||||
|
"if",
|
||||||
|
"implements",
|
||||||
|
"import",
|
||||||
|
"instanceof",
|
||||||
|
"int",
|
||||||
|
"interface",
|
||||||
|
"long",
|
||||||
|
"native",
|
||||||
|
"new",
|
||||||
|
"null",
|
||||||
|
"package",
|
||||||
|
"private",
|
||||||
|
"protected",
|
||||||
|
"public",
|
||||||
|
"return",
|
||||||
|
"short",
|
||||||
|
"static",
|
||||||
|
"strictfp",
|
||||||
|
"super",
|
||||||
|
"switch",
|
||||||
|
"synchronized",
|
||||||
|
"this",
|
||||||
|
"throw",
|
||||||
|
"throws",
|
||||||
|
"transient",
|
||||||
|
"true",
|
||||||
|
"try",
|
||||||
|
"void",
|
||||||
|
"volatile",
|
||||||
|
"while",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
public static boolean isReserved(String str) {
|
||||||
|
return RESERVED_NAMES.contains(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isValidIdentifier(String str) {
|
||||||
|
return VALID_JAVA_IDENTIFIER.matcher(str).matches() && isAllCharsPrintable(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isValidFullIdentifier(String str) {
|
||||||
|
return VALID_JAVA_FULL_IDENTIFIER.matcher(str).matches() && isAllCharsPrintable(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isPrintableChar(int c) {
|
||||||
|
return 32 <= c && c <= 126;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isAllCharsPrintable(String str) {
|
||||||
|
int len = str.length();
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
if (!isPrintableChar(str.charAt(i))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
package jadx.core.deobf;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Stack;
|
||||||
|
|
||||||
|
public class PackageNode {
|
||||||
|
|
||||||
|
private static final char SEPARATOR_CHAR = '.';
|
||||||
|
|
||||||
|
private PackageNode parentPackage;
|
||||||
|
private List<PackageNode> innerPackages = Collections.emptyList();
|
||||||
|
|
||||||
|
private final String packageName;
|
||||||
|
private String packageAlias;
|
||||||
|
|
||||||
|
private String cachedPackageFullName;
|
||||||
|
private String cachedPackageFullAlias;
|
||||||
|
|
||||||
|
public PackageNode(String packageName) {
|
||||||
|
this.packageName = packageName;
|
||||||
|
this.parentPackage = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return packageName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFullName() {
|
||||||
|
if (cachedPackageFullName == null) {
|
||||||
|
Stack<PackageNode> pp = getParentPackages();
|
||||||
|
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
result.append(pp.pop().getName());
|
||||||
|
while (pp.size() > 0) {
|
||||||
|
result.append(SEPARATOR_CHAR);
|
||||||
|
result.append(pp.pop().getName());
|
||||||
|
}
|
||||||
|
cachedPackageFullName = result.toString();
|
||||||
|
}
|
||||||
|
return cachedPackageFullName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAlias() {
|
||||||
|
if (packageAlias != null) {
|
||||||
|
return packageAlias;
|
||||||
|
}
|
||||||
|
return packageName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAlias(String alias) {
|
||||||
|
packageAlias = alias;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasAlias() {
|
||||||
|
return packageAlias != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFullAlias() {
|
||||||
|
if (cachedPackageFullAlias == null) {
|
||||||
|
Stack<PackageNode> pp = getParentPackages();
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
result.append(pp.pop().getAlias());
|
||||||
|
while (pp.size() > 0) {
|
||||||
|
result.append(SEPARATOR_CHAR);
|
||||||
|
result.append(pp.pop().getAlias());
|
||||||
|
}
|
||||||
|
cachedPackageFullAlias = result.toString();
|
||||||
|
}
|
||||||
|
return cachedPackageFullAlias;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PackageNode getParentPackage() {
|
||||||
|
return parentPackage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<PackageNode> getInnerPackages() {
|
||||||
|
return innerPackages;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addInnerPackage(PackageNode pkg) {
|
||||||
|
if (innerPackages.isEmpty()) {
|
||||||
|
innerPackages = new ArrayList<PackageNode>();
|
||||||
|
}
|
||||||
|
innerPackages.add(pkg);
|
||||||
|
pkg.parentPackage = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets inner package node by name
|
||||||
|
*
|
||||||
|
* @param name inner package name
|
||||||
|
* @return package node or {@code null}
|
||||||
|
*/
|
||||||
|
public PackageNode getInnerPackageByName(String name) {
|
||||||
|
PackageNode result = null;
|
||||||
|
for (PackageNode p : innerPackages) {
|
||||||
|
if (p.getName().equals(name)) {
|
||||||
|
result = p;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fills stack with parent packages exclude root node
|
||||||
|
*
|
||||||
|
* @return stack with parent packages
|
||||||
|
*/
|
||||||
|
private Stack<PackageNode> getParentPackages() {
|
||||||
|
Stack<PackageNode> pp = new Stack<PackageNode>();
|
||||||
|
|
||||||
|
PackageNode currentP = this;
|
||||||
|
PackageNode parentP = currentP.getParentPackage();
|
||||||
|
|
||||||
|
while (currentP != parentP) {
|
||||||
|
pp.push(currentP);
|
||||||
|
currentP = parentP;
|
||||||
|
parentP = currentP.getParentPackage();
|
||||||
|
}
|
||||||
|
return pp;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package jadx.core.dex.attributes;
|
||||||
|
|
||||||
|
public enum AFlag {
|
||||||
|
TRY_ENTER,
|
||||||
|
TRY_LEAVE,
|
||||||
|
|
||||||
|
LOOP_START,
|
||||||
|
LOOP_END,
|
||||||
|
|
||||||
|
SYNTHETIC,
|
||||||
|
|
||||||
|
RETURN, // block contains only return instruction
|
||||||
|
ORIG_RETURN,
|
||||||
|
|
||||||
|
DECLARE_VAR,
|
||||||
|
DONT_WRAP,
|
||||||
|
|
||||||
|
DONT_SHRINK,
|
||||||
|
DONT_INLINE,
|
||||||
|
DONT_GENERATE,
|
||||||
|
SKIP,
|
||||||
|
REMOVE,
|
||||||
|
|
||||||
|
SKIP_FIRST_ARG,
|
||||||
|
ANONYMOUS_CONSTRUCTOR,
|
||||||
|
ANONYMOUS_CLASS,
|
||||||
|
|
||||||
|
ELSE_IF_CHAIN,
|
||||||
|
|
||||||
|
WRAPPED,
|
||||||
|
ARITH_ONEARG,
|
||||||
|
|
||||||
|
FALL_THROUGH,
|
||||||
|
|
||||||
|
INCONSISTENT_CODE, // warning about incorrect decompilation
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package jadx.core.dex.attributes;
|
||||||
|
|
||||||
|
import jadx.core.dex.attributes.annotations.AnnotationsList;
|
||||||
|
import jadx.core.dex.attributes.annotations.MethodParameters;
|
||||||
|
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
|
||||||
|
import jadx.core.dex.attributes.nodes.EnumClassAttr;
|
||||||
|
import jadx.core.dex.attributes.nodes.EnumMapAttr;
|
||||||
|
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
|
||||||
|
import jadx.core.dex.attributes.nodes.ForceReturnAttr;
|
||||||
|
import jadx.core.dex.attributes.nodes.IgnoreEdgeAttr;
|
||||||
|
import jadx.core.dex.attributes.nodes.JadxErrorAttr;
|
||||||
|
import jadx.core.dex.attributes.nodes.JumpInfo;
|
||||||
|
import jadx.core.dex.attributes.nodes.LoopInfo;
|
||||||
|
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
||||||
|
import jadx.core.dex.attributes.nodes.MethodInlineAttr;
|
||||||
|
import jadx.core.dex.attributes.nodes.PhiListAttr;
|
||||||
|
import jadx.core.dex.attributes.nodes.SourceFileAttr;
|
||||||
|
import jadx.core.dex.nodes.parser.FieldValueAttr;
|
||||||
|
import jadx.core.dex.trycatch.CatchAttr;
|
||||||
|
import jadx.core.dex.trycatch.ExcHandlerAttr;
|
||||||
|
import jadx.core.dex.trycatch.SplitterBlockAttr;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attribute types enumeration,
|
||||||
|
* uses generic type for omit cast after 'AttributeStorage.get' method
|
||||||
|
*
|
||||||
|
* @param <T> attribute class implementation
|
||||||
|
*/
|
||||||
|
public class AType<T extends IAttribute> {
|
||||||
|
|
||||||
|
public static final AType<AttrList<JumpInfo>> JUMP = new AType<AttrList<JumpInfo>>();
|
||||||
|
public static final AType<AttrList<LoopInfo>> LOOP = new AType<AttrList<LoopInfo>>();
|
||||||
|
|
||||||
|
public static final AType<ExcHandlerAttr> EXC_HANDLER = new AType<ExcHandlerAttr>();
|
||||||
|
public static final AType<CatchAttr> CATCH_BLOCK = new AType<CatchAttr>();
|
||||||
|
public static final AType<SplitterBlockAttr> SPLITTER_BLOCK = new AType<SplitterBlockAttr>();
|
||||||
|
public static final AType<ForceReturnAttr> FORCE_RETURN = new AType<ForceReturnAttr>();
|
||||||
|
public static final AType<FieldValueAttr> FIELD_VALUE = new AType<FieldValueAttr>();
|
||||||
|
public static final AType<FieldReplaceAttr> FIELD_REPLACE = new AType<FieldReplaceAttr>();
|
||||||
|
public static final AType<JadxErrorAttr> JADX_ERROR = new AType<JadxErrorAttr>();
|
||||||
|
public static final AType<MethodInlineAttr> METHOD_INLINE = new AType<MethodInlineAttr>();
|
||||||
|
public static final AType<EnumClassAttr> ENUM_CLASS = new AType<EnumClassAttr>();
|
||||||
|
public static final AType<EnumMapAttr> ENUM_MAP = new AType<EnumMapAttr>();
|
||||||
|
public static final AType<AnnotationsList> ANNOTATION_LIST = new AType<AnnotationsList>();
|
||||||
|
public static final AType<MethodParameters> ANNOTATION_MTH_PARAMETERS = new AType<MethodParameters>();
|
||||||
|
public static final AType<PhiListAttr> PHI_LIST = new AType<PhiListAttr>();
|
||||||
|
public static final AType<SourceFileAttr> SOURCE_FILE = new AType<SourceFileAttr>();
|
||||||
|
public static final AType<DeclareVariablesAttr> DECLARE_VARIABLES = new AType<DeclareVariablesAttr>();
|
||||||
|
public static final AType<LoopLabelAttr> LOOP_LABEL = new AType<LoopLabelAttr>();
|
||||||
|
public static final AType<IgnoreEdgeAttr> IGNORE_EDGE = new AType<IgnoreEdgeAttr>();
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package jadx.core.dex.attributes;
|
||||||
|
|
||||||
|
import jadx.core.utils.Utils;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class AttrList<T> implements IAttribute {
|
||||||
|
|
||||||
|
private final AType<AttrList<T>> type;
|
||||||
|
private final List<T> list = new LinkedList<T>();
|
||||||
|
|
||||||
|
public AttrList(AType<AttrList<T>> type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<T> getList() {
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AType<AttrList<T>> getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return Utils.listToString(list);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
package jadx.core.dex.attributes;
|
||||||
|
|
||||||
|
import jadx.core.dex.attributes.annotations.Annotation;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public abstract class AttrNode implements IAttributeNode {
|
||||||
|
|
||||||
|
private static final AttributeStorage EMPTY_ATTR_STORAGE = new EmptyAttrStorage();
|
||||||
|
|
||||||
|
private AttributeStorage storage = EMPTY_ATTR_STORAGE;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void add(AFlag flag) {
|
||||||
|
initStorage().add(flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addAttr(IAttribute attr) {
|
||||||
|
initStorage().add(attr);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> void addAttr(AType<AttrList<T>> type, T obj) {
|
||||||
|
initStorage().add(type, obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void copyAttributesFrom(AttrNode attrNode) {
|
||||||
|
initStorage().addAll(attrNode.storage);
|
||||||
|
}
|
||||||
|
|
||||||
|
AttributeStorage initStorage() {
|
||||||
|
AttributeStorage store = storage;
|
||||||
|
if (store == EMPTY_ATTR_STORAGE) {
|
||||||
|
store = new AttributeStorage();
|
||||||
|
storage = store;
|
||||||
|
}
|
||||||
|
return store;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean contains(AFlag flag) {
|
||||||
|
return storage.contains(flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T extends IAttribute> boolean contains(AType<T> type) {
|
||||||
|
return storage.contains(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T extends IAttribute> T get(AType<T> type) {
|
||||||
|
return storage.get(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Annotation getAnnotation(String cls) {
|
||||||
|
return storage.getAnnotation(cls);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> List<T> getAll(AType<AttrList<T>> type) {
|
||||||
|
return storage.getAll(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove(AFlag flag) {
|
||||||
|
storage.remove(flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T extends IAttribute> void remove(AType<T> type) {
|
||||||
|
storage.remove(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeAttr(IAttribute attr) {
|
||||||
|
storage.remove(attr);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearAttributes() {
|
||||||
|
storage.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getAttributesStringsList() {
|
||||||
|
return storage.getAttributeStrings();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAttributesString() {
|
||||||
|
return storage.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
package jadx.core.dex.attributes;
|
||||||
|
|
||||||
|
import jadx.core.dex.attributes.annotations.Annotation;
|
||||||
|
import jadx.core.dex.attributes.annotations.AnnotationsList;
|
||||||
|
import jadx.core.utils.Utils;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.IdentityHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Storage for different attribute types:
|
||||||
|
* 1. flags - boolean attribute (set or not)
|
||||||
|
* 2. attribute - class instance associated with attribute type.
|
||||||
|
*/
|
||||||
|
public class AttributeStorage {
|
||||||
|
|
||||||
|
private final Set<AFlag> flags;
|
||||||
|
private final Map<AType<?>, IAttribute> attributes;
|
||||||
|
|
||||||
|
public AttributeStorage() {
|
||||||
|
flags = EnumSet.noneOf(AFlag.class);
|
||||||
|
attributes = new IdentityHashMap<AType<?>, IAttribute>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(AFlag flag) {
|
||||||
|
flags.add(flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(IAttribute attr) {
|
||||||
|
attributes.put(attr.getType(), attr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> void add(AType<AttrList<T>> type, T obj) {
|
||||||
|
AttrList<T> list = get(type);
|
||||||
|
if (list == null) {
|
||||||
|
list = new AttrList<T>(type);
|
||||||
|
add(list);
|
||||||
|
}
|
||||||
|
list.getList().add(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addAll(AttributeStorage otherList) {
|
||||||
|
flags.addAll(otherList.flags);
|
||||||
|
attributes.putAll(otherList.attributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean contains(AFlag flag) {
|
||||||
|
return flags.contains(flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T extends IAttribute> boolean contains(AType<T> type) {
|
||||||
|
return attributes.containsKey(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T extends IAttribute> T get(AType<T> type) {
|
||||||
|
return (T) attributes.get(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Annotation getAnnotation(String cls) {
|
||||||
|
AnnotationsList aList = get(AType.ANNOTATION_LIST);
|
||||||
|
return aList == null ? null : aList.get(cls);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> List<T> getAll(AType<AttrList<T>> type) {
|
||||||
|
AttrList<T> attrList = get(type);
|
||||||
|
if (attrList == null) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
return Collections.unmodifiableList(attrList.getList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void remove(AFlag flag) {
|
||||||
|
flags.remove(flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T extends IAttribute> void remove(AType<T> type) {
|
||||||
|
attributes.remove(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void remove(IAttribute attr) {
|
||||||
|
AType<?> type = attr.getType();
|
||||||
|
IAttribute a = attributes.get(type);
|
||||||
|
if (a == attr) {
|
||||||
|
attributes.remove(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
flags.clear();
|
||||||
|
attributes.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getAttributeStrings() {
|
||||||
|
int size = flags.size() + attributes.size() + attributes.size();
|
||||||
|
if (size == 0) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
List<String> list = new ArrayList<String>(size);
|
||||||
|
for (AFlag a : flags) {
|
||||||
|
list.add(a.toString());
|
||||||
|
}
|
||||||
|
for (IAttribute a : attributes.values()) {
|
||||||
|
list.add(a.toString());
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
List<String> list = getAttributeStrings();
|
||||||
|
if (list.isEmpty()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return "A:{" + Utils.listToString(list) + "}";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package jadx.core.dex.attributes;
|
||||||
|
|
||||||
|
import jadx.core.dex.attributes.annotations.Annotation;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public final class EmptyAttrStorage extends AttributeStorage {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean contains(AFlag flag) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T extends IAttribute> boolean contains(AType<T> type) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T extends IAttribute> T get(AType<T> type) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Annotation getAnnotation(String cls) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> List<T> getAll(AType<AttrList<T>> type) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove(AFlag flag) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T extends IAttribute> void remove(AType<T> type) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove(IAttribute attr) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getAttributeStrings() {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package jadx.core.dex.attributes;
|
||||||
|
|
||||||
|
public interface IAttribute {
|
||||||
|
|
||||||
|
AType<?> getType();
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package jadx.core.dex.attributes;
|
||||||
|
|
||||||
|
import jadx.core.dex.attributes.annotations.Annotation;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface IAttributeNode {
|
||||||
|
|
||||||
|
void add(AFlag flag);
|
||||||
|
|
||||||
|
void addAttr(IAttribute attr);
|
||||||
|
|
||||||
|
<T> void addAttr(AType<AttrList<T>> type, T obj);
|
||||||
|
|
||||||
|
void copyAttributesFrom(AttrNode attrNode);
|
||||||
|
|
||||||
|
boolean contains(AFlag flag);
|
||||||
|
|
||||||
|
<T extends IAttribute> boolean contains(AType<T> type);
|
||||||
|
|
||||||
|
<T extends IAttribute> T get(AType<T> type);
|
||||||
|
|
||||||
|
Annotation getAnnotation(String cls);
|
||||||
|
|
||||||
|
<T> List<T> getAll(AType<AttrList<T>> type);
|
||||||
|
|
||||||
|
void remove(AFlag flag);
|
||||||
|
|
||||||
|
<T extends IAttribute> void remove(AType<T> type);
|
||||||
|
|
||||||
|
void removeAttr(IAttribute attr);
|
||||||
|
|
||||||
|
void clearAttributes();
|
||||||
|
|
||||||
|
List<String> getAttributesStringsList();
|
||||||
|
|
||||||
|
String getAttributesString();
|
||||||
|
}
|
||||||
+7
-3
@@ -1,12 +1,12 @@
|
|||||||
package jadx.dex.attributes.annotations;
|
package jadx.core.dex.attributes.annotations;
|
||||||
|
|
||||||
import jadx.dex.instructions.args.ArgType;
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class Annotation {
|
public class Annotation {
|
||||||
|
|
||||||
public static enum Visibility {
|
public enum Visibility {
|
||||||
BUILD, RUNTIME, SYSTEM
|
BUILD, RUNTIME, SYSTEM
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,6 +36,10 @@ public class Annotation {
|
|||||||
return values;
|
return values;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Object getDefaultValue() {
|
||||||
|
return values.get("value");
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "Annotation[" + visibility + ", " + atype + ", " + values + "]";
|
return "Annotation[" + visibility + ", " + atype + ", " + values + "]";
|
||||||
+13
-6
@@ -1,16 +1,19 @@
|
|||||||
package jadx.dex.attributes.annotations;
|
package jadx.core.dex.attributes.annotations;
|
||||||
|
|
||||||
import jadx.dex.attributes.AttributeType;
|
import jadx.core.dex.attributes.AType;
|
||||||
import jadx.dex.attributes.IAttribute;
|
import jadx.core.dex.attributes.IAttribute;
|
||||||
import jadx.utils.Utils;
|
import jadx.core.utils.Utils;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class AnnotationsList implements IAttribute {
|
public class AnnotationsList implements IAttribute {
|
||||||
|
|
||||||
|
public static final AnnotationsList EMPTY = new AnnotationsList(Collections.<Annotation>emptyList());
|
||||||
|
|
||||||
private final Map<String, Annotation> map;
|
private final Map<String, Annotation> map;
|
||||||
|
|
||||||
public AnnotationsList(List<Annotation> anList) {
|
public AnnotationsList(List<Annotation> anList) {
|
||||||
@@ -32,9 +35,13 @@ public class AnnotationsList implements IAttribute {
|
|||||||
return map.size();
|
return map.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return map.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AttributeType getType() {
|
public AType<AnnotationsList> getType() {
|
||||||
return AttributeType.ANNOTATION_LIST;
|
return AType.ANNOTATION_LIST;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
+6
-6
@@ -1,8 +1,8 @@
|
|||||||
package jadx.dex.attributes.annotations;
|
package jadx.core.dex.attributes.annotations;
|
||||||
|
|
||||||
import jadx.dex.attributes.AttributeType;
|
import jadx.core.dex.attributes.AType;
|
||||||
import jadx.dex.attributes.IAttribute;
|
import jadx.core.dex.attributes.IAttribute;
|
||||||
import jadx.utils.Utils;
|
import jadx.core.utils.Utils;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -20,8 +20,8 @@ public class MethodParameters implements IAttribute {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AttributeType getType() {
|
public AType<MethodParameters> getType() {
|
||||||
return AttributeType.ANNOTATION_MTH_PARAMETERS;
|
return AType.ANNOTATION_MTH_PARAMETERS;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package jadx.core.dex.attributes.nodes;
|
||||||
|
|
||||||
|
import jadx.core.dex.attributes.AType;
|
||||||
|
import jadx.core.dex.attributes.IAttribute;
|
||||||
|
import jadx.core.dex.instructions.args.RegisterArg;
|
||||||
|
import jadx.core.utils.Utils;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of variables to be declared at region start.
|
||||||
|
*/
|
||||||
|
public class DeclareVariablesAttr implements IAttribute {
|
||||||
|
|
||||||
|
private final List<RegisterArg> vars = new LinkedList<RegisterArg>();
|
||||||
|
|
||||||
|
public Iterable<RegisterArg> getVars() {
|
||||||
|
return vars;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addVar(RegisterArg arg) {
|
||||||
|
vars.add(arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AType<DeclareVariablesAttr> getType() {
|
||||||
|
return AType.DECLARE_VARIABLES;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "DECL_VAR: " + Utils.listToString(vars);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
package jadx.core.dex.attributes.nodes;
|
||||||
|
|
||||||
|
import jadx.core.dex.attributes.AType;
|
||||||
|
import jadx.core.dex.attributes.IAttribute;
|
||||||
|
import jadx.core.dex.info.FieldInfo;
|
||||||
|
import jadx.core.dex.instructions.mods.ConstructorInsn;
|
||||||
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class EnumClassAttr implements IAttribute {
|
||||||
|
|
||||||
|
public static class EnumField {
|
||||||
|
private final FieldInfo field;
|
||||||
|
private final ConstructorInsn constrInsn;
|
||||||
|
private final int startArg;
|
||||||
|
private ClassNode cls;
|
||||||
|
|
||||||
|
public EnumField(FieldInfo field, ConstructorInsn co, int startArg) {
|
||||||
|
this.field = field;
|
||||||
|
this.constrInsn = co;
|
||||||
|
this.startArg = startArg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FieldInfo getField() {
|
||||||
|
return field;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConstructorInsn getConstrInsn() {
|
||||||
|
return constrInsn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getStartArg() {
|
||||||
|
return startArg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClassNode getCls() {
|
||||||
|
return cls;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCls(ClassNode cls) {
|
||||||
|
this.cls = cls;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return field + "(" + constrInsn + ") " + cls;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final List<EnumField> fields;
|
||||||
|
private MethodNode staticMethod;
|
||||||
|
|
||||||
|
public EnumClassAttr(int fieldsCount) {
|
||||||
|
this.fields = new ArrayList<EnumField>(fieldsCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<EnumField> getFields() {
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MethodNode getStaticMethod() {
|
||||||
|
return staticMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStaticMethod(MethodNode staticMethod) {
|
||||||
|
this.staticMethod = staticMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AType<EnumClassAttr> getType() {
|
||||||
|
return AType.ENUM_CLASS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Enum fields: " + fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package jadx.core.dex.attributes.nodes;
|
||||||
|
|
||||||
|
import jadx.core.dex.attributes.AType;
|
||||||
|
import jadx.core.dex.attributes.IAttribute;
|
||||||
|
import jadx.core.dex.nodes.FieldNode;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class EnumMapAttr implements IAttribute {
|
||||||
|
|
||||||
|
public static class KeyValueMap {
|
||||||
|
private final Map<Object, Object> map = new HashMap<Object, Object>();
|
||||||
|
|
||||||
|
public Object get(Object key) {
|
||||||
|
return map.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
void put(Object key, Object value) {
|
||||||
|
map.put(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Map<FieldNode, KeyValueMap> fieldsMap = new HashMap<FieldNode, KeyValueMap>();
|
||||||
|
|
||||||
|
public KeyValueMap getMap(FieldNode field) {
|
||||||
|
return fieldsMap.get(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(FieldNode field, Object key, Object value) {
|
||||||
|
KeyValueMap map = getMap(field);
|
||||||
|
if (map == null) {
|
||||||
|
map = new KeyValueMap();
|
||||||
|
fieldsMap.put(field, map);
|
||||||
|
}
|
||||||
|
map.put(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AType<EnumMapAttr> getType() {
|
||||||
|
return AType.ENUM_MAP;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Enum fields map: " + fieldsMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package jadx.core.dex.attributes.nodes;
|
||||||
|
|
||||||
|
import jadx.core.dex.attributes.AType;
|
||||||
|
import jadx.core.dex.attributes.IAttribute;
|
||||||
|
import jadx.core.dex.info.FieldInfo;
|
||||||
|
|
||||||
|
public class FieldReplaceAttr implements IAttribute {
|
||||||
|
|
||||||
|
private final FieldInfo fieldInfo;
|
||||||
|
private final boolean isOuterClass;
|
||||||
|
|
||||||
|
public FieldReplaceAttr(FieldInfo fieldInfo, boolean isOuterClass) {
|
||||||
|
this.fieldInfo = fieldInfo;
|
||||||
|
this.isOuterClass = isOuterClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FieldInfo getFieldInfo() {
|
||||||
|
return fieldInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isOuterClass() {
|
||||||
|
return isOuterClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AType<FieldReplaceAttr> getType() {
|
||||||
|
return AType.FIELD_REPLACE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "REPLACE: " + fieldInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
+7
-5
@@ -1,7 +1,9 @@
|
|||||||
package jadx.dex.attributes;
|
package jadx.core.dex.attributes.nodes;
|
||||||
|
|
||||||
import jadx.dex.nodes.InsnNode;
|
import jadx.core.dex.attributes.AType;
|
||||||
import jadx.utils.Utils;
|
import jadx.core.dex.attributes.IAttribute;
|
||||||
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
|
import jadx.core.utils.Utils;
|
||||||
|
|
||||||
public class ForceReturnAttr implements IAttribute {
|
public class ForceReturnAttr implements IAttribute {
|
||||||
|
|
||||||
@@ -16,8 +18,8 @@ public class ForceReturnAttr implements IAttribute {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AttributeType getType() {
|
public AType<ForceReturnAttr> getType() {
|
||||||
return AttributeType.FORCE_RETURN;
|
return AType.FORCE_RETURN;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package jadx.core.dex.attributes.nodes;
|
||||||
|
|
||||||
|
import jadx.core.dex.attributes.AType;
|
||||||
|
import jadx.core.dex.attributes.IAttribute;
|
||||||
|
import jadx.core.dex.nodes.BlockNode;
|
||||||
|
import jadx.core.utils.Utils;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class IgnoreEdgeAttr implements IAttribute {
|
||||||
|
|
||||||
|
private final Set<BlockNode> blocks = new HashSet<BlockNode>(3);
|
||||||
|
|
||||||
|
public Set<BlockNode> getBlocks() {
|
||||||
|
return blocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean contains(BlockNode block) {
|
||||||
|
return blocks.contains(block);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AType<IgnoreEdgeAttr> getType() {
|
||||||
|
return AType.IGNORE_EDGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "IGNORE_EDGES: " + Utils.listToString(blocks);
|
||||||
|
}
|
||||||
|
}
|
||||||
+7
-5
@@ -1,6 +1,8 @@
|
|||||||
package jadx.dex.attributes;
|
package jadx.core.dex.attributes.nodes;
|
||||||
|
|
||||||
import jadx.utils.Utils;
|
import jadx.core.dex.attributes.AType;
|
||||||
|
import jadx.core.dex.attributes.IAttribute;
|
||||||
|
import jadx.core.utils.Utils;
|
||||||
|
|
||||||
public class JadxErrorAttr implements IAttribute {
|
public class JadxErrorAttr implements IAttribute {
|
||||||
|
|
||||||
@@ -15,8 +17,8 @@ public class JadxErrorAttr implements IAttribute {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AttributeType getType() {
|
public AType<JadxErrorAttr> getType() {
|
||||||
return AttributeType.JADX_ERROR;
|
return AType.JADX_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -26,7 +28,7 @@ public class JadxErrorAttr implements IAttribute {
|
|||||||
if (cause == null) {
|
if (cause == null) {
|
||||||
str.append("null");
|
str.append("null");
|
||||||
} else {
|
} else {
|
||||||
str.append(cause.getClass().toString());
|
str.append(cause.getClass());
|
||||||
str.append(":");
|
str.append(":");
|
||||||
str.append(cause.getMessage());
|
str.append(cause.getMessage());
|
||||||
str.append("\n");
|
str.append("\n");
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package jadx.core.dex.attributes.nodes;
|
||||||
|
|
||||||
|
import jadx.core.utils.InsnUtils;
|
||||||
|
|
||||||
|
public class JumpInfo {
|
||||||
|
|
||||||
|
private final int src;
|
||||||
|
private final int dest;
|
||||||
|
|
||||||
|
public JumpInfo(int src, int dest) {
|
||||||
|
this.src = src;
|
||||||
|
this.dest = dest;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSrc() {
|
||||||
|
return src;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getDest() {
|
||||||
|
return dest;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return 31 * dest + src;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (obj == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (getClass() != obj.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
JumpInfo other = (JumpInfo) obj;
|
||||||
|
return dest == other.dest && src == other.src;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "JUMP: " + InsnUtils.formatOffset(src) + " -> " + InsnUtils.formatOffset(dest);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package jadx.core.dex.attributes.nodes;
|
||||||
|
|
||||||
|
import jadx.core.dex.attributes.AttrNode;
|
||||||
|
|
||||||
|
public abstract class LineAttrNode extends AttrNode {
|
||||||
|
|
||||||
|
private int sourceLine;
|
||||||
|
|
||||||
|
private int decompiledLine;
|
||||||
|
|
||||||
|
public int getSourceLine() {
|
||||||
|
return sourceLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSourceLine(int sourceLine) {
|
||||||
|
this.sourceLine = sourceLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getDecompiledLine() {
|
||||||
|
return decompiledLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDecompiledLine(int decompiledLine) {
|
||||||
|
this.decompiledLine = decompiledLine;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
package jadx.core.dex.attributes.nodes;
|
||||||
|
|
||||||
|
import jadx.core.dex.attributes.AType;
|
||||||
|
import jadx.core.dex.nodes.BlockNode;
|
||||||
|
import jadx.core.dex.nodes.Edge;
|
||||||
|
import jadx.core.utils.BlockUtils;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class LoopInfo {
|
||||||
|
|
||||||
|
private final BlockNode start;
|
||||||
|
private final BlockNode end;
|
||||||
|
private final Set<BlockNode> loopBlocks;
|
||||||
|
|
||||||
|
private int id;
|
||||||
|
private LoopInfo parentLoop;
|
||||||
|
|
||||||
|
public LoopInfo(BlockNode start, BlockNode end) {
|
||||||
|
this.start = start;
|
||||||
|
this.end = end;
|
||||||
|
this.loopBlocks = Collections.unmodifiableSet(BlockUtils.getAllPathsBlocks(start, end));
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlockNode getStart() {
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlockNode getEnd() {
|
||||||
|
return end;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<BlockNode> getLoopBlocks() {
|
||||||
|
return loopBlocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return source blocks of exit edges. <br>
|
||||||
|
* Exit nodes belongs to loop (contains in {@code loopBlocks})
|
||||||
|
*/
|
||||||
|
public Set<BlockNode> getExitNodes() {
|
||||||
|
Set<BlockNode> nodes = new HashSet<BlockNode>();
|
||||||
|
Set<BlockNode> blocks = getLoopBlocks();
|
||||||
|
for (BlockNode block : blocks) {
|
||||||
|
// exit: successor node not from this loop, (don't change to getCleanSuccessors)
|
||||||
|
for (BlockNode s : block.getSuccessors()) {
|
||||||
|
if (!blocks.contains(s) && !s.contains(AType.EXC_HANDLER)) {
|
||||||
|
nodes.add(block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return loop exit edges.
|
||||||
|
*/
|
||||||
|
public List<Edge> getExitEdges() {
|
||||||
|
List<Edge> edges = new LinkedList<Edge>();
|
||||||
|
Set<BlockNode> blocks = getLoopBlocks();
|
||||||
|
for (BlockNode block : blocks) {
|
||||||
|
for (BlockNode s : block.getSuccessors()) {
|
||||||
|
if (!blocks.contains(s) && !s.contains(AType.EXC_HANDLER)) {
|
||||||
|
edges.add(new Edge(block, s));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return edges;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(int id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LoopInfo getParentLoop() {
|
||||||
|
return parentLoop;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setParentLoop(LoopInfo parentLoop) {
|
||||||
|
this.parentLoop = parentLoop;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "LOOP:" + id + ": " + start + "->" + end;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package jadx.core.dex.attributes.nodes;
|
||||||
|
|
||||||
|
import jadx.core.dex.attributes.AType;
|
||||||
|
import jadx.core.dex.attributes.IAttribute;
|
||||||
|
|
||||||
|
public class LoopLabelAttr implements IAttribute {
|
||||||
|
|
||||||
|
private final LoopInfo loop;
|
||||||
|
|
||||||
|
public LoopLabelAttr(LoopInfo loop) {
|
||||||
|
this.loop = loop;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LoopInfo getLoop() {
|
||||||
|
return loop;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AType<LoopLabelAttr> getType() {
|
||||||
|
return AType.LOOP_LABEL;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "LOOP_LABEL: " + loop;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package jadx.core.dex.attributes.nodes;
|
||||||
|
|
||||||
|
import jadx.core.dex.attributes.AType;
|
||||||
|
import jadx.core.dex.attributes.IAttribute;
|
||||||
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
|
|
||||||
|
public class MethodInlineAttr implements IAttribute {
|
||||||
|
|
||||||
|
private final InsnNode insn;
|
||||||
|
|
||||||
|
public MethodInlineAttr(InsnNode insn) {
|
||||||
|
this.insn = insn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public InsnNode getInsn() {
|
||||||
|
return insn;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AType<MethodInlineAttr> getType() {
|
||||||
|
return AType.METHOD_INLINE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "INLINE: " + insn;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package jadx.core.dex.attributes.nodes;
|
||||||
|
|
||||||
|
import jadx.core.dex.attributes.AType;
|
||||||
|
import jadx.core.dex.attributes.IAttribute;
|
||||||
|
import jadx.core.dex.instructions.PhiInsn;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class PhiListAttr implements IAttribute {
|
||||||
|
|
||||||
|
private final List<PhiInsn> list = new LinkedList<PhiInsn>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AType<PhiListAttr> getType() {
|
||||||
|
return AType.PHI_LIST;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<PhiInsn> getList() {
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("PHI: ");
|
||||||
|
for (PhiInsn phiInsn : list) {
|
||||||
|
sb.append('r').append(phiInsn.getResult().getRegNum()).append(" ");
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package jadx.core.dex.attributes.nodes;
|
||||||
|
|
||||||
|
import jadx.core.dex.attributes.AType;
|
||||||
|
import jadx.core.dex.attributes.IAttribute;
|
||||||
|
|
||||||
|
public class SourceFileAttr implements IAttribute {
|
||||||
|
|
||||||
|
private final String fileName;
|
||||||
|
|
||||||
|
public SourceFileAttr(String fileName) {
|
||||||
|
this.fileName = fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFileName() {
|
||||||
|
return fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AType<SourceFileAttr> getType() {
|
||||||
|
return AType.SOURCE_FILE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "SOURCE:" + fileName;
|
||||||
|
}
|
||||||
|
}
|
||||||
+85
-45
@@ -1,6 +1,6 @@
|
|||||||
package jadx.dex.info;
|
package jadx.core.dex.info;
|
||||||
|
|
||||||
import jadx.Consts;
|
import jadx.core.Consts;
|
||||||
|
|
||||||
import com.android.dx.rop.code.AccessFlags;
|
import com.android.dx.rop.code.AccessFlags;
|
||||||
|
|
||||||
@@ -8,7 +8,7 @@ public class AccessInfo {
|
|||||||
|
|
||||||
private final int accFlags;
|
private final int accFlags;
|
||||||
|
|
||||||
public static enum AFType {
|
public enum AFType {
|
||||||
CLASS, FIELD, METHOD
|
CLASS, FIELD, METHOD
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,19 +24,31 @@ public class AccessInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public AccessInfo remove(int flag) {
|
public AccessInfo remove(int flag) {
|
||||||
if (containsFlag(flag))
|
if (containsFlag(flag)) {
|
||||||
return new AccessInfo(accFlags - flag, type);
|
return new AccessInfo(accFlags & ~flag, type);
|
||||||
else
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AccessInfo getVisibility() {
|
public AccessInfo getVisibility() {
|
||||||
int f = (accFlags & AccessFlags.ACC_PUBLIC)
|
int f = accFlags & AccessFlags.ACC_PUBLIC
|
||||||
| (accFlags & AccessFlags.ACC_PROTECTED)
|
| accFlags & AccessFlags.ACC_PROTECTED
|
||||||
| (accFlags & AccessFlags.ACC_PRIVATE);
|
| accFlags & AccessFlags.ACC_PRIVATE;
|
||||||
return new AccessInfo(f, type);
|
return new AccessInfo(f, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isPublic() {
|
||||||
|
return (accFlags & AccessFlags.ACC_PUBLIC) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isProtected() {
|
||||||
|
return (accFlags & AccessFlags.ACC_PROTECTED) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPrivate() {
|
||||||
|
return (accFlags & AccessFlags.ACC_PRIVATE) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isAbstract() {
|
public boolean isAbstract() {
|
||||||
return (accFlags & AccessFlags.ACC_ABSTRACT) != 0;
|
return (accFlags & AccessFlags.ACC_ABSTRACT) != 0;
|
||||||
}
|
}
|
||||||
@@ -81,76 +93,104 @@ public class AccessInfo {
|
|||||||
return (accFlags & AccessFlags.ACC_VARARGS) != 0;
|
return (accFlags & AccessFlags.ACC_VARARGS) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getFlags() {
|
public boolean isSynchronized() {
|
||||||
return accFlags;
|
return (accFlags & (AccessFlags.ACC_SYNCHRONIZED | AccessFlags.ACC_DECLARED_SYNCHRONIZED)) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isTransient() {
|
||||||
|
return (accFlags & AccessFlags.ACC_TRANSIENT) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isVolatile() {
|
||||||
|
return (accFlags & AccessFlags.ACC_VOLATILE) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AFType getType() {
|
||||||
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String makeString() {
|
public String makeString() {
|
||||||
StringBuilder code = new StringBuilder();
|
StringBuilder code = new StringBuilder();
|
||||||
if ((accFlags & AccessFlags.ACC_PUBLIC) != 0)
|
if (isPublic()) {
|
||||||
code.append("public ");
|
code.append("public ");
|
||||||
|
}
|
||||||
if ((accFlags & AccessFlags.ACC_PRIVATE) != 0)
|
if (isPrivate()) {
|
||||||
code.append("private ");
|
code.append("private ");
|
||||||
|
}
|
||||||
if ((accFlags & AccessFlags.ACC_PROTECTED) != 0)
|
if (isProtected()) {
|
||||||
code.append("protected ");
|
code.append("protected ");
|
||||||
|
}
|
||||||
if (isStatic())
|
if (isStatic()) {
|
||||||
code.append("static ");
|
code.append("static ");
|
||||||
|
}
|
||||||
if (isFinal())
|
if (isFinal()) {
|
||||||
code.append("final ");
|
code.append("final ");
|
||||||
|
}
|
||||||
if (isAbstract())
|
if (isAbstract()) {
|
||||||
code.append("abstract ");
|
code.append("abstract ");
|
||||||
|
}
|
||||||
if (isNative())
|
if (isNative()) {
|
||||||
code.append("native ");
|
code.append("native ");
|
||||||
|
}
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case METHOD:
|
case METHOD:
|
||||||
if ((accFlags & AccessFlags.ACC_SYNCHRONIZED) != 0)
|
if (isSynchronized()) {
|
||||||
code.append("synchronized ");
|
code.append("synchronized ");
|
||||||
|
}
|
||||||
if ((accFlags & AccessFlags.ACC_DECLARED_SYNCHRONIZED) != 0)
|
if (isBridge()) {
|
||||||
code.append("synchronized ");
|
|
||||||
|
|
||||||
if (isBridge())
|
|
||||||
code.append("/* bridge */ ");
|
code.append("/* bridge */ ");
|
||||||
|
}
|
||||||
if (Consts.DEBUG) {
|
if (Consts.DEBUG) {
|
||||||
if (isVarArgs())
|
if (isVarArgs()) {
|
||||||
code.append("/* varargs */ ");
|
code.append("/* varargs */ ");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case FIELD:
|
case FIELD:
|
||||||
if ((accFlags & AccessFlags.ACC_VOLATILE) != 0)
|
if (isVolatile()) {
|
||||||
code.append("volatile ");
|
code.append("volatile ");
|
||||||
|
}
|
||||||
if ((accFlags & AccessFlags.ACC_TRANSIENT) != 0)
|
if (isTransient()) {
|
||||||
code.append("transient ");
|
code.append("transient ");
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CLASS:
|
case CLASS:
|
||||||
if ((accFlags & AccessFlags.ACC_STRICT) != 0)
|
if ((accFlags & AccessFlags.ACC_STRICT) != 0) {
|
||||||
code.append("strict ");
|
code.append("strict ");
|
||||||
|
}
|
||||||
if (Consts.DEBUG) {
|
if (Consts.DEBUG) {
|
||||||
if ((accFlags & AccessFlags.ACC_SUPER) != 0)
|
if ((accFlags & AccessFlags.ACC_SUPER) != 0) {
|
||||||
code.append("/* super */ ");
|
code.append("/* super */ ");
|
||||||
|
}
|
||||||
if ((accFlags & AccessFlags.ACC_ENUM) != 0)
|
if ((accFlags & AccessFlags.ACC_ENUM) != 0) {
|
||||||
code.append("/* enum */ ");
|
code.append("/* enum */ ");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (isSynthetic()) {
|
||||||
if (isSynthetic())
|
|
||||||
code.append("/* synthetic */ ");
|
code.append("/* synthetic */ ");
|
||||||
|
}
|
||||||
return code.toString();
|
return code.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String rawString() {
|
||||||
|
switch (type) {
|
||||||
|
case CLASS:
|
||||||
|
return AccessFlags.classString(accFlags);
|
||||||
|
case FIELD:
|
||||||
|
return AccessFlags.fieldString(accFlags);
|
||||||
|
case METHOD:
|
||||||
|
return AccessFlags.methodString(accFlags);
|
||||||
|
default:
|
||||||
|
return "?";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "AccessInfo: " + type + " 0x" + Integer.toHexString(accFlags) + " (" + rawString() + ")";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,184 @@
|
|||||||
|
package jadx.core.dex.info;
|
||||||
|
|
||||||
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
|
import jadx.core.dex.nodes.DexNode;
|
||||||
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
public final class ClassInfo {
|
||||||
|
|
||||||
|
private final ArgType type;
|
||||||
|
private String pkg;
|
||||||
|
private String name;
|
||||||
|
private String fullName;
|
||||||
|
// for inner class not equals null
|
||||||
|
private ClassInfo parentClass;
|
||||||
|
// class info after rename (deobfuscation)
|
||||||
|
private ClassInfo alias;
|
||||||
|
|
||||||
|
private ClassInfo(DexNode dex, ArgType type) {
|
||||||
|
this(dex, type, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ClassInfo(DexNode dex, ArgType type, boolean inner) {
|
||||||
|
if (!type.isObject() || type.isGeneric()) {
|
||||||
|
throw new JadxRuntimeException("Not class type: " + type);
|
||||||
|
}
|
||||||
|
this.type = type;
|
||||||
|
this.alias = this;
|
||||||
|
|
||||||
|
splitNames(dex, inner);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ClassInfo fromType(DexNode dex, ArgType type) {
|
||||||
|
if (type.isArray()) {
|
||||||
|
type = ArgType.OBJECT;
|
||||||
|
}
|
||||||
|
ClassInfo cls = dex.getInfoStorage().getCls(type);
|
||||||
|
if (cls != null) {
|
||||||
|
return cls;
|
||||||
|
}
|
||||||
|
cls = new ClassInfo(dex, type);
|
||||||
|
return dex.getInfoStorage().putCls(cls);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ClassInfo fromDex(DexNode dex, int clsIndex) {
|
||||||
|
if (clsIndex == DexNode.NO_INDEX) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return fromType(dex, dex.getType(clsIndex));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ClassInfo fromName(DexNode dex, String clsName) {
|
||||||
|
return fromType(dex, ArgType.object(clsName));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ClassInfo extCls(DexNode dex, ArgType type) {
|
||||||
|
ClassInfo classInfo = fromName(dex, type.getObject());
|
||||||
|
return classInfo.alias;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void rename(DexNode dex, String fullName) {
|
||||||
|
ClassInfo newAlias = new ClassInfo(dex, ArgType.object(fullName), isInner());
|
||||||
|
if (!alias.getFullName().equals(newAlias.getFullName())) {
|
||||||
|
this.alias = newAlias;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRenamed() {
|
||||||
|
return alias != this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClassInfo getAlias() {
|
||||||
|
return alias;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void splitNames(DexNode dex, boolean canBeInner) {
|
||||||
|
String fullObjectName = type.getObject();
|
||||||
|
String clsName;
|
||||||
|
int dot = fullObjectName.lastIndexOf('.');
|
||||||
|
if (dot == -1) {
|
||||||
|
pkg = "";
|
||||||
|
clsName = fullObjectName;
|
||||||
|
} else {
|
||||||
|
pkg = fullObjectName.substring(0, dot);
|
||||||
|
clsName = fullObjectName.substring(dot + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int sep = clsName.lastIndexOf('$');
|
||||||
|
if (canBeInner && sep > 0 && sep != clsName.length() - 1) {
|
||||||
|
String parClsName = pkg + "." + clsName.substring(0, sep);
|
||||||
|
parentClass = fromName(dex, parClsName);
|
||||||
|
clsName = clsName.substring(sep + 1);
|
||||||
|
} else {
|
||||||
|
parentClass = null;
|
||||||
|
}
|
||||||
|
this.name = clsName;
|
||||||
|
this.fullName = makeFullClsName(clsName, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String makeFullClsName(String shortName, boolean raw) {
|
||||||
|
if (parentClass != null) {
|
||||||
|
String innerSep = raw ? "$" : ".";
|
||||||
|
return parentClass.makeFullClsName(parentClass.getShortName(), raw) + innerSep + shortName;
|
||||||
|
}
|
||||||
|
return pkg.isEmpty() ? shortName : pkg + "." + shortName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFullPath() {
|
||||||
|
ClassInfo alias = getAlias();
|
||||||
|
return alias.getPackage().replace('.', File.separatorChar)
|
||||||
|
+ File.separatorChar
|
||||||
|
+ alias.getNameWithoutPackage().replace('.', '_');
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFullName() {
|
||||||
|
return fullName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getShortName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPackage() {
|
||||||
|
return pkg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRawName() {
|
||||||
|
return type.getObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNameWithoutPackage() {
|
||||||
|
if (parentClass == null) {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
return parentClass.getNameWithoutPackage() + "." + name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClassInfo getParentClass() {
|
||||||
|
return parentClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClassInfo getTopParentClass() {
|
||||||
|
if (parentClass != null) {
|
||||||
|
ClassInfo topCls = parentClass.getTopParentClass();
|
||||||
|
return topCls != null ? topCls : parentClass;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isInner() {
|
||||||
|
return parentClass != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void notInner(DexNode dex) {
|
||||||
|
splitNames(dex, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArgType getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return fullName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return fullName.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (obj instanceof ClassInfo) {
|
||||||
|
ClassInfo other = (ClassInfo) obj;
|
||||||
|
return this.type.equals(other.type);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
package jadx.core.dex.info;
|
||||||
|
|
||||||
|
import jadx.core.codegen.TypeGen;
|
||||||
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
|
import jadx.core.dex.nodes.DexNode;
|
||||||
|
|
||||||
|
import com.android.dex.FieldId;
|
||||||
|
|
||||||
|
public final class FieldInfo {
|
||||||
|
|
||||||
|
private final ClassInfo declClass;
|
||||||
|
private final String name;
|
||||||
|
private final ArgType type;
|
||||||
|
private String alias;
|
||||||
|
|
||||||
|
private FieldInfo(ClassInfo declClass, String name, ArgType type) {
|
||||||
|
this.declClass = declClass;
|
||||||
|
this.name = name;
|
||||||
|
this.type = type;
|
||||||
|
this.alias = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FieldInfo from(DexNode dex, ClassInfo declClass, String name, ArgType type) {
|
||||||
|
FieldInfo field = new FieldInfo(declClass, name, type);
|
||||||
|
return dex.getInfoStorage().getField(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FieldInfo fromDex(DexNode dex, int index) {
|
||||||
|
FieldId field = dex.getFieldId(index);
|
||||||
|
return from(dex,
|
||||||
|
ClassInfo.fromDex(dex, field.getDeclaringClassIndex()),
|
||||||
|
dex.getString(field.getNameIndex()),
|
||||||
|
dex.getType(field.getTypeIndex()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArgType getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClassInfo getDeclClass() {
|
||||||
|
return declClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAlias() {
|
||||||
|
return alias;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAlias(String alias) {
|
||||||
|
this.alias = alias;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFullId() {
|
||||||
|
return declClass.getFullName() + "." + name + ":" + TypeGen.signature(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRenamed() {
|
||||||
|
return !name.equals(alias);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
FieldInfo fieldInfo = (FieldInfo) o;
|
||||||
|
return name.equals(fieldInfo.name)
|
||||||
|
&& type.equals(fieldInfo.type)
|
||||||
|
&& declClass.equals(fieldInfo.declClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = name.hashCode();
|
||||||
|
result = 31 * result + type.hashCode();
|
||||||
|
result = 31 * result + declClass.hashCode();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return declClass + "." + name + " " + type;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package jadx.core.dex.info;
|
||||||
|
|
||||||
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class InfoStorage {
|
||||||
|
|
||||||
|
private final Map<ArgType, ClassInfo> classes = new HashMap<ArgType, ClassInfo>();
|
||||||
|
private final Map<Integer, MethodInfo> methods = new HashMap<Integer, MethodInfo>();
|
||||||
|
private final Map<FieldInfo, FieldInfo> fields = new HashMap<FieldInfo, FieldInfo>();
|
||||||
|
|
||||||
|
public ClassInfo getCls(ArgType type) {
|
||||||
|
return classes.get(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClassInfo putCls(ClassInfo cls) {
|
||||||
|
synchronized (classes) {
|
||||||
|
ClassInfo prev = classes.put(cls.getType(), cls);
|
||||||
|
return prev == null ? cls : prev;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public MethodInfo getMethod(int mtdId) {
|
||||||
|
return methods.get(mtdId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MethodInfo putMethod(int mthId, MethodInfo mth) {
|
||||||
|
synchronized (methods) {
|
||||||
|
MethodInfo prev = methods.put(mthId, mth);
|
||||||
|
return prev == null ? mth : prev;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public FieldInfo getField(FieldInfo field) {
|
||||||
|
synchronized (fields) {
|
||||||
|
FieldInfo f = fields.get(field);
|
||||||
|
if (f != null) {
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
fields.put(field, field);
|
||||||
|
return field;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
package jadx.core.dex.info;
|
||||||
|
|
||||||
|
import jadx.core.codegen.TypeGen;
|
||||||
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
|
import jadx.core.dex.nodes.DexNode;
|
||||||
|
import jadx.core.utils.Utils;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.android.dex.MethodId;
|
||||||
|
import com.android.dex.ProtoId;
|
||||||
|
|
||||||
|
public final class MethodInfo {
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
private final ArgType retType;
|
||||||
|
private final List<ArgType> args;
|
||||||
|
private final ClassInfo declClass;
|
||||||
|
private final String shortId;
|
||||||
|
private String alias;
|
||||||
|
|
||||||
|
private MethodInfo(DexNode dex, int mthIndex) {
|
||||||
|
MethodId mthId = dex.getMethodId(mthIndex);
|
||||||
|
name = dex.getString(mthId.getNameIndex());
|
||||||
|
alias = name;
|
||||||
|
declClass = ClassInfo.fromDex(dex, mthId.getDeclaringClassIndex());
|
||||||
|
|
||||||
|
ProtoId proto = dex.getProtoId(mthId.getProtoIndex());
|
||||||
|
retType = dex.getType(proto.getReturnTypeIndex());
|
||||||
|
args = dex.readParamList(proto.getParametersOffset());
|
||||||
|
shortId = makeSignature(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MethodInfo fromDex(DexNode dex, int mthIndex) {
|
||||||
|
MethodInfo mth = dex.getInfoStorage().getMethod(mthIndex);
|
||||||
|
if (mth != null) {
|
||||||
|
return mth;
|
||||||
|
}
|
||||||
|
mth = new MethodInfo(dex, mthIndex);
|
||||||
|
return dex.getInfoStorage().putMethod(mthIndex, mth);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String makeSignature(boolean includeRetType) {
|
||||||
|
StringBuilder signature = new StringBuilder();
|
||||||
|
signature.append(name);
|
||||||
|
signature.append('(');
|
||||||
|
for (ArgType arg : args) {
|
||||||
|
signature.append(TypeGen.signature(arg));
|
||||||
|
}
|
||||||
|
signature.append(')');
|
||||||
|
if (includeRetType) {
|
||||||
|
signature.append(TypeGen.signature(retType));
|
||||||
|
}
|
||||||
|
return signature.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFullName() {
|
||||||
|
return declClass.getFullName() + "." + name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFullId() {
|
||||||
|
return declClass.getFullName() + "." + shortId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method name and signature
|
||||||
|
*/
|
||||||
|
public String getShortId() {
|
||||||
|
return shortId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClassInfo getDeclClass() {
|
||||||
|
return declClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArgType getReturnType() {
|
||||||
|
return retType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ArgType> getArgumentsTypes() {
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getArgsCount() {
|
||||||
|
return args.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isConstructor() {
|
||||||
|
return name.equals("<init>");
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isClassInit() {
|
||||||
|
return name.equals("<clinit>");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAlias() {
|
||||||
|
return alias;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAlias(String alias) {
|
||||||
|
this.alias = alias;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRenamed() {
|
||||||
|
return !name.equals(alias);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = declClass.hashCode();
|
||||||
|
result = 31 * result + retType.hashCode();
|
||||||
|
result = 31 * result + shortId.hashCode();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (obj == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (getClass() != obj.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
MethodInfo other = (MethodInfo) obj;
|
||||||
|
return shortId.equals(other.shortId)
|
||||||
|
&& retType.equals(other.retType)
|
||||||
|
&& declClass.equals(other.declClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return declClass.getFullName() + "." + name
|
||||||
|
+ "(" + Utils.listToString(args) + "):" + retType;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
package jadx.core.dex.instructions;
|
||||||
|
|
||||||
|
import jadx.core.dex.attributes.AFlag;
|
||||||
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
|
import jadx.core.dex.instructions.args.InsnArg;
|
||||||
|
import jadx.core.dex.instructions.args.RegisterArg;
|
||||||
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
|
import jadx.core.utils.InsnUtils;
|
||||||
|
|
||||||
|
import com.android.dx.io.instructions.DecodedInstruction;
|
||||||
|
|
||||||
|
public class ArithNode extends InsnNode {
|
||||||
|
|
||||||
|
private final ArithOp op;
|
||||||
|
|
||||||
|
public ArithNode(DecodedInstruction insn, ArithOp op, ArgType type, boolean literal) {
|
||||||
|
super(InsnType.ARITH, 2);
|
||||||
|
this.op = op;
|
||||||
|
setResult(InsnArg.reg(insn, 0, type));
|
||||||
|
|
||||||
|
int rc = insn.getRegisterCount();
|
||||||
|
if (literal) {
|
||||||
|
if (rc == 1) {
|
||||||
|
// self
|
||||||
|
addReg(insn, 0, type);
|
||||||
|
addLit(insn, type);
|
||||||
|
} else if (rc == 2) {
|
||||||
|
// normal
|
||||||
|
addReg(insn, 1, type);
|
||||||
|
addLit(insn, type);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (rc == 2) {
|
||||||
|
// self
|
||||||
|
addReg(insn, 0, type);
|
||||||
|
addReg(insn, 1, type);
|
||||||
|
} else if (rc == 3) {
|
||||||
|
// normal
|
||||||
|
addReg(insn, 1, type);
|
||||||
|
addReg(insn, 2, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert getArgsCount() == 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArithNode(ArithOp op, RegisterArg res, InsnArg a, InsnArg b) {
|
||||||
|
super(InsnType.ARITH, 2);
|
||||||
|
this.op = op;
|
||||||
|
setResult(res);
|
||||||
|
addArg(a);
|
||||||
|
addArg(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArithNode(ArithOp op, RegisterArg res, InsnArg a) {
|
||||||
|
this(op, res, res, a);
|
||||||
|
add(AFlag.ARITH_ONEARG);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArithOp getOp() {
|
||||||
|
return op;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSame(InsnNode obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(obj instanceof ArithNode) || !super.isSame(obj)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ArithNode other = (ArithNode) obj;
|
||||||
|
return op == other.op;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return InsnUtils.formatOffset(offset) + ": "
|
||||||
|
+ InsnUtils.insnTypeToString(insnType)
|
||||||
|
+ getResult() + " = "
|
||||||
|
+ getArg(0) + " "
|
||||||
|
+ op.getSymbol() + " "
|
||||||
|
+ getArg(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+4
-7
@@ -1,4 +1,4 @@
|
|||||||
package jadx.dex.instructions;
|
package jadx.core.dex.instructions;
|
||||||
|
|
||||||
public enum ArithOp {
|
public enum ArithOp {
|
||||||
ADD("+"),
|
ADD("+"),
|
||||||
@@ -7,9 +7,6 @@ public enum ArithOp {
|
|||||||
DIV("/"),
|
DIV("/"),
|
||||||
REM("%"),
|
REM("%"),
|
||||||
|
|
||||||
INC("++"),
|
|
||||||
DEC("--"),
|
|
||||||
|
|
||||||
AND("&"),
|
AND("&"),
|
||||||
OR("|"),
|
OR("|"),
|
||||||
XOR("^"),
|
XOR("^"),
|
||||||
@@ -18,12 +15,12 @@ public enum ArithOp {
|
|||||||
SHR(">>"),
|
SHR(">>"),
|
||||||
USHR(">>>");
|
USHR(">>>");
|
||||||
|
|
||||||
private ArithOp(String symbol) {
|
private final String symbol;
|
||||||
|
|
||||||
|
ArithOp(String symbol) {
|
||||||
this.symbol = symbol;
|
this.symbol = symbol;
|
||||||
}
|
}
|
||||||
|
|
||||||
private final String symbol;
|
|
||||||
|
|
||||||
public String getSymbol() {
|
public String getSymbol() {
|
||||||
return this.symbol;
|
return this.symbol;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package jadx.core.dex.instructions;
|
||||||
|
|
||||||
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
|
|
||||||
|
public final class ConstClassNode extends InsnNode {
|
||||||
|
|
||||||
|
private final ArgType clsType;
|
||||||
|
|
||||||
|
public ConstClassNode(ArgType clsType) {
|
||||||
|
super(InsnType.CONST_CLASS, 0);
|
||||||
|
this.clsType = clsType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArgType getClsType() {
|
||||||
|
return clsType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSame(InsnNode obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(obj instanceof ConstClassNode) || !super.isSame(obj)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ConstClassNode other = (ConstClassNode) obj;
|
||||||
|
return clsType.equals(other.clsType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return super.toString() + " " + clsType;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package jadx.core.dex.instructions;
|
||||||
|
|
||||||
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
|
|
||||||
|
public final class ConstStringNode extends InsnNode {
|
||||||
|
|
||||||
|
private final String str;
|
||||||
|
|
||||||
|
public ConstStringNode(String str) {
|
||||||
|
super(InsnType.CONST_STR, 0);
|
||||||
|
this.str = str;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getString() {
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSame(InsnNode obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(obj instanceof ConstStringNode) || !super.isSame(obj)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ConstStringNode other = (ConstStringNode) obj;
|
||||||
|
return str.equals(other.str);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return super.toString() + " \"" + str + "\"";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
package jadx.core.dex.instructions;
|
||||||
|
|
||||||
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
|
import jadx.core.dex.instructions.args.InsnArg;
|
||||||
|
import jadx.core.dex.instructions.args.LiteralArg;
|
||||||
|
import jadx.core.dex.instructions.args.PrimitiveType;
|
||||||
|
import jadx.core.dex.nodes.DexNode;
|
||||||
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.android.dx.io.instructions.FillArrayDataPayloadDecodedInstruction;
|
||||||
|
|
||||||
|
public final class FillArrayNode extends InsnNode {
|
||||||
|
|
||||||
|
private final Object data;
|
||||||
|
private final int size;
|
||||||
|
private ArgType elemType;
|
||||||
|
|
||||||
|
public FillArrayNode(int resReg, FillArrayDataPayloadDecodedInstruction payload) {
|
||||||
|
super(InsnType.FILL_ARRAY, 0);
|
||||||
|
ArgType elType;
|
||||||
|
switch (payload.getElementWidthUnit()) {
|
||||||
|
case 1:
|
||||||
|
elType = ArgType.unknown(PrimitiveType.BOOLEAN, PrimitiveType.BYTE);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
elType = ArgType.unknown(PrimitiveType.SHORT, PrimitiveType.CHAR);
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
elType = ArgType.unknown(PrimitiveType.INT, PrimitiveType.FLOAT);
|
||||||
|
break;
|
||||||
|
case 8:
|
||||||
|
elType = ArgType.unknown(PrimitiveType.LONG, PrimitiveType.DOUBLE);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new JadxRuntimeException("Unknown array element width: " + payload.getElementWidthUnit());
|
||||||
|
}
|
||||||
|
setResult(InsnArg.reg(resReg, ArgType.array(elType)));
|
||||||
|
|
||||||
|
this.data = payload.getData();
|
||||||
|
this.size = payload.getSize();
|
||||||
|
this.elemType = elType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getData() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSize() {
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArgType getElementType() {
|
||||||
|
return elemType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void mergeElementType(DexNode dex, ArgType foundElemType) {
|
||||||
|
ArgType r = ArgType.merge(dex, elemType, foundElemType);
|
||||||
|
if (r != null) {
|
||||||
|
elemType = r;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<LiteralArg> getLiteralArgs() {
|
||||||
|
List<LiteralArg> list = new ArrayList<LiteralArg>(size);
|
||||||
|
Object array = data;
|
||||||
|
if (array instanceof int[]) {
|
||||||
|
for (int b : (int[]) array) {
|
||||||
|
list.add(InsnArg.lit(b, elemType));
|
||||||
|
}
|
||||||
|
} else if (array instanceof byte[]) {
|
||||||
|
for (byte b : (byte[]) array) {
|
||||||
|
list.add(InsnArg.lit(b, elemType));
|
||||||
|
}
|
||||||
|
} else if (array instanceof short[]) {
|
||||||
|
for (short b : (short[]) array) {
|
||||||
|
list.add(InsnArg.lit(b, elemType));
|
||||||
|
}
|
||||||
|
} else if (array instanceof long[]) {
|
||||||
|
for (long b : (long[]) array) {
|
||||||
|
list.add(InsnArg.lit(b, elemType));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new JadxRuntimeException("Unknown type: " + data.getClass() + ", expected: " + elemType);
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSame(InsnNode obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(obj instanceof FillArrayNode) || !super.isSame(obj)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
FillArrayNode other = (FillArrayNode) obj;
|
||||||
|
return elemType.equals(other.elemType) && data == other.data;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package jadx.core.dex.instructions;
|
||||||
|
|
||||||
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
public class FilledNewArrayNode extends InsnNode {
|
||||||
|
|
||||||
|
private final ArgType elemType;
|
||||||
|
|
||||||
|
public FilledNewArrayNode(@NotNull ArgType elemType, int size) {
|
||||||
|
super(InsnType.FILLED_NEW_ARRAY, size);
|
||||||
|
this.elemType = elemType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArgType getElemType() {
|
||||||
|
return elemType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArgType getArrayType() {
|
||||||
|
return ArgType.array(elemType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSame(InsnNode obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(obj instanceof FilledNewArrayNode) || !super.isSame(obj)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
FilledNewArrayNode other = (FilledNewArrayNode) obj;
|
||||||
|
return elemType == other.elemType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return super.toString() + " elemType: " + elemType;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package jadx.core.dex.instructions;
|
||||||
|
|
||||||
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
|
import jadx.core.utils.InsnUtils;
|
||||||
|
|
||||||
|
public class GotoNode extends InsnNode {
|
||||||
|
|
||||||
|
protected int target;
|
||||||
|
|
||||||
|
public GotoNode(int target) {
|
||||||
|
this(InsnType.GOTO, target, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected GotoNode(InsnType type, int target, int argsCount) {
|
||||||
|
super(type, argsCount);
|
||||||
|
this.target = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTarget() {
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return super.toString() + "-> " + InsnUtils.formatOffset(target);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
package jadx.core.dex.instructions;
|
||||||
|
|
||||||
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
|
import jadx.core.dex.instructions.args.InsnArg;
|
||||||
|
import jadx.core.dex.instructions.args.PrimitiveType;
|
||||||
|
import jadx.core.dex.nodes.BlockNode;
|
||||||
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
|
import jadx.core.utils.InsnUtils;
|
||||||
|
|
||||||
|
import com.android.dx.io.instructions.DecodedInstruction;
|
||||||
|
|
||||||
|
import static jadx.core.utils.BlockUtils.getBlockByOffset;
|
||||||
|
import static jadx.core.utils.BlockUtils.selectOther;
|
||||||
|
|
||||||
|
public class IfNode extends GotoNode {
|
||||||
|
|
||||||
|
private static final ArgType ARG_TYPE = ArgType.unknown(
|
||||||
|
PrimitiveType.INT, PrimitiveType.OBJECT, PrimitiveType.ARRAY,
|
||||||
|
PrimitiveType.BOOLEAN, PrimitiveType.SHORT, PrimitiveType.CHAR);
|
||||||
|
|
||||||
|
protected IfOp op;
|
||||||
|
|
||||||
|
private BlockNode thenBlock;
|
||||||
|
private BlockNode elseBlock;
|
||||||
|
|
||||||
|
public IfNode(DecodedInstruction insn, IfOp op) {
|
||||||
|
this(op, insn.getTarget(),
|
||||||
|
InsnArg.reg(insn, 0, ARG_TYPE),
|
||||||
|
insn.getRegisterCount() == 1 ? InsnArg.lit(0, ARG_TYPE) : InsnArg.reg(insn, 1, ARG_TYPE));
|
||||||
|
}
|
||||||
|
|
||||||
|
public IfNode(IfOp op, int targetOffset, InsnArg arg1, InsnArg arg2) {
|
||||||
|
super(InsnType.IF, targetOffset, 2);
|
||||||
|
this.op = op;
|
||||||
|
addArg(arg1);
|
||||||
|
addArg(arg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IfOp getOp() {
|
||||||
|
return op;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void invertCondition() {
|
||||||
|
op = op.invert();
|
||||||
|
BlockNode tmp = thenBlock;
|
||||||
|
thenBlock = elseBlock;
|
||||||
|
elseBlock = tmp;
|
||||||
|
target = thenBlock.getStartOffset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void changeCondition(IfOp op, InsnArg arg1, InsnArg arg2) {
|
||||||
|
this.op = op;
|
||||||
|
setArg(0, arg1);
|
||||||
|
setArg(1, arg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void initBlocks(BlockNode curBlock) {
|
||||||
|
thenBlock = getBlockByOffset(target, curBlock.getSuccessors());
|
||||||
|
if (curBlock.getSuccessors().size() == 1) {
|
||||||
|
elseBlock = thenBlock;
|
||||||
|
} else {
|
||||||
|
elseBlock = selectOther(thenBlock, curBlock.getSuccessors());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlockNode getThenBlock() {
|
||||||
|
return thenBlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlockNode getElseBlock() {
|
||||||
|
return elseBlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSame(InsnNode obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(obj instanceof IfNode) || !super.isSame(obj)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
IfNode other = (IfNode) obj;
|
||||||
|
return op == other.op;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return InsnUtils.formatOffset(offset) + ": "
|
||||||
|
+ InsnUtils.insnTypeToString(insnType)
|
||||||
|
+ getArg(0) + " " + op.getSymbol() + " " + getArg(1)
|
||||||
|
+ " -> " + (thenBlock != null ? thenBlock : InsnUtils.formatOffset(target));
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
-9
@@ -1,4 +1,6 @@
|
|||||||
package jadx.dex.instructions;
|
package jadx.core.dex.instructions;
|
||||||
|
|
||||||
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
public enum IfOp {
|
public enum IfOp {
|
||||||
EQ("=="),
|
EQ("=="),
|
||||||
@@ -10,7 +12,7 @@ public enum IfOp {
|
|||||||
|
|
||||||
private final String symbol;
|
private final String symbol;
|
||||||
|
|
||||||
private IfOp(String symbol) {
|
IfOp(String symbol) {
|
||||||
this.symbol = symbol;
|
this.symbol = symbol;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21,22 +23,22 @@ public enum IfOp {
|
|||||||
public IfOp invert() {
|
public IfOp invert() {
|
||||||
switch (this) {
|
switch (this) {
|
||||||
case EQ:
|
case EQ:
|
||||||
return IfOp.NE;
|
return NE;
|
||||||
case NE:
|
case NE:
|
||||||
return IfOp.EQ;
|
return EQ;
|
||||||
|
|
||||||
case LT:
|
case LT:
|
||||||
return IfOp.GE;
|
return GE;
|
||||||
case LE:
|
case LE:
|
||||||
return IfOp.GT;
|
return GT;
|
||||||
|
|
||||||
case GT:
|
case GT:
|
||||||
return IfOp.LE;
|
return LE;
|
||||||
case GE:
|
case GE:
|
||||||
return IfOp.LT;
|
return LT;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return null;
|
throw new JadxRuntimeException("Unknown if operations type: " + this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package jadx.core.dex.instructions;
|
||||||
|
|
||||||
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
|
import jadx.core.utils.InsnUtils;
|
||||||
|
|
||||||
|
public class IndexInsnNode extends InsnNode {
|
||||||
|
|
||||||
|
private final Object index;
|
||||||
|
|
||||||
|
public IndexInsnNode(InsnType type, Object index, int argCount) {
|
||||||
|
super(type, argCount);
|
||||||
|
this.index = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getIndex() {
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSame(InsnNode obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(obj instanceof IndexInsnNode) || !super.isSame(obj)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
IndexInsnNode other = (IndexInsnNode) obj;
|
||||||
|
return index == null ? other.index == null : index.equals(other.index);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return super.toString() + " " + InsnUtils.indexToString(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
+124
-107
@@ -1,69 +1,71 @@
|
|||||||
package jadx.dex.instructions;
|
package jadx.core.dex.instructions;
|
||||||
|
|
||||||
import jadx.dex.info.FieldInfo;
|
import jadx.core.dex.info.FieldInfo;
|
||||||
import jadx.dex.instructions.args.ArgType;
|
import jadx.core.dex.info.MethodInfo;
|
||||||
import jadx.dex.instructions.args.InsnArg;
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
import jadx.dex.instructions.args.PrimitiveType;
|
import jadx.core.dex.instructions.args.InsnArg;
|
||||||
import jadx.dex.instructions.args.RegisterArg;
|
import jadx.core.dex.instructions.args.PrimitiveType;
|
||||||
import jadx.dex.nodes.DexNode;
|
import jadx.core.dex.instructions.args.RegisterArg;
|
||||||
import jadx.dex.nodes.InsnNode;
|
import jadx.core.dex.nodes.DexNode;
|
||||||
import jadx.dex.nodes.MethodNode;
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
import jadx.utils.exceptions.DecodeException;
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
|
import jadx.core.utils.InsnUtils;
|
||||||
|
import jadx.core.utils.exceptions.DecodeException;
|
||||||
|
|
||||||
import com.android.dx.io.Code;
|
import com.android.dex.Code;
|
||||||
import com.android.dx.io.OpcodeInfo;
|
import com.android.dx.io.OpcodeInfo;
|
||||||
import com.android.dx.io.Opcodes;
|
import com.android.dx.io.Opcodes;
|
||||||
import com.android.dx.io.instructions.DecodedInstruction;
|
import com.android.dx.io.instructions.DecodedInstruction;
|
||||||
import com.android.dx.io.instructions.FillArrayDataPayloadDecodedInstruction;
|
import com.android.dx.io.instructions.FillArrayDataPayloadDecodedInstruction;
|
||||||
import com.android.dx.io.instructions.PackedSwitchPayloadDecodedInstruction;
|
import com.android.dx.io.instructions.PackedSwitchPayloadDecodedInstruction;
|
||||||
|
import com.android.dx.io.instructions.ShortArrayCodeInput;
|
||||||
import com.android.dx.io.instructions.SparseSwitchPayloadDecodedInstruction;
|
import com.android.dx.io.instructions.SparseSwitchPayloadDecodedInstruction;
|
||||||
|
|
||||||
public class InsnDecoder {
|
public class InsnDecoder {
|
||||||
|
|
||||||
private final MethodNode method;
|
private final MethodNode method;
|
||||||
private final DecodedInstruction[] insnArr;
|
|
||||||
private final DexNode dex;
|
private final DexNode dex;
|
||||||
|
private DecodedInstruction[] insnArr;
|
||||||
|
|
||||||
public InsnDecoder(MethodNode mthNode, Code mthCode) {
|
public InsnDecoder(MethodNode mthNode) {
|
||||||
this.method = mthNode;
|
this.method = mthNode;
|
||||||
this.dex = method.dex();
|
this.dex = method.dex();
|
||||||
this.insnArr = DecodedInstruction.decodeAll(mthCode.getInstructions());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public InsnNode[] run() throws DecodeException {
|
public void decodeInsns(Code mthCode) throws DecodeException {
|
||||||
InsnNode[] instructions = new InsnNode[insnArr.length];
|
short[] encodedInstructions = mthCode.getInstructions();
|
||||||
|
int size = encodedInstructions.length;
|
||||||
|
DecodedInstruction[] decoded = new DecodedInstruction[size];
|
||||||
|
ShortArrayCodeInput in = new ShortArrayCodeInput(encodedInstructions);
|
||||||
|
|
||||||
|
try {
|
||||||
|
while (in.hasMore()) {
|
||||||
|
decoded[in.cursor()] = DecodedInstruction.decode(in);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new DecodeException(method, "", e);
|
||||||
|
}
|
||||||
|
insnArr = decoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
public InsnNode[] process() throws DecodeException {
|
||||||
|
InsnNode[] instructions = new InsnNode[insnArr.length];
|
||||||
for (int i = 0; i < insnArr.length; i++) {
|
for (int i = 0; i < insnArr.length; i++) {
|
||||||
DecodedInstruction rawInsn = insnArr[i];
|
DecodedInstruction rawInsn = insnArr[i];
|
||||||
if (rawInsn != null) {
|
if (rawInsn != null) {
|
||||||
InsnNode insn = decode(rawInsn, i);
|
InsnNode insn = decode(rawInsn, i);
|
||||||
if (insn != null) {
|
if (insn != null) {
|
||||||
insn.setOffset(i);
|
insn.setOffset(i);
|
||||||
insn.setInsnHashCode(calcHashCode(rawInsn));
|
|
||||||
}
|
}
|
||||||
instructions[i] = insn;
|
instructions[i] = insn;
|
||||||
} else {
|
} else {
|
||||||
instructions[i] = null;
|
instructions[i] = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
insnArr = null;
|
||||||
return instructions;
|
return instructions;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int calcHashCode(DecodedInstruction insn) {
|
|
||||||
int hash = insn.getOpcode();
|
|
||||||
hash = hash * 31 + insn.getClass().getName().hashCode();
|
|
||||||
hash = hash * 31 + insn.getFormat().ordinal();
|
|
||||||
hash = hash * 31 + insn.getRegisterCount();
|
|
||||||
hash = hash * 31 + insn.getIndex();
|
|
||||||
hash = hash * 31 + insn.getTarget();
|
|
||||||
hash = hash * 31 + insn.getA();
|
|
||||||
hash = hash * 31 + insn.getB();
|
|
||||||
hash = hash * 31 + insn.getC();
|
|
||||||
hash = hash * 31 + insn.getD();
|
|
||||||
hash = hash * 31 + insn.getE();
|
|
||||||
return hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
private InsnNode decode(DecodedInstruction insn, int offset) throws DecodeException {
|
private InsnNode decode(DecodedInstruction insn, int offset) throws DecodeException {
|
||||||
switch (insn.getOpcode()) {
|
switch (insn.getOpcode()) {
|
||||||
case Opcodes.NOP:
|
case Opcodes.NOP:
|
||||||
@@ -72,11 +74,11 @@ public class InsnDecoder {
|
|||||||
case Opcodes.FILL_ARRAY_DATA_PAYLOAD:
|
case Opcodes.FILL_ARRAY_DATA_PAYLOAD:
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
// move-result will be process in invoke and filled-new-array instructions
|
// move-result will be process in invoke and filled-new-array instructions
|
||||||
case Opcodes.MOVE_RESULT:
|
case Opcodes.MOVE_RESULT:
|
||||||
case Opcodes.MOVE_RESULT_WIDE:
|
case Opcodes.MOVE_RESULT_WIDE:
|
||||||
case Opcodes.MOVE_RESULT_OBJECT:
|
case Opcodes.MOVE_RESULT_OBJECT:
|
||||||
return new InsnNode(method, InsnType.NOP, 0);
|
return new InsnNode(InsnType.NOP, 0);
|
||||||
|
|
||||||
case Opcodes.CONST:
|
case Opcodes.CONST:
|
||||||
case Opcodes.CONST_4:
|
case Opcodes.CONST_4:
|
||||||
@@ -94,13 +96,13 @@ public class InsnDecoder {
|
|||||||
|
|
||||||
case Opcodes.CONST_STRING:
|
case Opcodes.CONST_STRING:
|
||||||
case Opcodes.CONST_STRING_JUMBO: {
|
case Opcodes.CONST_STRING_JUMBO: {
|
||||||
InsnNode node = new IndexInsnNode(method, InsnType.CONST, dex.getString(insn.getIndex()), 0);
|
InsnNode node = new ConstStringNode(dex.getString(insn.getIndex()));
|
||||||
node.setResult(InsnArg.reg(insn, 0, ArgType.STRING));
|
node.setResult(InsnArg.reg(insn, 0, ArgType.STRING));
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
case Opcodes.CONST_CLASS: {
|
case Opcodes.CONST_CLASS: {
|
||||||
InsnNode node = new IndexInsnNode(method, InsnType.CONST, dex.getType(insn.getIndex()), 0);
|
InsnNode node = new ConstClassNode(dex.getType(insn.getIndex()));
|
||||||
node.setResult(InsnArg.reg(insn, 0, ArgType.CLASS));
|
node.setResult(InsnArg.reg(insn, 0, ArgType.CLASS));
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
@@ -144,20 +146,15 @@ public class InsnDecoder {
|
|||||||
|
|
||||||
case Opcodes.ADD_INT_LIT8:
|
case Opcodes.ADD_INT_LIT8:
|
||||||
case Opcodes.ADD_INT_LIT16:
|
case Opcodes.ADD_INT_LIT16:
|
||||||
return arith_lit(insn, ArithOp.ADD, ArgType.INT);
|
return arithLit(insn, ArithOp.ADD, ArgType.INT);
|
||||||
|
|
||||||
case Opcodes.SUB_INT:
|
case Opcodes.SUB_INT:
|
||||||
case Opcodes.SUB_INT_2ADDR:
|
case Opcodes.SUB_INT_2ADDR:
|
||||||
return arith(insn, ArithOp.SUB, ArgType.INT);
|
return arith(insn, ArithOp.SUB, ArgType.INT);
|
||||||
|
|
||||||
case Opcodes.RSUB_INT:
|
|
||||||
return new ArithNode(method, ArithOp.SUB,
|
|
||||||
InsnArg.reg(insn, 0, ArgType.INT),
|
|
||||||
InsnArg.reg(insn, 2, ArgType.INT),
|
|
||||||
InsnArg.reg(insn, 1, ArgType.INT));
|
|
||||||
|
|
||||||
case Opcodes.RSUB_INT_LIT8:
|
case Opcodes.RSUB_INT_LIT8:
|
||||||
return new ArithNode(method, ArithOp.SUB,
|
case Opcodes.RSUB_INT: // LIT16
|
||||||
|
return new ArithNode(ArithOp.SUB,
|
||||||
InsnArg.reg(insn, 0, ArgType.INT),
|
InsnArg.reg(insn, 0, ArgType.INT),
|
||||||
InsnArg.lit(insn, ArgType.INT),
|
InsnArg.lit(insn, ArgType.INT),
|
||||||
InsnArg.reg(insn, 1, ArgType.INT));
|
InsnArg.reg(insn, 1, ArgType.INT));
|
||||||
@@ -192,7 +189,7 @@ public class InsnDecoder {
|
|||||||
|
|
||||||
case Opcodes.MUL_INT_LIT8:
|
case Opcodes.MUL_INT_LIT8:
|
||||||
case Opcodes.MUL_INT_LIT16:
|
case Opcodes.MUL_INT_LIT16:
|
||||||
return arith_lit(insn, ArithOp.MUL, ArgType.INT);
|
return arithLit(insn, ArithOp.MUL, ArgType.INT);
|
||||||
|
|
||||||
case Opcodes.DIV_INT:
|
case Opcodes.DIV_INT:
|
||||||
case Opcodes.DIV_INT_2ADDR:
|
case Opcodes.DIV_INT_2ADDR:
|
||||||
@@ -228,11 +225,11 @@ public class InsnDecoder {
|
|||||||
|
|
||||||
case Opcodes.DIV_INT_LIT8:
|
case Opcodes.DIV_INT_LIT8:
|
||||||
case Opcodes.DIV_INT_LIT16:
|
case Opcodes.DIV_INT_LIT16:
|
||||||
return arith_lit(insn, ArithOp.DIV, ArgType.INT);
|
return arithLit(insn, ArithOp.DIV, ArgType.INT);
|
||||||
|
|
||||||
case Opcodes.REM_INT_LIT8:
|
case Opcodes.REM_INT_LIT8:
|
||||||
case Opcodes.REM_INT_LIT16:
|
case Opcodes.REM_INT_LIT16:
|
||||||
return arith_lit(insn, ArithOp.REM, ArgType.INT);
|
return arithLit(insn, ArithOp.REM, ArgType.INT);
|
||||||
|
|
||||||
case Opcodes.AND_INT:
|
case Opcodes.AND_INT:
|
||||||
case Opcodes.AND_INT_2ADDR:
|
case Opcodes.AND_INT_2ADDR:
|
||||||
@@ -240,11 +237,11 @@ public class InsnDecoder {
|
|||||||
|
|
||||||
case Opcodes.AND_INT_LIT8:
|
case Opcodes.AND_INT_LIT8:
|
||||||
case Opcodes.AND_INT_LIT16:
|
case Opcodes.AND_INT_LIT16:
|
||||||
return arith_lit(insn, ArithOp.AND, ArgType.INT);
|
return arithLit(insn, ArithOp.AND, ArgType.INT);
|
||||||
|
|
||||||
case Opcodes.XOR_INT_LIT8:
|
case Opcodes.XOR_INT_LIT8:
|
||||||
case Opcodes.XOR_INT_LIT16:
|
case Opcodes.XOR_INT_LIT16:
|
||||||
return arith_lit(insn, ArithOp.XOR, ArgType.INT);
|
return arithLit(insn, ArithOp.XOR, ArgType.INT);
|
||||||
|
|
||||||
case Opcodes.AND_LONG:
|
case Opcodes.AND_LONG:
|
||||||
case Opcodes.AND_LONG_2ADDR:
|
case Opcodes.AND_LONG_2ADDR:
|
||||||
@@ -256,7 +253,7 @@ public class InsnDecoder {
|
|||||||
|
|
||||||
case Opcodes.OR_INT_LIT8:
|
case Opcodes.OR_INT_LIT8:
|
||||||
case Opcodes.OR_INT_LIT16:
|
case Opcodes.OR_INT_LIT16:
|
||||||
return arith_lit(insn, ArithOp.OR, ArgType.INT);
|
return arithLit(insn, ArithOp.OR, ArgType.INT);
|
||||||
|
|
||||||
case Opcodes.XOR_INT:
|
case Opcodes.XOR_INT:
|
||||||
case Opcodes.XOR_INT_2ADDR:
|
case Opcodes.XOR_INT_2ADDR:
|
||||||
@@ -295,11 +292,11 @@ public class InsnDecoder {
|
|||||||
return arith(insn, ArithOp.SHR, ArgType.LONG);
|
return arith(insn, ArithOp.SHR, ArgType.LONG);
|
||||||
|
|
||||||
case Opcodes.SHL_INT_LIT8:
|
case Opcodes.SHL_INT_LIT8:
|
||||||
return arith_lit(insn, ArithOp.SHL, ArgType.INT);
|
return arithLit(insn, ArithOp.SHL, ArgType.INT);
|
||||||
case Opcodes.SHR_INT_LIT8:
|
case Opcodes.SHR_INT_LIT8:
|
||||||
return arith_lit(insn, ArithOp.SHR, ArgType.INT);
|
return arithLit(insn, ArithOp.SHR, ArgType.INT);
|
||||||
case Opcodes.USHR_INT_LIT8:
|
case Opcodes.USHR_INT_LIT8:
|
||||||
return arith_lit(insn, ArithOp.USHR, ArgType.INT);
|
return arithLit(insn, ArithOp.USHR, ArgType.INT);
|
||||||
|
|
||||||
case Opcodes.NEG_INT:
|
case Opcodes.NEG_INT:
|
||||||
return neg(insn, ArgType.INT);
|
return neg(insn, ArgType.INT);
|
||||||
@@ -346,27 +343,27 @@ public class InsnDecoder {
|
|||||||
|
|
||||||
case Opcodes.IF_EQ:
|
case Opcodes.IF_EQ:
|
||||||
case Opcodes.IF_EQZ:
|
case Opcodes.IF_EQZ:
|
||||||
return new IfNode(method, insn, IfOp.EQ);
|
return new IfNode(insn, IfOp.EQ);
|
||||||
|
|
||||||
case Opcodes.IF_NE:
|
case Opcodes.IF_NE:
|
||||||
case Opcodes.IF_NEZ:
|
case Opcodes.IF_NEZ:
|
||||||
return new IfNode(method, insn, IfOp.NE);
|
return new IfNode(insn, IfOp.NE);
|
||||||
|
|
||||||
case Opcodes.IF_GT:
|
case Opcodes.IF_GT:
|
||||||
case Opcodes.IF_GTZ:
|
case Opcodes.IF_GTZ:
|
||||||
return new IfNode(method, insn, IfOp.GT);
|
return new IfNode(insn, IfOp.GT);
|
||||||
|
|
||||||
case Opcodes.IF_GE:
|
case Opcodes.IF_GE:
|
||||||
case Opcodes.IF_GEZ:
|
case Opcodes.IF_GEZ:
|
||||||
return new IfNode(method, insn, IfOp.GE);
|
return new IfNode(insn, IfOp.GE);
|
||||||
|
|
||||||
case Opcodes.IF_LT:
|
case Opcodes.IF_LT:
|
||||||
case Opcodes.IF_LTZ:
|
case Opcodes.IF_LTZ:
|
||||||
return new IfNode(method, insn, IfOp.LT);
|
return new IfNode(insn, IfOp.LT);
|
||||||
|
|
||||||
case Opcodes.IF_LE:
|
case Opcodes.IF_LE:
|
||||||
case Opcodes.IF_LEZ:
|
case Opcodes.IF_LEZ:
|
||||||
return new IfNode(method, insn, IfOp.LE);
|
return new IfNode(insn, IfOp.LE);
|
||||||
|
|
||||||
case Opcodes.CMP_LONG:
|
case Opcodes.CMP_LONG:
|
||||||
return cmp(insn, InsnType.CMP_L, ArgType.LONG);
|
return cmp(insn, InsnType.CMP_L, ArgType.LONG);
|
||||||
@@ -383,7 +380,7 @@ public class InsnDecoder {
|
|||||||
case Opcodes.GOTO:
|
case Opcodes.GOTO:
|
||||||
case Opcodes.GOTO_16:
|
case Opcodes.GOTO_16:
|
||||||
case Opcodes.GOTO_32:
|
case Opcodes.GOTO_32:
|
||||||
return new GotoNode(method, insn.getTarget());
|
return new GotoNode(insn.getTarget());
|
||||||
|
|
||||||
case Opcodes.THROW:
|
case Opcodes.THROW:
|
||||||
return insn(InsnType.THROW, null,
|
return insn(InsnType.THROW, null,
|
||||||
@@ -394,17 +391,17 @@ public class InsnDecoder {
|
|||||||
InsnArg.reg(insn, 0, ArgType.unknown(PrimitiveType.OBJECT)));
|
InsnArg.reg(insn, 0, ArgType.unknown(PrimitiveType.OBJECT)));
|
||||||
|
|
||||||
case Opcodes.RETURN_VOID:
|
case Opcodes.RETURN_VOID:
|
||||||
return new InsnNode(method, InsnType.RETURN, 0);
|
return new InsnNode(InsnType.RETURN, 0);
|
||||||
|
|
||||||
case Opcodes.RETURN:
|
case Opcodes.RETURN:
|
||||||
case Opcodes.RETURN_WIDE:
|
case Opcodes.RETURN_WIDE:
|
||||||
case Opcodes.RETURN_OBJECT:
|
case Opcodes.RETURN_OBJECT:
|
||||||
return insn(InsnType.RETURN,
|
return insn(InsnType.RETURN,
|
||||||
null,
|
null,
|
||||||
InsnArg.reg(insn, 0, method.getMethodInfo().getReturnType()));
|
InsnArg.reg(insn, 0, method.getReturnType()));
|
||||||
|
|
||||||
case Opcodes.INSTANCE_OF: {
|
case Opcodes.INSTANCE_OF: {
|
||||||
InsnNode node = new IndexInsnNode(method, InsnType.INSTANCE_OF, dex.getType(insn.getIndex()), 1);
|
InsnNode node = new IndexInsnNode(InsnType.INSTANCE_OF, dex.getType(insn.getIndex()), 1);
|
||||||
node.setResult(InsnArg.reg(insn, 0, ArgType.BOOLEAN));
|
node.setResult(InsnArg.reg(insn, 0, ArgType.BOOLEAN));
|
||||||
node.addArg(InsnArg.reg(insn, 1, ArgType.UNKNOWN_OBJECT));
|
node.addArg(InsnArg.reg(insn, 1, ArgType.UNKNOWN_OBJECT));
|
||||||
return node;
|
return node;
|
||||||
@@ -412,7 +409,7 @@ public class InsnDecoder {
|
|||||||
|
|
||||||
case Opcodes.CHECK_CAST: {
|
case Opcodes.CHECK_CAST: {
|
||||||
ArgType castType = dex.getType(insn.getIndex());
|
ArgType castType = dex.getType(insn.getIndex());
|
||||||
InsnNode node = new IndexInsnNode(method, InsnType.CHECK_CAST, castType, 1);
|
InsnNode node = new IndexInsnNode(InsnType.CHECK_CAST, castType, 1);
|
||||||
node.setResult(InsnArg.reg(insn, 0, castType));
|
node.setResult(InsnArg.reg(insn, 0, castType));
|
||||||
node.addArg(InsnArg.reg(insn, 0, ArgType.UNKNOWN_OBJECT));
|
node.addArg(InsnArg.reg(insn, 0, ArgType.UNKNOWN_OBJECT));
|
||||||
return node;
|
return node;
|
||||||
@@ -426,7 +423,7 @@ public class InsnDecoder {
|
|||||||
case Opcodes.IGET_WIDE:
|
case Opcodes.IGET_WIDE:
|
||||||
case Opcodes.IGET_OBJECT: {
|
case Opcodes.IGET_OBJECT: {
|
||||||
FieldInfo field = FieldInfo.fromDex(dex, insn.getIndex());
|
FieldInfo field = FieldInfo.fromDex(dex, insn.getIndex());
|
||||||
InsnNode node = new IndexInsnNode(method, InsnType.IGET, field, 1);
|
InsnNode node = new IndexInsnNode(InsnType.IGET, field, 1);
|
||||||
node.setResult(InsnArg.reg(insn, 0, field.getType()));
|
node.setResult(InsnArg.reg(insn, 0, field.getType()));
|
||||||
node.addArg(InsnArg.reg(insn, 1, field.getDeclClass().getType()));
|
node.addArg(InsnArg.reg(insn, 1, field.getDeclClass().getType()));
|
||||||
return node;
|
return node;
|
||||||
@@ -440,7 +437,7 @@ public class InsnDecoder {
|
|||||||
case Opcodes.IPUT_WIDE:
|
case Opcodes.IPUT_WIDE:
|
||||||
case Opcodes.IPUT_OBJECT: {
|
case Opcodes.IPUT_OBJECT: {
|
||||||
FieldInfo field = FieldInfo.fromDex(dex, insn.getIndex());
|
FieldInfo field = FieldInfo.fromDex(dex, insn.getIndex());
|
||||||
InsnNode node = new IndexInsnNode(method, InsnType.IPUT, field, 2);
|
InsnNode node = new IndexInsnNode(InsnType.IPUT, field, 2);
|
||||||
node.addArg(InsnArg.reg(insn, 0, field.getType()));
|
node.addArg(InsnArg.reg(insn, 0, field.getType()));
|
||||||
node.addArg(InsnArg.reg(insn, 1, field.getDeclClass().getType()));
|
node.addArg(InsnArg.reg(insn, 1, field.getDeclClass().getType()));
|
||||||
return node;
|
return node;
|
||||||
@@ -454,7 +451,7 @@ public class InsnDecoder {
|
|||||||
case Opcodes.SGET_WIDE:
|
case Opcodes.SGET_WIDE:
|
||||||
case Opcodes.SGET_OBJECT: {
|
case Opcodes.SGET_OBJECT: {
|
||||||
FieldInfo field = FieldInfo.fromDex(dex, insn.getIndex());
|
FieldInfo field = FieldInfo.fromDex(dex, insn.getIndex());
|
||||||
InsnNode node = new IndexInsnNode(method, InsnType.SGET, field, 0);
|
InsnNode node = new IndexInsnNode(InsnType.SGET, field, 0);
|
||||||
node.setResult(InsnArg.reg(insn, 0, field.getType()));
|
node.setResult(InsnArg.reg(insn, 0, field.getType()));
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
@@ -467,15 +464,15 @@ public class InsnDecoder {
|
|||||||
case Opcodes.SPUT_WIDE:
|
case Opcodes.SPUT_WIDE:
|
||||||
case Opcodes.SPUT_OBJECT: {
|
case Opcodes.SPUT_OBJECT: {
|
||||||
FieldInfo field = FieldInfo.fromDex(dex, insn.getIndex());
|
FieldInfo field = FieldInfo.fromDex(dex, insn.getIndex());
|
||||||
InsnNode node = new IndexInsnNode(method, InsnType.SPUT, field, 1);
|
InsnNode node = new IndexInsnNode(InsnType.SPUT, field, 1);
|
||||||
node.addArg(InsnArg.reg(insn, 0, field.getType()));
|
node.addArg(InsnArg.reg(insn, 0, field.getType()));
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
case Opcodes.ARRAY_LENGTH: {
|
case Opcodes.ARRAY_LENGTH: {
|
||||||
InsnNode node = new InsnNode(method, InsnType.ARRAY_LENGTH, 1);
|
InsnNode node = new InsnNode(InsnType.ARRAY_LENGTH, 1);
|
||||||
node.setResult(InsnArg.reg(insn, 0, ArgType.INT));
|
node.setResult(InsnArg.reg(insn, 0, ArgType.INT));
|
||||||
node.addArg(InsnArg.reg(insn, 1, ArgType.unknown(PrimitiveType.ARRAY)));
|
node.addArg(InsnArg.reg(insn, 1, ArgType.array(ArgType.UNKNOWN)));
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -538,8 +535,9 @@ public class InsnDecoder {
|
|||||||
InsnArg.reg(insn, 0, dex.getType(insn.getIndex())));
|
InsnArg.reg(insn, 0, dex.getType(insn.getIndex())));
|
||||||
|
|
||||||
case Opcodes.NEW_ARRAY:
|
case Opcodes.NEW_ARRAY:
|
||||||
return insn(InsnType.NEW_ARRAY,
|
ArgType arrType = dex.getType(insn.getIndex());
|
||||||
InsnArg.reg(insn, 0, dex.getType(insn.getIndex())),
|
return new NewArrayNode(arrType,
|
||||||
|
InsnArg.reg(insn, 0, arrType),
|
||||||
InsnArg.reg(insn, 1, ArgType.INT));
|
InsnArg.reg(insn, 1, ArgType.INT));
|
||||||
|
|
||||||
case Opcodes.FILL_ARRAY_DATA:
|
case Opcodes.FILL_ARRAY_DATA:
|
||||||
@@ -573,56 +571,66 @@ public class InsnDecoder {
|
|||||||
private InsnNode decodeSwitch(DecodedInstruction insn, int offset, boolean packed) {
|
private InsnNode decodeSwitch(DecodedInstruction insn, int offset, boolean packed) {
|
||||||
int payloadOffset = insn.getTarget();
|
int payloadOffset = insn.getTarget();
|
||||||
DecodedInstruction payload = insnArr[payloadOffset];
|
DecodedInstruction payload = insnArr[payloadOffset];
|
||||||
int[] keys;
|
Object[] keys;
|
||||||
int[] targets;
|
int[] targets;
|
||||||
if (packed) {
|
if (packed) {
|
||||||
PackedSwitchPayloadDecodedInstruction ps = (PackedSwitchPayloadDecodedInstruction) payload;
|
PackedSwitchPayloadDecodedInstruction ps = (PackedSwitchPayloadDecodedInstruction) payload;
|
||||||
targets = ps.getTargets();
|
targets = ps.getTargets();
|
||||||
keys = new int[targets.length];
|
keys = new Object[targets.length];
|
||||||
int k = ps.getFirstKey();
|
int k = ps.getFirstKey();
|
||||||
for (int i = 0; i < keys.length; i++)
|
for (int i = 0; i < keys.length; i++) {
|
||||||
keys[i] = k++;
|
keys[i] = k++;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
SparseSwitchPayloadDecodedInstruction ss = (SparseSwitchPayloadDecodedInstruction) payload;
|
SparseSwitchPayloadDecodedInstruction ss = (SparseSwitchPayloadDecodedInstruction) payload;
|
||||||
targets = ss.getTargets();
|
targets = ss.getTargets();
|
||||||
keys = ss.getKeys();
|
keys = new Object[targets.length];
|
||||||
|
for (int i = 0; i < keys.length; i++) {
|
||||||
|
keys[i] = ss.getKeys()[i];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// convert from relative to absolute offsets
|
// convert from relative to absolute offsets
|
||||||
for (int i = 0; i < targets.length; i++) {
|
for (int i = 0; i < targets.length; i++) {
|
||||||
targets[i] = targets[i] - payloadOffset + offset;
|
targets[i] = targets[i] - payloadOffset + offset;
|
||||||
}
|
}
|
||||||
int nextOffset = getNextInsnOffset(insnArr, offset);
|
int nextOffset = getNextInsnOffset(insnArr, offset);
|
||||||
return new SwitchNode(method, InsnArg.reg(insn, 0, ArgType.NARROW),
|
return new SwitchNode(InsnArg.reg(insn, 0, ArgType.NARROW), keys, targets, nextOffset);
|
||||||
keys, targets, nextOffset);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private InsnNode fillArray(DecodedInstruction insn) {
|
private InsnNode fillArray(DecodedInstruction insn) {
|
||||||
DecodedInstruction payload = insnArr[insn.getTarget()];
|
DecodedInstruction payload = insnArr[insn.getTarget()];
|
||||||
return new FillArrayOp(method, insn.getA(), (FillArrayDataPayloadDecodedInstruction) payload);
|
return new FillArrayNode(insn.getA(), (FillArrayDataPayloadDecodedInstruction) payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
private InsnNode filledNewArray(DecodedInstruction insn, int offset, boolean isRange) {
|
private InsnNode filledNewArray(DecodedInstruction insn, int offset, boolean isRange) {
|
||||||
int resReg = getMoveResultRegister(insnArr, offset);
|
int resReg = getMoveResultRegister(insnArr, offset);
|
||||||
ArgType arrType = dex.getType(insn.getIndex());
|
ArgType arrType = dex.getType(insn.getIndex());
|
||||||
ArgType elType = arrType.getArrayElement();
|
ArgType elType = arrType.getArrayElement();
|
||||||
InsnArg[] regs = new InsnArg[insn.getRegisterCount()];
|
boolean typeImmutable = elType.isPrimitive();
|
||||||
|
int regsCount = insn.getRegisterCount();
|
||||||
|
InsnArg[] regs = new InsnArg[regsCount];
|
||||||
if (isRange) {
|
if (isRange) {
|
||||||
int r = insn.getA();
|
int r = insn.getA();
|
||||||
for (int i = 0; i < insn.getRegisterCount(); i++) {
|
for (int i = 0; i < regsCount; i++) {
|
||||||
regs[i] = InsnArg.reg(r, elType);
|
regs[i] = InsnArg.reg(r, elType, typeImmutable);
|
||||||
r++;
|
r++;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (int i = 0; i < insn.getRegisterCount(); i++)
|
for (int i = 0; i < regsCount; i++) {
|
||||||
regs[i] = InsnArg.reg(insn, i, elType);
|
int regNum = InsnUtils.getArg(insn, i);
|
||||||
|
regs[i] = InsnArg.reg(regNum, elType, typeImmutable);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return insn(InsnType.FILLED_NEW_ARRAY,
|
InsnNode node = new FilledNewArrayNode(elType, regs.length);
|
||||||
resReg == -1 ? null : InsnArg.reg(resReg, arrType),
|
node.setResult(resReg == -1 ? null : InsnArg.reg(resReg, arrType));
|
||||||
regs);
|
for (InsnArg arg : regs) {
|
||||||
|
node.addArg(arg);
|
||||||
|
}
|
||||||
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
private InsnNode cmp(DecodedInstruction insn, InsnType itype, ArgType argType) {
|
private InsnNode cmp(DecodedInstruction insn, InsnType itype, ArgType argType) {
|
||||||
InsnNode inode = new InsnNode(method, itype, 2);
|
InsnNode inode = new InsnNode(itype, 2);
|
||||||
inode.setResult(InsnArg.reg(insn, 0, ArgType.INT));
|
inode.setResult(InsnArg.reg(insn, 0, ArgType.INT));
|
||||||
inode.addArg(InsnArg.reg(insn, 1, argType));
|
inode.addArg(InsnArg.reg(insn, 1, argType));
|
||||||
inode.addArg(InsnArg.reg(insn, 2, argType));
|
inode.addArg(InsnArg.reg(insn, 2, argType));
|
||||||
@@ -630,7 +638,7 @@ public class InsnDecoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private InsnNode cast(DecodedInstruction insn, ArgType from, ArgType to) {
|
private InsnNode cast(DecodedInstruction insn, ArgType from, ArgType to) {
|
||||||
InsnNode inode = new IndexInsnNode(method, InsnType.CAST, to, 1);
|
InsnNode inode = new IndexInsnNode(InsnType.CAST, to, 1);
|
||||||
inode.setResult(InsnArg.reg(insn, 0, to));
|
inode.setResult(InsnArg.reg(insn, 0, to));
|
||||||
inode.addArg(InsnArg.reg(insn, 1, from));
|
inode.addArg(InsnArg.reg(insn, 1, from));
|
||||||
return inode;
|
return inode;
|
||||||
@@ -638,11 +646,12 @@ public class InsnDecoder {
|
|||||||
|
|
||||||
private InsnNode invoke(DecodedInstruction insn, int offset, InvokeType type, boolean isRange) {
|
private InsnNode invoke(DecodedInstruction insn, int offset, InvokeType type, boolean isRange) {
|
||||||
int resReg = getMoveResultRegister(insnArr, offset);
|
int resReg = getMoveResultRegister(insnArr, offset);
|
||||||
return new InvokeNode(method, insn, type, isRange, resReg);
|
MethodInfo mth = MethodInfo.fromDex(dex, insn.getIndex());
|
||||||
|
return new InvokeNode(mth, insn, type, isRange, resReg);
|
||||||
}
|
}
|
||||||
|
|
||||||
private InsnNode arrayGet(DecodedInstruction insn, ArgType argType) {
|
private InsnNode arrayGet(DecodedInstruction insn, ArgType argType) {
|
||||||
InsnNode inode = new InsnNode(method, InsnType.AGET, 2);
|
InsnNode inode = new InsnNode(InsnType.AGET, 2);
|
||||||
inode.setResult(InsnArg.reg(insn, 0, argType));
|
inode.setResult(InsnArg.reg(insn, 0, argType));
|
||||||
inode.addArg(InsnArg.reg(insn, 1, ArgType.unknown(PrimitiveType.ARRAY)));
|
inode.addArg(InsnArg.reg(insn, 1, ArgType.unknown(PrimitiveType.ARRAY)));
|
||||||
inode.addArg(InsnArg.reg(insn, 2, ArgType.INT));
|
inode.addArg(InsnArg.reg(insn, 2, ArgType.INT));
|
||||||
@@ -650,7 +659,7 @@ public class InsnDecoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private InsnNode arrayPut(DecodedInstruction insn, ArgType argType) {
|
private InsnNode arrayPut(DecodedInstruction insn, ArgType argType) {
|
||||||
InsnNode inode = new InsnNode(method, InsnType.APUT, 3);
|
InsnNode inode = new InsnNode(InsnType.APUT, 3);
|
||||||
inode.addArg(InsnArg.reg(insn, 1, ArgType.unknown(PrimitiveType.ARRAY)));
|
inode.addArg(InsnArg.reg(insn, 1, ArgType.unknown(PrimitiveType.ARRAY)));
|
||||||
inode.addArg(InsnArg.reg(insn, 2, ArgType.INT));
|
inode.addArg(InsnArg.reg(insn, 2, ArgType.INT));
|
||||||
inode.addArg(InsnArg.reg(insn, 0, argType));
|
inode.addArg(InsnArg.reg(insn, 0, argType));
|
||||||
@@ -658,27 +667,31 @@ public class InsnDecoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private InsnNode arith(DecodedInstruction insn, ArithOp op, ArgType type) {
|
private InsnNode arith(DecodedInstruction insn, ArithOp op, ArgType type) {
|
||||||
return new ArithNode(method, insn, op, type, false);
|
return new ArithNode(insn, op, type, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private InsnNode arith_lit(DecodedInstruction insn, ArithOp op, ArgType type) {
|
private InsnNode arithLit(DecodedInstruction insn, ArithOp op, ArgType type) {
|
||||||
return new ArithNode(method, insn, op, type, true);
|
return new ArithNode(insn, op, type, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private InsnNode neg(DecodedInstruction insn, ArgType type) {
|
private InsnNode neg(DecodedInstruction insn, ArgType type) {
|
||||||
InsnNode inode = new InsnNode(method, InsnType.NEG, 1);
|
InsnNode inode = new InsnNode(InsnType.NEG, 1);
|
||||||
inode.setResult(InsnArg.reg(insn, 0, type));
|
inode.setResult(InsnArg.reg(insn, 0, type));
|
||||||
inode.addArg(InsnArg.reg(insn, 1, type));
|
inode.addArg(InsnArg.reg(insn, 1, type));
|
||||||
return inode;
|
return inode;
|
||||||
}
|
}
|
||||||
|
|
||||||
private InsnNode insn(InsnType type, RegisterArg res, InsnArg... args) {
|
private InsnNode insn(InsnType type, RegisterArg res) {
|
||||||
InsnNode inode = new InsnNode(method, type, args == null ? 0 : args.length);
|
InsnNode node = new InsnNode(type, 0);
|
||||||
inode.setResult(res);
|
node.setResult(res);
|
||||||
if (args != null)
|
return node;
|
||||||
for (InsnArg arg : args)
|
}
|
||||||
inode.addArg(arg);
|
|
||||||
return inode;
|
private InsnNode insn(InsnType type, RegisterArg res, InsnArg arg) {
|
||||||
|
InsnNode node = new InsnNode(type, 1);
|
||||||
|
node.setResult(res);
|
||||||
|
node.addArg(arg);
|
||||||
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getMoveResultRegister(DecodedInstruction[] insnArr, int offset) {
|
private int getMoveResultRegister(DecodedInstruction[] insnArr, int offset) {
|
||||||
@@ -697,19 +710,23 @@ public class InsnDecoder {
|
|||||||
|
|
||||||
public static int getPrevInsnOffset(Object[] insnArr, int offset) {
|
public static int getPrevInsnOffset(Object[] insnArr, int offset) {
|
||||||
int i = offset - 1;
|
int i = offset - 1;
|
||||||
while (i >= 0 && insnArr[i] == null)
|
while (i >= 0 && insnArr[i] == null) {
|
||||||
i--;
|
i--;
|
||||||
if (i < 0)
|
}
|
||||||
|
if (i < 0) {
|
||||||
return -1;
|
return -1;
|
||||||
|
}
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getNextInsnOffset(Object[] insnArr, int offset) {
|
public static int getNextInsnOffset(Object[] insnArr, int offset) {
|
||||||
int i = offset + 1;
|
int i = offset + 1;
|
||||||
while (i < insnArr.length && insnArr[i] == null)
|
while (i < insnArr.length && insnArr[i] == null) {
|
||||||
i++;
|
i++;
|
||||||
if (i >= insnArr.length)
|
}
|
||||||
|
if (i >= insnArr.length) {
|
||||||
return -1;
|
return -1;
|
||||||
|
}
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+20
-5
@@ -1,9 +1,10 @@
|
|||||||
package jadx.dex.instructions;
|
package jadx.core.dex.instructions;
|
||||||
|
|
||||||
public enum InsnType {
|
public enum InsnType {
|
||||||
NOP, // replacement for removed instructions
|
|
||||||
|
|
||||||
CONST,
|
CONST,
|
||||||
|
CONST_STR,
|
||||||
|
CONST_CLASS,
|
||||||
|
|
||||||
ARITH,
|
ARITH,
|
||||||
NEG,
|
NEG,
|
||||||
@@ -46,10 +47,24 @@ public enum InsnType {
|
|||||||
|
|
||||||
INVOKE,
|
INVOKE,
|
||||||
|
|
||||||
// additional instructions
|
// *** Additional instructions ***
|
||||||
|
|
||||||
|
// replacement for removed instructions
|
||||||
|
NOP,
|
||||||
|
|
||||||
|
TERNARY,
|
||||||
CONSTRUCTOR,
|
CONSTRUCTOR,
|
||||||
|
|
||||||
BREAK,
|
BREAK,
|
||||||
CONTINUE,
|
CONTINUE,
|
||||||
TERNARY,
|
|
||||||
NEW_MULTIDIM_ARRAY // TODO: now multidimensional arrays created using Array.newInstance function
|
// strings concatenation
|
||||||
|
STR_CONCAT,
|
||||||
|
|
||||||
|
// just generate one argument
|
||||||
|
ONE_ARG,
|
||||||
|
PHI,
|
||||||
|
|
||||||
|
// TODO: now multidimensional arrays created using Array.newInstance function
|
||||||
|
NEW_MULTIDIM_ARRAY
|
||||||
}
|
}
|
||||||
+24
-13
@@ -1,12 +1,11 @@
|
|||||||
package jadx.dex.instructions;
|
package jadx.core.dex.instructions;
|
||||||
|
|
||||||
import jadx.dex.info.MethodInfo;
|
import jadx.core.dex.info.MethodInfo;
|
||||||
import jadx.dex.instructions.args.ArgType;
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
import jadx.dex.instructions.args.InsnArg;
|
import jadx.core.dex.instructions.args.InsnArg;
|
||||||
import jadx.dex.nodes.InsnNode;
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
import jadx.dex.nodes.MethodNode;
|
import jadx.core.utils.InsnUtils;
|
||||||
import jadx.utils.InsnUtils;
|
import jadx.core.utils.Utils;
|
||||||
import jadx.utils.Utils;
|
|
||||||
|
|
||||||
import com.android.dx.io.instructions.DecodedInstruction;
|
import com.android.dx.io.instructions.DecodedInstruction;
|
||||||
|
|
||||||
@@ -15,14 +14,14 @@ public class InvokeNode extends InsnNode {
|
|||||||
private final InvokeType type;
|
private final InvokeType type;
|
||||||
private final MethodInfo mth;
|
private final MethodInfo mth;
|
||||||
|
|
||||||
public InvokeNode(MethodNode method, DecodedInstruction insn, InvokeType type, boolean isRange,
|
public InvokeNode(MethodInfo mth, DecodedInstruction insn, InvokeType type, boolean isRange, int resReg) {
|
||||||
int resReg) {
|
super(InsnType.INVOKE, mth.getArgsCount() + (type != InvokeType.STATIC ? 1 : 0));
|
||||||
super(method, InsnType.INVOKE);
|
this.mth = mth;
|
||||||
this.mth = MethodInfo.fromDex(method.dex(), insn.getIndex());
|
|
||||||
this.type = type;
|
this.type = type;
|
||||||
|
|
||||||
if (resReg >= 0)
|
if (resReg >= 0) {
|
||||||
setResult(InsnArg.reg(resReg, mth.getReturnType()));
|
setResult(InsnArg.reg(resReg, mth.getReturnType()));
|
||||||
|
}
|
||||||
|
|
||||||
int k = isRange ? insn.getA() : 0;
|
int k = isRange ? insn.getA() : 0;
|
||||||
if (type != InvokeType.STATIC) {
|
if (type != InvokeType.STATIC) {
|
||||||
@@ -45,6 +44,18 @@ public class InvokeNode extends InsnNode {
|
|||||||
return mth;
|
return mth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSame(InsnNode obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(obj instanceof InvokeNode) || !super.isSame(obj)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
InvokeNode other = (InvokeNode) obj;
|
||||||
|
return type == other.type && mth.equals(other.mth);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return InsnUtils.formatOffset(offset) + ": "
|
return InsnUtils.formatOffset(offset) + ": "
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package jadx.dex.instructions;
|
package jadx.core.dex.instructions;
|
||||||
|
|
||||||
public enum InvokeType {
|
public enum InvokeType {
|
||||||
STATIC,
|
STATIC,
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package jadx.core.dex.instructions;
|
||||||
|
|
||||||
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
|
import jadx.core.dex.instructions.args.InsnArg;
|
||||||
|
import jadx.core.dex.instructions.args.RegisterArg;
|
||||||
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
public class NewArrayNode extends InsnNode {
|
||||||
|
|
||||||
|
private final ArgType arrType;
|
||||||
|
|
||||||
|
public NewArrayNode(@NotNull ArgType arrType, RegisterArg res, InsnArg size) {
|
||||||
|
super(InsnType.NEW_ARRAY, 1);
|
||||||
|
this.arrType = arrType;
|
||||||
|
setResult(res);
|
||||||
|
addArg(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArgType getArrayType() {
|
||||||
|
return arrType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSame(InsnNode obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(obj instanceof NewArrayNode) || !super.isSame(obj)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
NewArrayNode other = (NewArrayNode) obj;
|
||||||
|
return arrType == other.arrType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return super.toString() + " type: " + arrType;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
package jadx.core.dex.instructions;
|
||||||
|
|
||||||
|
import jadx.core.dex.attributes.AFlag;
|
||||||
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
|
import jadx.core.dex.instructions.args.InsnArg;
|
||||||
|
import jadx.core.dex.instructions.args.RegisterArg;
|
||||||
|
import jadx.core.dex.nodes.BlockNode;
|
||||||
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
|
import jadx.core.utils.InstructionRemover;
|
||||||
|
import jadx.core.utils.Utils;
|
||||||
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
|
import java.util.IdentityHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
public final class PhiInsn extends InsnNode {
|
||||||
|
|
||||||
|
private final Map<RegisterArg, BlockNode> blockBinds;
|
||||||
|
|
||||||
|
public PhiInsn(int regNum, int predecessors) {
|
||||||
|
super(InsnType.PHI, predecessors);
|
||||||
|
this.blockBinds = new IdentityHashMap<RegisterArg, BlockNode>(predecessors);
|
||||||
|
setResult(InsnArg.reg(regNum, ArgType.UNKNOWN));
|
||||||
|
add(AFlag.DONT_INLINE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RegisterArg bindArg(BlockNode pred) {
|
||||||
|
RegisterArg arg = InsnArg.reg(getResult().getRegNum(), getResult().getType());
|
||||||
|
bindArg(arg, pred);
|
||||||
|
return arg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void bindArg(RegisterArg arg, BlockNode pred) {
|
||||||
|
if (blockBinds.containsValue(pred)) {
|
||||||
|
throw new JadxRuntimeException("Duplicate predecessors in PHI insn: " + pred + ", " + this);
|
||||||
|
}
|
||||||
|
addArg(arg);
|
||||||
|
blockBinds.put(arg, pred);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlockNode getBlockByArg(RegisterArg arg) {
|
||||||
|
return blockBinds.get(arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<RegisterArg, BlockNode> getBlockBinds() {
|
||||||
|
return blockBinds;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NotNull
|
||||||
|
public RegisterArg getArg(int n) {
|
||||||
|
return (RegisterArg) super.getArg(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean removeArg(InsnArg arg) {
|
||||||
|
if (!(arg instanceof RegisterArg)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
RegisterArg reg = (RegisterArg) arg;
|
||||||
|
if (super.removeArg(reg)) {
|
||||||
|
blockBinds.remove(reg);
|
||||||
|
InstructionRemover.fixUsedInPhiFlag(reg);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean replaceArg(InsnArg from, InsnArg to) {
|
||||||
|
if (!(from instanceof RegisterArg) || !(to instanceof RegisterArg)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
BlockNode pred = getBlockByArg((RegisterArg) from);
|
||||||
|
if (pred == null) {
|
||||||
|
throw new JadxRuntimeException("Unknown predecessor block by arg " + from + " in PHI: " + this);
|
||||||
|
}
|
||||||
|
if (removeArg(from)) {
|
||||||
|
bindArg((RegisterArg) to, pred);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setArg(int n, InsnArg arg) {
|
||||||
|
throw new JadxRuntimeException("Unsupported operation for PHI node");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "PHI: " + getResult() + " = " + Utils.listToString(getArguments())
|
||||||
|
+ " binds: " + blockBinds;
|
||||||
|
}
|
||||||
|
}
|
||||||
+24
-10
@@ -1,20 +1,19 @@
|
|||||||
package jadx.dex.instructions;
|
package jadx.core.dex.instructions;
|
||||||
|
|
||||||
import jadx.dex.instructions.args.InsnArg;
|
import jadx.core.dex.instructions.args.InsnArg;
|
||||||
import jadx.dex.nodes.InsnNode;
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
import jadx.dex.nodes.MethodNode;
|
import jadx.core.utils.InsnUtils;
|
||||||
import jadx.utils.InsnUtils;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
public class SwitchNode extends InsnNode {
|
public class SwitchNode extends InsnNode {
|
||||||
|
|
||||||
private final int[] keys;
|
private final Object[] keys;
|
||||||
private final int[] targets;
|
private final int[] targets;
|
||||||
private final int def; // next instruction
|
private final int def; // next instruction
|
||||||
|
|
||||||
public SwitchNode(MethodNode mth, InsnArg arg, int[] keys, int[] targets, int def) {
|
public SwitchNode(InsnArg arg, Object[] keys, int[] targets, int def) {
|
||||||
super(mth, InsnType.SWITCH, 1);
|
super(InsnType.SWITCH, 1);
|
||||||
this.keys = keys;
|
this.keys = keys;
|
||||||
this.targets = targets;
|
this.targets = targets;
|
||||||
this.def = def;
|
this.def = def;
|
||||||
@@ -25,7 +24,7 @@ public class SwitchNode extends InsnNode {
|
|||||||
return keys.length;
|
return keys.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int[] getKeys() {
|
public Object[] getKeys() {
|
||||||
return keys;
|
return keys;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,14 +36,29 @@ public class SwitchNode extends InsnNode {
|
|||||||
return def;
|
return def;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSame(InsnNode obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(obj instanceof SwitchNode) || !super.isSame(obj)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
SwitchNode other = (SwitchNode) obj;
|
||||||
|
return def == other.def
|
||||||
|
&& Arrays.equals(keys, other.keys)
|
||||||
|
&& Arrays.equals(targets, other.targets);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
StringBuilder targ = new StringBuilder();
|
StringBuilder targ = new StringBuilder();
|
||||||
targ.append('[');
|
targ.append('[');
|
||||||
for (int i = 0; i < targets.length; i++) {
|
for (int i = 0; i < targets.length; i++) {
|
||||||
targ.append(InsnUtils.formatOffset(targets[i]));
|
targ.append(InsnUtils.formatOffset(targets[i]));
|
||||||
if (i < targets.length - 1)
|
if (i < targets.length - 1) {
|
||||||
targ.append(", ");
|
targ.append(", ");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
targ.append(']');
|
targ.append(']');
|
||||||
return super.toString() + " k:" + Arrays.toString(keys) + " t:" + targ;
|
return super.toString() + " k:" + Arrays.toString(keys) + " t:" + targ;
|
||||||
@@ -0,0 +1,672 @@
|
|||||||
|
package jadx.core.dex.instructions.args;
|
||||||
|
|
||||||
|
import jadx.core.Consts;
|
||||||
|
import jadx.core.dex.nodes.DexNode;
|
||||||
|
import jadx.core.dex.nodes.parser.SignatureParser;
|
||||||
|
import jadx.core.utils.Utils;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
public abstract class ArgType {
|
||||||
|
|
||||||
|
public static final ArgType INT = primitive(PrimitiveType.INT);
|
||||||
|
public static final ArgType BOOLEAN = primitive(PrimitiveType.BOOLEAN);
|
||||||
|
public static final ArgType BYTE = primitive(PrimitiveType.BYTE);
|
||||||
|
public static final ArgType SHORT = primitive(PrimitiveType.SHORT);
|
||||||
|
public static final ArgType CHAR = primitive(PrimitiveType.CHAR);
|
||||||
|
public static final ArgType FLOAT = primitive(PrimitiveType.FLOAT);
|
||||||
|
public static final ArgType DOUBLE = primitive(PrimitiveType.DOUBLE);
|
||||||
|
public static final ArgType LONG = primitive(PrimitiveType.LONG);
|
||||||
|
public static final ArgType VOID = primitive(PrimitiveType.VOID);
|
||||||
|
|
||||||
|
public static final ArgType OBJECT = object(Consts.CLASS_OBJECT);
|
||||||
|
public static final ArgType CLASS = object(Consts.CLASS_CLASS);
|
||||||
|
public static final ArgType STRING = object(Consts.CLASS_STRING);
|
||||||
|
public static final ArgType ENUM = object(Consts.CLASS_ENUM);
|
||||||
|
public static final ArgType THROWABLE = object(Consts.CLASS_THROWABLE);
|
||||||
|
|
||||||
|
public static final ArgType UNKNOWN = unknown(PrimitiveType.values());
|
||||||
|
public static final ArgType UNKNOWN_OBJECT = unknown(PrimitiveType.OBJECT, PrimitiveType.ARRAY);
|
||||||
|
|
||||||
|
public static final ArgType NARROW = unknown(
|
||||||
|
PrimitiveType.INT, PrimitiveType.FLOAT,
|
||||||
|
PrimitiveType.BOOLEAN, PrimitiveType.SHORT, PrimitiveType.BYTE, PrimitiveType.CHAR,
|
||||||
|
PrimitiveType.OBJECT, PrimitiveType.ARRAY);
|
||||||
|
|
||||||
|
public static final ArgType NARROW_NUMBERS = unknown(
|
||||||
|
PrimitiveType.INT, PrimitiveType.FLOAT,
|
||||||
|
PrimitiveType.BOOLEAN, PrimitiveType.SHORT, PrimitiveType.BYTE, PrimitiveType.CHAR);
|
||||||
|
|
||||||
|
public static final ArgType WIDE = unknown(PrimitiveType.LONG, PrimitiveType.DOUBLE);
|
||||||
|
|
||||||
|
protected int hash;
|
||||||
|
|
||||||
|
private static ArgType primitive(PrimitiveType stype) {
|
||||||
|
return new PrimitiveArg(stype);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ArgType object(String obj) {
|
||||||
|
return new ObjectType(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ArgType genericType(String type) {
|
||||||
|
return new GenericType(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ArgType wildcard() {
|
||||||
|
return new WildcardType(OBJECT, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ArgType wildcard(ArgType obj, int bound) {
|
||||||
|
return new WildcardType(obj, bound);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ArgType generic(String sign) {
|
||||||
|
return new SignatureParser(sign).consumeType();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ArgType generic(String obj, ArgType[] generics) {
|
||||||
|
return new GenericObject(obj, generics);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ArgType genericInner(ArgType genericType, String innerName, ArgType[] generics) {
|
||||||
|
return new GenericObject((GenericObject) genericType, innerName, generics);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ArgType array(ArgType vtype) {
|
||||||
|
return new ArrayArg(vtype);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ArgType unknown(PrimitiveType... types) {
|
||||||
|
return new UnknownArg(types);
|
||||||
|
}
|
||||||
|
|
||||||
|
private abstract static class KnownType extends ArgType {
|
||||||
|
|
||||||
|
private static final PrimitiveType[] EMPTY_POSSIBLES = new PrimitiveType[0];
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isTypeKnown() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean contains(PrimitiveType type) {
|
||||||
|
return getPrimitiveType() == type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ArgType selectFirst() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PrimitiveType[] getPossibleTypes() {
|
||||||
|
return EMPTY_POSSIBLES;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class PrimitiveArg extends KnownType {
|
||||||
|
private final PrimitiveType type;
|
||||||
|
|
||||||
|
public PrimitiveArg(PrimitiveType type) {
|
||||||
|
this.type = type;
|
||||||
|
this.hash = type.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PrimitiveType getPrimitiveType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isPrimitive() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean internalEquals(Object obj) {
|
||||||
|
return type == ((PrimitiveArg) obj).type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return type.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ObjectType extends KnownType {
|
||||||
|
private final String object;
|
||||||
|
|
||||||
|
public ObjectType(String obj) {
|
||||||
|
this.object = Utils.cleanObjectName(obj);
|
||||||
|
this.hash = object.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getObject() {
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isObject() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PrimitiveType getPrimitiveType() {
|
||||||
|
return PrimitiveType.OBJECT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean internalEquals(Object obj) {
|
||||||
|
return object.equals(((ObjectType) obj).object);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class GenericType extends ObjectType {
|
||||||
|
public GenericType(String obj) {
|
||||||
|
super(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isGenericType() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class WildcardType extends ObjectType {
|
||||||
|
private final ArgType type;
|
||||||
|
private final int bounds;
|
||||||
|
|
||||||
|
public WildcardType(ArgType obj, int bound) {
|
||||||
|
super(OBJECT.getObject());
|
||||||
|
this.type = obj;
|
||||||
|
this.bounds = bound;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isGeneric() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ArgType getWildcardType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return wildcard bounds:
|
||||||
|
* <ul>
|
||||||
|
* <li> 1 for upper bound (? extends A) </li>
|
||||||
|
* <li> 0 no bounds (?) </li>
|
||||||
|
* <li>-1 for lower bound (? super A) </li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getWildcardBounds() {
|
||||||
|
return bounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean internalEquals(Object obj) {
|
||||||
|
return super.internalEquals(obj)
|
||||||
|
&& bounds == ((WildcardType) obj).bounds
|
||||||
|
&& type.equals(((WildcardType) obj).type);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
if (bounds == 0) {
|
||||||
|
return "?";
|
||||||
|
}
|
||||||
|
return "? " + (bounds == -1 ? "super" : "extends") + " " + type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class GenericObject extends ObjectType {
|
||||||
|
private final ArgType[] generics;
|
||||||
|
private final GenericObject outerType;
|
||||||
|
|
||||||
|
public GenericObject(String obj, ArgType[] generics) {
|
||||||
|
super(obj);
|
||||||
|
this.outerType = null;
|
||||||
|
this.generics = generics;
|
||||||
|
this.hash = obj.hashCode() + 31 * Arrays.hashCode(generics);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GenericObject(GenericObject outerType, String innerName, ArgType[] generics) {
|
||||||
|
super(outerType.getObject() + "$" + innerName);
|
||||||
|
this.outerType = outerType;
|
||||||
|
this.generics = generics;
|
||||||
|
this.hash = outerType.hashCode() + 31 * innerName.hashCode()
|
||||||
|
+ 31 * 31 * Arrays.hashCode(generics);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isGeneric() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ArgType[] getGenericTypes() {
|
||||||
|
return generics;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ArgType getOuterType() {
|
||||||
|
return outerType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean internalEquals(Object obj) {
|
||||||
|
return super.internalEquals(obj)
|
||||||
|
&& Arrays.equals(generics, ((GenericObject) obj).generics);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return super.toString() + "<" + Utils.arrayToString(generics) + ">";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class ArrayArg extends KnownType {
|
||||||
|
public static final PrimitiveType[] ARRAY_POSSIBLES = new PrimitiveType[]{PrimitiveType.ARRAY};
|
||||||
|
private final ArgType arrayElement;
|
||||||
|
|
||||||
|
public ArrayArg(ArgType arrayElement) {
|
||||||
|
this.arrayElement = arrayElement;
|
||||||
|
this.hash = arrayElement.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ArgType getArrayElement() {
|
||||||
|
return arrayElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isArray() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PrimitiveType getPrimitiveType() {
|
||||||
|
return PrimitiveType.ARRAY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isTypeKnown() {
|
||||||
|
return arrayElement.isTypeKnown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ArgType selectFirst() {
|
||||||
|
return array(arrayElement.selectFirst());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PrimitiveType[] getPossibleTypes() {
|
||||||
|
return ARRAY_POSSIBLES;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getArrayDimension() {
|
||||||
|
return 1 + arrayElement.getArrayDimension();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ArgType getArrayRootElement() {
|
||||||
|
return arrayElement.getArrayRootElement();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean internalEquals(Object obj) {
|
||||||
|
return arrayElement.equals(((ArrayArg) obj).arrayElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return arrayElement + "[]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class UnknownArg extends ArgType {
|
||||||
|
private final PrimitiveType[] possibleTypes;
|
||||||
|
|
||||||
|
public UnknownArg(PrimitiveType[] types) {
|
||||||
|
this.possibleTypes = types;
|
||||||
|
this.hash = Arrays.hashCode(possibleTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PrimitiveType[] getPossibleTypes() {
|
||||||
|
return possibleTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isTypeKnown() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean contains(PrimitiveType type) {
|
||||||
|
for (PrimitiveType t : possibleTypes) {
|
||||||
|
if (t == type) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ArgType selectFirst() {
|
||||||
|
PrimitiveType f = possibleTypes[0];
|
||||||
|
if (contains(PrimitiveType.OBJECT)) {
|
||||||
|
return OBJECT;
|
||||||
|
} else if (contains(PrimitiveType.ARRAY)) {
|
||||||
|
return array(OBJECT);
|
||||||
|
} else {
|
||||||
|
return primitive(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean internalEquals(Object obj) {
|
||||||
|
return Arrays.equals(possibleTypes, ((UnknownArg) obj).possibleTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
if (possibleTypes.length == PrimitiveType.values().length) {
|
||||||
|
return "?";
|
||||||
|
} else {
|
||||||
|
return "?" + Arrays.toString(possibleTypes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isTypeKnown() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PrimitiveType getPrimitiveType() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPrimitive() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getObject() {
|
||||||
|
throw new UnsupportedOperationException("ArgType.getObject(), call class: " + this.getClass());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isObject() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isGeneric() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isGenericType() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArgType[] getGenericTypes() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArgType getWildcardType() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see WildcardType#getWildcardBounds()
|
||||||
|
*/
|
||||||
|
public int getWildcardBounds() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArgType getOuterType() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isArray() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getArrayDimension() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArgType getArrayElement() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArgType getArrayRootElement() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract boolean contains(PrimitiveType type);
|
||||||
|
|
||||||
|
public abstract ArgType selectFirst();
|
||||||
|
|
||||||
|
public abstract PrimitiveType[] getPossibleTypes();
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static ArgType merge(@Nullable DexNode dex, ArgType a, ArgType b) {
|
||||||
|
if (a == null || b == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (a.equals(b)) {
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
ArgType res = mergeInternal(dex, a, b);
|
||||||
|
if (res == null) {
|
||||||
|
res = mergeInternal(dex, b, a); // swap
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ArgType mergeInternal(@Nullable DexNode dex, ArgType a, ArgType b) {
|
||||||
|
if (a == UNKNOWN) {
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
if (a.isArray()) {
|
||||||
|
return mergeArrays(dex, (ArrayArg) a, b);
|
||||||
|
} else if (b.isArray()) {
|
||||||
|
return mergeArrays(dex, (ArrayArg) b, a);
|
||||||
|
}
|
||||||
|
if (!a.isTypeKnown()) {
|
||||||
|
if (b.isTypeKnown()) {
|
||||||
|
if (a.contains(b.getPrimitiveType())) {
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
// both types unknown
|
||||||
|
List<PrimitiveType> types = new ArrayList<PrimitiveType>();
|
||||||
|
for (PrimitiveType type : a.getPossibleTypes()) {
|
||||||
|
if (b.contains(type)) {
|
||||||
|
types.add(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (types.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (types.size() == 1) {
|
||||||
|
PrimitiveType nt = types.get(0);
|
||||||
|
if (nt == PrimitiveType.OBJECT || nt == PrimitiveType.ARRAY) {
|
||||||
|
return unknown(nt);
|
||||||
|
} else {
|
||||||
|
return primitive(nt);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return unknown(types.toArray(new PrimitiveType[types.size()]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (a.isGenericType()) {
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
if (b.isGenericType()) {
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a.isObject() && b.isObject()) {
|
||||||
|
String aObj = a.getObject();
|
||||||
|
String bObj = b.getObject();
|
||||||
|
if (aObj.equals(bObj)) {
|
||||||
|
return a.getGenericTypes() != null ? a : b;
|
||||||
|
}
|
||||||
|
if (aObj.equals(Consts.CLASS_OBJECT)) {
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
if (bObj.equals(Consts.CLASS_OBJECT)) {
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
if (dex == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String obj = dex.root().getClsp().getCommonAncestor(aObj, bObj);
|
||||||
|
return obj == null ? null : object(obj);
|
||||||
|
}
|
||||||
|
if (a.isPrimitive() && b.isPrimitive() && a.getRegCount() == b.getRegCount()) {
|
||||||
|
return primitive(PrimitiveType.getSmaller(a.getPrimitiveType(), b.getPrimitiveType()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ArgType mergeArrays(DexNode dex, ArrayArg array, ArgType b) {
|
||||||
|
if (b.isArray()) {
|
||||||
|
ArgType ea = array.getArrayElement();
|
||||||
|
ArgType eb = b.getArrayElement();
|
||||||
|
if (ea.isPrimitive() && eb.isPrimitive()) {
|
||||||
|
return OBJECT;
|
||||||
|
}
|
||||||
|
ArgType res = merge(dex, ea, eb);
|
||||||
|
return res == null ? null : array(res);
|
||||||
|
}
|
||||||
|
if (b.contains(PrimitiveType.ARRAY)) {
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
if (b.equals(OBJECT)) {
|
||||||
|
return OBJECT;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isCastNeeded(DexNode dex, ArgType from, ArgType to) {
|
||||||
|
if (from.equals(to)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (from.isObject() && to.isObject()
|
||||||
|
&& dex.root().getClsp().isImplements(from.getObject(), to.getObject())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isInstanceOf(DexNode dex, ArgType type, ArgType of) {
|
||||||
|
if (type.equals(of)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!type.isObject() || !of.isObject()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return dex.root().getClsp().isImplements(type.getObject(), of.getObject());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ArgType parse(String type) {
|
||||||
|
char f = type.charAt(0);
|
||||||
|
switch (f) {
|
||||||
|
case 'L':
|
||||||
|
return object(type);
|
||||||
|
case 'T':
|
||||||
|
return genericType(type.substring(1, type.length() - 1));
|
||||||
|
case '[':
|
||||||
|
return array(parse(type.substring(1)));
|
||||||
|
default:
|
||||||
|
return parse(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ArgType parse(char f) {
|
||||||
|
switch (f) {
|
||||||
|
case 'Z':
|
||||||
|
return BOOLEAN;
|
||||||
|
case 'B':
|
||||||
|
return BYTE;
|
||||||
|
case 'C':
|
||||||
|
return CHAR;
|
||||||
|
case 'S':
|
||||||
|
return SHORT;
|
||||||
|
case 'I':
|
||||||
|
return INT;
|
||||||
|
case 'J':
|
||||||
|
return LONG;
|
||||||
|
case 'F':
|
||||||
|
return FLOAT;
|
||||||
|
case 'D':
|
||||||
|
return DOUBLE;
|
||||||
|
case 'V':
|
||||||
|
return VOID;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRegCount() {
|
||||||
|
if (isPrimitive()) {
|
||||||
|
PrimitiveType type = getPrimitiveType();
|
||||||
|
if (type == PrimitiveType.LONG || type == PrimitiveType.DOUBLE) {
|
||||||
|
return 2;
|
||||||
|
} else {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!isTypeKnown()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "ARG_TYPE";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract boolean internalEquals(Object obj);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (obj == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (hash != obj.hashCode()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (getClass() != obj.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return internalEquals(obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package jadx.core.dex.instructions.args;
|
||||||
|
|
||||||
|
import jadx.core.dex.info.FieldInfo;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
// TODO: don't extend RegisterArg (now used as a result of instruction)
|
||||||
|
public final class FieldArg extends RegisterArg {
|
||||||
|
|
||||||
|
private final FieldInfo field;
|
||||||
|
// instArg equal 'null' for static fields
|
||||||
|
@Nullable
|
||||||
|
private final InsnArg instArg;
|
||||||
|
|
||||||
|
public FieldArg(FieldInfo field, @Nullable InsnArg reg) {
|
||||||
|
super(-1);
|
||||||
|
this.instArg = reg;
|
||||||
|
this.field = field;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FieldInfo getField() {
|
||||||
|
return field;
|
||||||
|
}
|
||||||
|
|
||||||
|
public InsnArg getInstanceArg() {
|
||||||
|
return instArg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isStatic() {
|
||||||
|
return instArg == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isField() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isRegister() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setType(ArgType type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(obj instanceof FieldArg) || !super.equals(obj)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
FieldArg fieldArg = (FieldArg) obj;
|
||||||
|
if (!field.equals(fieldArg.field)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (instArg != null ? !instArg.equals(fieldArg.instArg) : fieldArg.instArg != null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = super.hashCode();
|
||||||
|
result = 31 * result + field.hashCode();
|
||||||
|
result = 31 * result + (instArg != null ? instArg.hashCode() : 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "(" + field + ")";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
package jadx.core.dex.instructions.args;
|
||||||
|
|
||||||
|
import jadx.core.dex.attributes.AFlag;
|
||||||
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
|
import jadx.core.utils.InsnUtils;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.android.dx.io.instructions.DecodedInstruction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instruction argument,
|
||||||
|
* argument can be register, literal or instruction
|
||||||
|
*/
|
||||||
|
public abstract class InsnArg extends Typed {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(InsnArg.class);
|
||||||
|
|
||||||
|
@Nullable("Null for method arguments")
|
||||||
|
protected InsnNode parentInsn;
|
||||||
|
|
||||||
|
public static RegisterArg reg(int regNum, ArgType type) {
|
||||||
|
return new RegisterArg(regNum, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RegisterArg reg(DecodedInstruction insn, int argNum, ArgType type) {
|
||||||
|
return reg(InsnUtils.getArg(insn, argNum), type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TypeImmutableArg typeImmutableReg(int regNum, ArgType type) {
|
||||||
|
return new TypeImmutableArg(regNum, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RegisterArg reg(int regNum, ArgType type, boolean typeImmutable) {
|
||||||
|
return typeImmutable ? new TypeImmutableArg(regNum, type) : new RegisterArg(regNum, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LiteralArg lit(long literal, ArgType type) {
|
||||||
|
return new LiteralArg(literal, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LiteralArg lit(DecodedInstruction insn, ArgType type) {
|
||||||
|
return lit(insn.getLiteral(), type);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static InsnWrapArg wrap(InsnNode insn) {
|
||||||
|
return new InsnWrapArg(insn);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRegister() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isLiteral() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isInsnWrap() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isNamed() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isField() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public InsnNode getParentInsn() {
|
||||||
|
return parentInsn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setParentInsn(@Nullable InsnNode parentInsn) {
|
||||||
|
this.parentInsn = parentInsn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public InsnArg wrapInstruction(InsnNode insn) {
|
||||||
|
InsnNode parent = parentInsn;
|
||||||
|
if (parent == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (parent == insn) {
|
||||||
|
LOG.debug("Can't wrap instruction info itself: {}", insn);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int i = getArgIndex(parent, this);
|
||||||
|
if (i == -1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
insn.add(AFlag.WRAPPED);
|
||||||
|
InsnArg arg = wrapArg(insn);
|
||||||
|
parent.setArg(i, arg);
|
||||||
|
return arg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void updateParentInsn(InsnNode fromInsn, InsnNode toInsn) {
|
||||||
|
List<RegisterArg> args = new ArrayList<RegisterArg>();
|
||||||
|
fromInsn.getRegisterArgs(args);
|
||||||
|
for (RegisterArg reg : args) {
|
||||||
|
reg.setParentInsn(toInsn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getArgIndex(InsnNode parent, InsnArg arg) {
|
||||||
|
int count = parent.getArgsCount();
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
if (parent.getArg(i) == arg) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static InsnArg wrapArg(InsnNode insn) {
|
||||||
|
InsnArg arg;
|
||||||
|
switch (insn.getType()) {
|
||||||
|
case MOVE:
|
||||||
|
case CONST:
|
||||||
|
arg = insn.getArg(0);
|
||||||
|
break;
|
||||||
|
case CONST_STR:
|
||||||
|
arg = wrap(insn);
|
||||||
|
arg.setType(ArgType.STRING);
|
||||||
|
break;
|
||||||
|
case CONST_CLASS:
|
||||||
|
arg = wrap(insn);
|
||||||
|
arg.setType(ArgType.CLASS);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
arg = wrap(insn);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return arg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isThis() {
|
||||||
|
// must be implemented in RegisterArg and MthParameterArg
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package jadx.core.dex.instructions.args;
|
||||||
|
|
||||||
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
public final class InsnWrapArg extends InsnArg {
|
||||||
|
|
||||||
|
private final InsnNode wrappedInsn;
|
||||||
|
|
||||||
|
public InsnWrapArg(@NotNull InsnNode insn) {
|
||||||
|
RegisterArg result = insn.getResult();
|
||||||
|
this.type = result != null ? result.getType() : ArgType.VOID;
|
||||||
|
this.wrappedInsn = insn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public InsnNode getWrapInsn() {
|
||||||
|
return wrappedInsn;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setParentInsn(InsnNode parentInsn) {
|
||||||
|
assert parentInsn != wrappedInsn : "Can't wrap instruction info itself: " + parentInsn;
|
||||||
|
this.parentInsn = parentInsn;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInsnWrap() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return wrappedInsn.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(o instanceof InsnWrapArg)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
InsnWrapArg that = (InsnWrapArg) o;
|
||||||
|
InsnNode thisInsn = wrappedInsn;
|
||||||
|
InsnNode thatInsn = that.wrappedInsn;
|
||||||
|
if (!thisInsn.isSame(thatInsn)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int count = thisInsn.getArgsCount();
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
if (!thisInsn.getArg(i).equals(thatInsn.getArg(i))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "(wrap: " + type + "\n " + wrappedInsn + ")";
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user