Compare commits
259 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 913a5b5d0f | |||
| c594137c19 | |||
| b2f41e95bf | |||
| e733c91783 | |||
| 4e982722a5 | |||
| 2b1f815c58 | |||
| 0fff1a6754 | |||
| d95d268ec5 | |||
| b4930bc40c | |||
| 5f302238ad | |||
| 7cba2c3f81 | |||
| 218c39b1ec | |||
| e915f4fcd7 | |||
| bc9164b952 | |||
| 7c34be267f | |||
| 042464438c | |||
| cf68e4722a | |||
| 7be37ff76e | |||
| 1118236075 | |||
| ef8a685621 | |||
| e4fef402c9 | |||
| 5528afa404 | |||
| e3189fae37 | |||
| 6d963b378c | |||
| 895ddfa38f | |||
| 28e334a0ba | |||
| d060f5b877 | |||
| 7b70d617e0 | |||
| 261ba4645d | |||
| 2ab7524e71 | |||
| d55969bc65 | |||
| 9976894091 | |||
| 76a0608a04 | |||
| 0d93d335a1 | |||
| ffb9788047 | |||
| 5dd82eede9 | |||
| 14b90466ef | |||
| 43592c3e49 | |||
| b46093b3cc | |||
| 2b9c092705 | |||
| bc73010d4e | |||
| 2d8d416483 | |||
| f549a0691e | |||
| 96c2fb6f54 | |||
| f6d475292c | |||
| bd4d4f49ff | |||
| 5a24eac375 | |||
| a684118dbb | |||
| a324376e60 | |||
| 04e50afaba | |||
| 69494c9212 | |||
| b2f0f02541 | |||
| 71f249113d | |||
| 1d84c00161 | |||
| 5bc7e19a28 | |||
| c46703a05d | |||
| 129a7c39af | |||
| ac3f3e8385 | |||
| bc8ad4df86 | |||
| 53ac3ec582 | |||
| d2d43711c2 | |||
| 510035b7b7 | |||
| c923d19bcc | |||
| bff9597360 | |||
| 78b39a60e8 | |||
| 932966b6b8 | |||
| 85a18e6d75 | |||
| 5d86bf9788 | |||
| 406d9878d8 | |||
| 4e6c5cb27a | |||
| a9c0185bf5 | |||
| 0111172a03 | |||
| 57541488d3 | |||
| 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 |
@@ -0,0 +1,3 @@
|
||||
[submodule "jadx-test-app/test-app"]
|
||||
path = jadx-test-app/test-app
|
||||
url = git://github.com/skylot/jadx-test-app.git
|
||||
+8
-2
@@ -1,7 +1,7 @@
|
||||
language: java
|
||||
jdk:
|
||||
- oraclejdk8
|
||||
- oraclejdk7
|
||||
- openjdk7
|
||||
- openjdk6
|
||||
|
||||
before_install:
|
||||
@@ -13,9 +13,15 @@ script:
|
||||
after_success:
|
||||
- TERM=dumb ./gradlew jacocoTestReport coveralls
|
||||
|
||||
sudo: false
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.gradle
|
||||
- $HOME/.gradle/caches/
|
||||
- $HOME/.gradle/wrapper/
|
||||
|
||||
before_cache:
|
||||
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
|
||||
|
||||
notifications:
|
||||
email:
|
||||
|
||||
@@ -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)
|
||||
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");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -18,8 +18,8 @@ limitations under the License.
|
||||
*******************************************************************************
|
||||
|
||||
|
||||
Various portions of the code including dx library are taken from
|
||||
the Android Open Source Project, and are used in accordance with
|
||||
Various portions of the code including dx library are taken from
|
||||
the Android Open Source Project, and are used in accordance with
|
||||
the following license:
|
||||
|
||||
*******************************************************************************
|
||||
@@ -74,10 +74,10 @@ Copyright (c) 2004-2011 QOS.ch
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
@@ -92,14 +92,14 @@ Logback source code and binaries are dual-licensed under the EPL v1.0 and the LG
|
||||
|
||||
*******************************************************************************
|
||||
Logback: the reliable, generic, fast and flexible logging framework.
|
||||
Copyright (C) 1999-2012, QOS.ch. All rights reserved.
|
||||
Copyright (C) 1999-2012, QOS.ch. All rights reserved.
|
||||
|
||||
This program and the accompanying materials are dual-licensed under
|
||||
either the terms of the Eclipse Public License v1.0 as published by
|
||||
the Eclipse Foundation
|
||||
|
||||
|
||||
or (per the licensee's choosing)
|
||||
|
||||
|
||||
under the terms of the GNU Lesser General Public License version 2.1
|
||||
as published by the Free Software Foundation.
|
||||
*******************************************************************************
|
||||
@@ -144,7 +144,8 @@ THE POSSIBILITY OF SUCH DAMAGE.
|
||||
Jadx-gui components
|
||||
===================
|
||||
|
||||
RSyntaxTextArea library licensed under modified BSD liense:
|
||||
RSyntaxTextArea library (https://github.com/bobbylight/RSyntaxTextArea)
|
||||
licensed under modified BSD license:
|
||||
|
||||
*******************************************************************************
|
||||
Copyright (c) 2012, Robert Futrell
|
||||
@@ -174,7 +175,39 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*******************************************************************************
|
||||
|
||||
|
||||
Concurrent Trees (https://code.google.com/p/concurrent-trees/)
|
||||
licenced under Apache License 2.0:
|
||||
|
||||
*******************************************************************************
|
||||
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.
|
||||
*******************************************************************************
|
||||
|
||||
|
||||
Image Viewer (https://github.com/kazocsaba/imageviewer)
|
||||
|
||||
*******************************************************************************
|
||||
Copyright (c) 2008-2012 Kazó Csaba
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*******************************************************************************
|
||||
|
||||
JFontChooser Component - http://sourceforge.jp/projects/jfontchooser/
|
||||
|
||||
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/)
|
||||
|
||||
- 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/)
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
[](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
|
||||
|
||||
@@ -41,27 +42,52 @@ Run **jadx** on itself:
|
||||
```
|
||||
jadx[-gui] [options] <input file> (.dex, .apk, .jar or .class)
|
||||
options:
|
||||
-d, --output-dir - output directory
|
||||
-j, --threads-count - processing threads count
|
||||
-f, --fallback - make simple dump (using goto instead of 'if', 'for', etc)
|
||||
--cfg - save methods control flow graph to dot file
|
||||
--raw-cfg - save methods control flow graph (use raw instructions)
|
||||
-v, --verbose - verbose output
|
||||
-h, --help - print this help
|
||||
-d, --output-dir - output directory
|
||||
-j, --threads-count - processing threads count
|
||||
-r, --no-res - do not decode resources
|
||||
-s, --no-src - do not decompile source code
|
||||
-e, --export-gradle - save as android gradle project
|
||||
--show-bad-code - show inconsistent code (incorrectly decompiled)
|
||||
--no-replace-consts - don't replace constant value with matching constant field
|
||||
--escape-unicode - escape non latin characters in strings (with \u)
|
||||
--deobf - activate deobfuscation
|
||||
--deobf-min - min length of name
|
||||
--deobf-max - max length of name
|
||||
--deobf-rewrite-cfg - force to save deobfuscation map
|
||||
--deobf-use-sourcename - use source file name as class name alias
|
||||
--cfg - save methods control flow graph to dot file
|
||||
--raw-cfg - save methods control flow graph (use raw instructions)
|
||||
-f, --fallback - make simple dump (using goto instead of 'if', 'for', etc)
|
||||
-v, --verbose - verbose output
|
||||
-h, --help - print this help
|
||||
Example:
|
||||
jadx -d out classes.dex
|
||||
```
|
||||
|
||||
### Troubleshooting
|
||||
##### Out of memory error:
|
||||
- Reduce processing threads count (`-j` option)
|
||||
- Reduce processing threads count (`-j` option)
|
||||
- Increase maximum java heap size:
|
||||
* command line (example for linux):
|
||||
* 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*
|
||||
|
||||
*Copyright 2014 by Skylot*
|
||||
*Copyright 2015 by Skylot*
|
||||
|
||||
+33
-23
@@ -1,13 +1,24 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id "com.github.kt3k.coveralls" version "2.3.1"
|
||||
id "info.solidsoft.pitest" version "1.1.4"
|
||||
// id "com.github.ben-manes.versions" version "0.8"
|
||||
}
|
||||
|
||||
ext.jadxVersion = file('version').readLines().get(0)
|
||||
version = jadxVersion
|
||||
|
||||
apply plugin: 'sonar-runner'
|
||||
|
||||
subprojects {
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'groovy'
|
||||
apply plugin: 'jacoco'
|
||||
apply plugin: 'coveralls'
|
||||
apply plugin: 'com.github.kt3k.coveralls'
|
||||
|
||||
version = jadxVersion
|
||||
|
||||
@@ -28,17 +39,20 @@ subprojects {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'org.slf4j:slf4j-api:1.7.7'
|
||||
compile 'org.slf4j:slf4j-api:1.7.10'
|
||||
|
||||
testCompile 'ch.qos.logback:logback-classic:1.1.2'
|
||||
testCompile 'junit:junit:4.11'
|
||||
testCompile 'org.mockito:mockito-core:1.9.5'
|
||||
testCompile 'org.spockframework:spock-core:0.7-groovy-2.0'
|
||||
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 {
|
||||
@@ -49,21 +63,10 @@ subprojects {
|
||||
}
|
||||
}
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// setup coveralls (http://coveralls.io/) see http://github.com/kt3k/coveralls-gradle-plugin
|
||||
classpath 'org.kt3k.gradle.plugin:coveralls-gradle-plugin:0.6.1'
|
||||
}
|
||||
}
|
||||
|
||||
task copyArtifacts(type: Sync, dependsOn: ['jadx-cli:installApp', 'jadx-gui:installApp']) {
|
||||
task copyArtifacts(type: Sync, dependsOn: ['jadx-cli:installDist', 'jadx-gui:installDist']) {
|
||||
destinationDir file("$buildDir/jadx")
|
||||
['jadx-cli', 'jadx-gui'].each {
|
||||
from tasks.getByPath(":${it}:installApp").destinationDir
|
||||
from tasks.getByPath(":${it}:installDist").destinationDir
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,13 +83,20 @@ task dist(dependsOn: pack) {
|
||||
task samples(dependsOn: 'jadx-samples:samples') {
|
||||
}
|
||||
|
||||
task build(dependsOn: [dist, samples]) {
|
||||
task testAppCheck(dependsOn: 'jadx-test-app:testAppCheck') {
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
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) {
|
||||
gradleVersion = '2.0'
|
||||
gradleVersion = '2.7'
|
||||
}
|
||||
|
||||
Vendored
BIN
Binary file not shown.
+1
-1
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-2.0-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-2.7-bin.zip
|
||||
|
||||
@@ -5,7 +5,7 @@ applicationName = 'jadx'
|
||||
|
||||
dependencies {
|
||||
compile(project(':jadx-core'))
|
||||
compile 'com.beust:jcommander:1.35'
|
||||
compile 'com.beust:jcommander:1.47'
|
||||
compile 'ch.qos.logback:logback-classic:1.1.2'
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ applicationDistribution.with {
|
||||
from '../.'
|
||||
include 'README.md'
|
||||
include 'NOTICE'
|
||||
include 'LICENSE'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ public class JadxCLI {
|
||||
processAndSave(jadxArgs);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
LOG.error("jadx error: " + e.getMessage(), e);
|
||||
LOG.error("jadx error: {}", e.getMessage(), e);
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
@@ -56,7 +56,7 @@ public class JadxCLI {
|
||||
} else {
|
||||
outDirName = name + "-jadx-out";
|
||||
}
|
||||
LOG.info("output directory: " + outDirName);
|
||||
LOG.info("output directory: {}", outDirName);
|
||||
outputDir = new File(outDirName);
|
||||
jadxArgs.setOutputDir(outputDir);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package jadx.cli;
|
||||
|
||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||
import ch.qos.logback.core.Appender;
|
||||
import jadx.api.IJadxArgs;
|
||||
import jadx.api.JadxDecompiler;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
@@ -8,17 +10,20 @@ 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.IStringConverter;
|
||||
import com.beust.jcommander.JCommander;
|
||||
import com.beust.jcommander.Parameter;
|
||||
import com.beust.jcommander.ParameterDescription;
|
||||
import com.beust.jcommander.ParameterException;
|
||||
|
||||
public final class JadxCLIArgs implements IJadxArgs {
|
||||
public class JadxCLIArgs implements IJadxArgs {
|
||||
|
||||
@Parameter(description = "<input file> (.dex, .apk, .jar or .class)")
|
||||
protected List<String> files;
|
||||
@@ -27,10 +32,41 @@ public final class JadxCLIArgs implements IJadxArgs {
|
||||
protected String outDirName;
|
||||
|
||||
@Parameter(names = {"-j", "--threads-count"}, description = "processing threads count")
|
||||
protected int threadsCount = Runtime.getRuntime().availableProcessors();
|
||||
protected int threadsCount = Math.max(1, Runtime.getRuntime().availableProcessors() / 2);
|
||||
|
||||
@Parameter(names = {"-f", "--fallback"}, description = "make simple dump (using goto instead of 'if', 'for', etc)", help = true)
|
||||
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 = {"-e", "--export-gradle"}, description = "save as android gradle project")
|
||||
protected boolean exportAsGradleProject = false;
|
||||
|
||||
@Parameter(names = {"--show-bad-code"}, description = "show inconsistent code (incorrectly decompiled)")
|
||||
protected boolean showInconsistentCode = false;
|
||||
|
||||
@Parameter(names = "--no-replace-consts", converter = InvertedBooleanConverter.class,
|
||||
description = "don't replace constant value with matching constant field")
|
||||
protected boolean replaceConsts = true;
|
||||
|
||||
@Parameter(names = {"--escape-unicode"}, description = "escape non latin characters in strings (with \\u)")
|
||||
protected boolean escapeUnicode = 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 = {"--deobf-use-sourcename"}, description = "use source file name as class name alias")
|
||||
protected boolean deobfuscationUseSourceNameAsAlias = false;
|
||||
|
||||
@Parameter(names = {"--cfg"}, description = "save methods control flow graph to dot file")
|
||||
protected boolean cfgOutput = false;
|
||||
@@ -38,6 +74,9 @@ public final class JadxCLIArgs implements IJadxArgs {
|
||||
@Parameter(names = {"--raw-cfg"}, description = "save methods control flow graph (use raw instructions)")
|
||||
protected boolean rawCfgOutput = false;
|
||||
|
||||
@Parameter(names = {"-f", "--fallback"}, description = "make simple dump (using goto instead of 'if', 'for', etc)")
|
||||
protected boolean fallbackMode = false;
|
||||
|
||||
@Parameter(names = {"-v", "--verbose"}, description = "verbose output")
|
||||
protected boolean verbose = false;
|
||||
|
||||
@@ -90,7 +129,11 @@ public final class JadxCLIArgs implements IJadxArgs {
|
||||
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);
|
||||
// remove INFO ThresholdFilter
|
||||
Appender<ILoggingEvent> appender = rootLogger.getAppender("STDOUT");
|
||||
if (appender != null) {
|
||||
appender.clearAllFilters();
|
||||
}
|
||||
}
|
||||
} catch (JadxException e) {
|
||||
System.err.println("ERROR: " + e.getMessage());
|
||||
@@ -111,28 +154,27 @@ public final class JadxCLIArgs implements IJadxArgs {
|
||||
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 = this.getClass().getDeclaredFields();
|
||||
Field[] fields = JadxCLIArgs.class.getDeclaredFields();
|
||||
for (Field f : fields) {
|
||||
for (ParameterDescription p : params) {
|
||||
String name = f.getName();
|
||||
if (name.equals(p.getParameterized().getName())) {
|
||||
StringBuilder opt = new StringBuilder();
|
||||
opt.append(' ').append(p.getNames());
|
||||
addSpaces(opt, maxNamesLen - opt.length() + 2);
|
||||
opt.append("- ").append(p.getDescription());
|
||||
out.println(opt.toString());
|
||||
break;
|
||||
}
|
||||
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");
|
||||
@@ -144,6 +186,13 @@ public final class JadxCLIArgs implements IJadxArgs {
|
||||
}
|
||||
}
|
||||
|
||||
public static class InvertedBooleanConverter implements IStringConverter<Boolean> {
|
||||
@Override
|
||||
public Boolean convert(String value) {
|
||||
return "false".equals(value);
|
||||
}
|
||||
}
|
||||
|
||||
public List<File> getInput() {
|
||||
return input;
|
||||
}
|
||||
@@ -161,6 +210,16 @@ public final class JadxCLIArgs implements IJadxArgs {
|
||||
return printHelp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSkipResources() {
|
||||
return skipResources;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSkipSources() {
|
||||
return skipSources;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getThreadsCount() {
|
||||
return threadsCount;
|
||||
@@ -181,8 +240,53 @@ public final class JadxCLIArgs implements IJadxArgs {
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useSourceNameAsClassAlias() {
|
||||
return deobfuscationUseSourceNameAsAlias;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean escapeUnicode() {
|
||||
return escapeUnicode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReplaceConsts() {
|
||||
return replaceConsts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExportAsGradleProject() {
|
||||
return exportAsGradleProject;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
<configuration>
|
||||
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||
<level>INFO</level>
|
||||
</filter>
|
||||
<encoder>
|
||||
<pattern>%-5level - %msg%n</pattern>
|
||||
<pattern>%d{HH:mm:ss} %-5level - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="INFO">
|
||||
<root level="DEBUG">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</root>
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package jadx.cli;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
public class JadxCLIArgsTest {
|
||||
|
||||
@Test
|
||||
public void testInvertedBooleanOption() throws Exception {
|
||||
assertThat(parse("--no-replace-consts").isReplaceConsts(), is(false));
|
||||
assertThat(parse("").isReplaceConsts(), is(true));
|
||||
}
|
||||
|
||||
private JadxCLIArgs parse(String... args) {
|
||||
JadxCLIArgs jadxArgs = new JadxCLIArgs();
|
||||
boolean res = jadxArgs.processArgs(args);
|
||||
assertThat(res, is(true));
|
||||
return jadxArgs;
|
||||
}
|
||||
}
|
||||
+18
-3
@@ -1,9 +1,17 @@
|
||||
ext.jadxClasspath = 'clsp-data/android-4.3.jar'
|
||||
ext.jadxClasspath = 'clsp-data/android-5.1.jar'
|
||||
|
||||
apply plugin: "info.solidsoft.pitest"
|
||||
|
||||
dependencies {
|
||||
compile files('lib/dx-1.8.jar')
|
||||
compile 'org.ow2.asm:asm:5.0.3'
|
||||
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'
|
||||
compile 'uk.com.robust-it:cloning:1.9.2'
|
||||
|
||||
testCompile 'org.smali:smali:2.0.3'
|
||||
}
|
||||
|
||||
task packTests(type: Jar) {
|
||||
@@ -11,3 +19,10 @@ task packTests(type: Jar) {
|
||||
from sourceSets.test.output
|
||||
}
|
||||
|
||||
pitest {
|
||||
excludedMethods = ['toString']
|
||||
threads = 4
|
||||
enableDefaultIncrementalAnalysis = true
|
||||
outputFormats = ['XML', 'HTML']
|
||||
jvmArgs = ['-Xmx12G']
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -2,24 +2,32 @@ package jadx.api;
|
||||
|
||||
public final class CodePosition {
|
||||
|
||||
private final JavaClass cls;
|
||||
private final JavaNode node;
|
||||
private final int line;
|
||||
private final int offset;
|
||||
|
||||
public CodePosition(JavaClass cls, int line, int offset) {
|
||||
this.cls = cls;
|
||||
public CodePosition(JavaNode node, int line, int offset) {
|
||||
this.node = node;
|
||||
this.line = line;
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
public CodePosition(int line, int offset) {
|
||||
this.cls = null;
|
||||
this.node = null;
|
||||
this.line = line;
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
public JavaNode getNode() {
|
||||
return node;
|
||||
}
|
||||
|
||||
public JavaClass getJavaClass() {
|
||||
return cls;
|
||||
JavaClass parent = node.getDeclaringClass();
|
||||
if (parent == null && node instanceof JavaClass) {
|
||||
return (JavaClass) node;
|
||||
}
|
||||
return parent;
|
||||
}
|
||||
|
||||
public int getLine() {
|
||||
@@ -30,10 +38,6 @@ public final class CodePosition {
|
||||
return offset;
|
||||
}
|
||||
|
||||
public boolean isSet() {
|
||||
return line != 0 || offset != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
@@ -53,6 +57,6 @@ public final class CodePosition {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return line + ":" + offset + (cls != null ? " " + cls : "");
|
||||
return line + ":" + offset + (node != null ? " " + node : "");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
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 isVerbose() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -13,5 +13,33 @@ public interface IJadxArgs {
|
||||
|
||||
boolean isFallbackMode();
|
||||
|
||||
boolean isShowInconsistentCode();
|
||||
|
||||
boolean isVerbose();
|
||||
|
||||
boolean isSkipResources();
|
||||
|
||||
boolean isSkipSources();
|
||||
|
||||
boolean isDeobfuscationOn();
|
||||
|
||||
int getDeobfuscationMinLength();
|
||||
|
||||
int getDeobfuscationMaxLength();
|
||||
|
||||
boolean isDeobfuscationForceSave();
|
||||
|
||||
boolean useSourceNameAsClassAlias();
|
||||
|
||||
boolean escapeUnicode();
|
||||
|
||||
/**
|
||||
* Replace constant values with static final fields with same value
|
||||
*/
|
||||
boolean isReplaceConsts();
|
||||
|
||||
/**
|
||||
* Save as gradle project
|
||||
*/
|
||||
boolean isExportAsGradleProject();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
package jadx.api;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class JadxArgs implements IJadxArgs {
|
||||
|
||||
private File outDir = new File("jadx-output");
|
||||
private int threadsCount = Math.max(1, Runtime.getRuntime().availableProcessors() - 1);
|
||||
|
||||
private boolean cfgOutput = false;
|
||||
private boolean rawCFGOutput = false;
|
||||
|
||||
private boolean isVerbose = false;
|
||||
private boolean fallbackMode = false;
|
||||
private boolean showInconsistentCode = false;
|
||||
|
||||
private boolean isSkipResources = false;
|
||||
private boolean isSkipSources = false;
|
||||
|
||||
private boolean isDeobfuscationOn = false;
|
||||
private boolean isDeobfuscationForceSave = false;
|
||||
private boolean useSourceNameAsClassAlias = false;
|
||||
|
||||
private int deobfuscationMinLength = 0;
|
||||
private int deobfuscationMaxLength = Integer.MAX_VALUE;
|
||||
|
||||
private boolean escapeUnicode = false;
|
||||
private boolean replaceConsts = true;
|
||||
private boolean exportAsGradleProject = false;
|
||||
|
||||
@Override
|
||||
public File getOutDir() {
|
||||
return outDir;
|
||||
}
|
||||
|
||||
public void setOutDir(File outDir) {
|
||||
this.outDir = outDir;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getThreadsCount() {
|
||||
return threadsCount;
|
||||
}
|
||||
|
||||
public void setThreadsCount(int threadsCount) {
|
||||
this.threadsCount = threadsCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCFGOutput() {
|
||||
return cfgOutput;
|
||||
}
|
||||
|
||||
public void setCfgOutput(boolean cfgOutput) {
|
||||
this.cfgOutput = cfgOutput;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRawCFGOutput() {
|
||||
return rawCFGOutput;
|
||||
}
|
||||
|
||||
public void setRawCFGOutput(boolean rawCFGOutput) {
|
||||
this.rawCFGOutput = rawCFGOutput;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFallbackMode() {
|
||||
return fallbackMode;
|
||||
}
|
||||
|
||||
public void setFallbackMode(boolean fallbackMode) {
|
||||
this.fallbackMode = fallbackMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isShowInconsistentCode() {
|
||||
return showInconsistentCode;
|
||||
}
|
||||
|
||||
public void setShowInconsistentCode(boolean showInconsistentCode) {
|
||||
this.showInconsistentCode = showInconsistentCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVerbose() {
|
||||
return isVerbose;
|
||||
}
|
||||
|
||||
public void setVerbose(boolean verbose) {
|
||||
isVerbose = verbose;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSkipResources() {
|
||||
return isSkipResources;
|
||||
}
|
||||
|
||||
public void setSkipResources(boolean skipResources) {
|
||||
isSkipResources = skipResources;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSkipSources() {
|
||||
return isSkipSources;
|
||||
}
|
||||
|
||||
public void setSkipSources(boolean skipSources) {
|
||||
isSkipSources = skipSources;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDeobfuscationOn() {
|
||||
return isDeobfuscationOn;
|
||||
}
|
||||
|
||||
public void setDeobfuscationOn(boolean deobfuscationOn) {
|
||||
isDeobfuscationOn = deobfuscationOn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDeobfuscationForceSave() {
|
||||
return isDeobfuscationForceSave;
|
||||
}
|
||||
|
||||
public void setDeobfuscationForceSave(boolean deobfuscationForceSave) {
|
||||
isDeobfuscationForceSave = deobfuscationForceSave;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useSourceNameAsClassAlias() {
|
||||
return useSourceNameAsClassAlias;
|
||||
}
|
||||
|
||||
public void setUseSourceNameAsClassAlias(boolean useSourceNameAsClassAlias) {
|
||||
this.useSourceNameAsClassAlias = useSourceNameAsClassAlias;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDeobfuscationMinLength() {
|
||||
return deobfuscationMinLength;
|
||||
}
|
||||
|
||||
public void setDeobfuscationMinLength(int deobfuscationMinLength) {
|
||||
this.deobfuscationMinLength = deobfuscationMinLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDeobfuscationMaxLength() {
|
||||
return deobfuscationMaxLength;
|
||||
}
|
||||
|
||||
public void setDeobfuscationMaxLength(int deobfuscationMaxLength) {
|
||||
this.deobfuscationMaxLength = deobfuscationMaxLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean escapeUnicode() {
|
||||
return escapeUnicode;
|
||||
}
|
||||
|
||||
public void setEscapeUnicode(boolean escapeUnicode) {
|
||||
this.escapeUnicode = escapeUnicode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReplaceConsts() {
|
||||
return replaceConsts;
|
||||
}
|
||||
|
||||
public void setReplaceConsts(boolean replaceConsts) {
|
||||
this.replaceConsts = replaceConsts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExportAsGradleProject() {
|
||||
return exportAsGradleProject;
|
||||
}
|
||||
|
||||
public void setExportAsGradleProject(boolean exportAsGradleProject) {
|
||||
this.exportAsGradleProject = exportAsGradleProject;
|
||||
}
|
||||
}
|
||||
@@ -2,15 +2,21 @@ package jadx.api;
|
||||
|
||||
import jadx.core.Jadx;
|
||||
import jadx.core.ProcessClass;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.codegen.CodeGen;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.dex.visitors.IDexTreeVisitor;
|
||||
import jadx.core.dex.visitors.SaveCode;
|
||||
import jadx.core.export.ExportGradleProject;
|
||||
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 jadx.core.xmlgen.ResourcesSaver;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -53,10 +59,19 @@ public final class JadxDecompiler {
|
||||
|
||||
private RootNode root;
|
||||
private List<IDexTreeVisitor> passes;
|
||||
private CodeGen codeGen;
|
||||
|
||||
private List<JavaClass> classes;
|
||||
private List<ResourceFile> resources;
|
||||
|
||||
private BinaryXMLParser xmlParser;
|
||||
|
||||
private Map<ClassNode, JavaClass> classesMap = new HashMap<ClassNode, JavaClass>();
|
||||
private Map<MethodNode, JavaMethod> methodsMap = new HashMap<MethodNode, JavaMethod>();
|
||||
private Map<FieldNode, JavaField> fieldsMap = new HashMap<FieldNode, JavaField>();
|
||||
|
||||
public JadxDecompiler() {
|
||||
this(new DefaultJadxArgs());
|
||||
this(new JadxArgs());
|
||||
}
|
||||
|
||||
public JadxDecompiler(IJadxArgs jadxArgs) {
|
||||
@@ -73,15 +88,19 @@ public final class JadxDecompiler {
|
||||
|
||||
void init() {
|
||||
if (outDir == null) {
|
||||
outDir = new DefaultJadxArgs().getOutDir();
|
||||
outDir = new JadxArgs().getOutDir();
|
||||
}
|
||||
this.passes = Jadx.getPassesList(args, outDir);
|
||||
this.codeGen = new CodeGen(args);
|
||||
}
|
||||
|
||||
void reset() {
|
||||
ClassInfo.clearCache();
|
||||
classes = null;
|
||||
resources = null;
|
||||
xmlParser = null;
|
||||
root = null;
|
||||
passes = null;
|
||||
codeGen = null;
|
||||
}
|
||||
|
||||
public static String getVersion() {
|
||||
@@ -99,7 +118,7 @@ public final class JadxDecompiler {
|
||||
inputFiles.clear();
|
||||
for (File file : files) {
|
||||
try {
|
||||
inputFiles.add(new InputFile(file));
|
||||
InputFile.addFilesFrom(file, inputFiles);
|
||||
} catch (IOException e) {
|
||||
throw new JadxException("Error load file: " + file, e);
|
||||
}
|
||||
@@ -108,8 +127,20 @@ public final class JadxDecompiler {
|
||||
}
|
||||
|
||||
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();
|
||||
ExecutorService ex = getSaveExecutor(saveSources, saveResources);
|
||||
ex.shutdown();
|
||||
ex.awaitTermination(1, TimeUnit.DAYS);
|
||||
} catch (InterruptedException e) {
|
||||
@@ -118,6 +149,10 @@ public final class JadxDecompiler {
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
@@ -126,7 +161,38 @@ public final class JadxDecompiler {
|
||||
|
||||
LOG.info("processing ...");
|
||||
ExecutorService executor = Executors.newFixedThreadPool(threadsCount);
|
||||
|
||||
File sourcesOutDir;
|
||||
File resOutDir;
|
||||
if (args.isExportAsGradleProject()) {
|
||||
ExportGradleProject export = new ExportGradleProject(root, outDir);
|
||||
export.init();
|
||||
sourcesOutDir = export.getSrcOutDir();
|
||||
resOutDir = export.getResOutDir();
|
||||
} else {
|
||||
sourcesOutDir = outDir;
|
||||
resOutDir = outDir;
|
||||
}
|
||||
if (saveSources) {
|
||||
appendSourcesSave(executor, sourcesOutDir);
|
||||
}
|
||||
if (saveResources) {
|
||||
appendResourcesSave(executor, resOutDir);
|
||||
}
|
||||
return executor;
|
||||
}
|
||||
|
||||
private void appendResourcesSave(ExecutorService executor, File outDir) {
|
||||
for (ResourceFile resourceFile : getResources()) {
|
||||
executor.execute(new ResourcesSaver(outDir, resourceFile));
|
||||
}
|
||||
}
|
||||
|
||||
private void appendSourcesSave(ExecutorService executor, final File outDir) {
|
||||
for (final JavaClass cls : getClasses()) {
|
||||
if (cls.getClassNode().contains(AFlag.DONT_GENERATE)) {
|
||||
continue;
|
||||
}
|
||||
executor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@@ -135,7 +201,6 @@ public final class JadxDecompiler {
|
||||
}
|
||||
});
|
||||
}
|
||||
return executor;
|
||||
}
|
||||
|
||||
public List<JavaClass> getClasses() {
|
||||
@@ -145,14 +210,27 @@ public final class JadxDecompiler {
|
||||
if (classes == null) {
|
||||
List<ClassNode> classNodeList = root.getClasses(false);
|
||||
List<JavaClass> clsList = new ArrayList<JavaClass>(classNodeList.size());
|
||||
classesMap.clear();
|
||||
for (ClassNode classNode : classNodeList) {
|
||||
clsList.add(new JavaClass(classNode, this));
|
||||
JavaClass javaClass = new JavaClass(classNode, this);
|
||||
clsList.add(javaClass);
|
||||
classesMap.put(classNode, javaClass);
|
||||
}
|
||||
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()) {
|
||||
@@ -195,38 +273,69 @@ public final class JadxDecompiler {
|
||||
if (root == null) {
|
||||
return;
|
||||
}
|
||||
root.getClsp().printMissingClasses();
|
||||
root.getErrorsCounter().printReport();
|
||||
}
|
||||
|
||||
void parse() throws DecodeException {
|
||||
reset();
|
||||
root = new RootNode();
|
||||
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);
|
||||
ProcessClass.process(cls, passes, codeGen);
|
||||
}
|
||||
|
||||
RootNode getRoot() {
|
||||
return root;
|
||||
}
|
||||
|
||||
JavaClass findJavaClass(ClassNode cls) {
|
||||
if (cls == null) {
|
||||
return null;
|
||||
synchronized BinaryXMLParser getXmlParser() {
|
||||
if (xmlParser == null) {
|
||||
xmlParser = new BinaryXMLParser(root);
|
||||
}
|
||||
for (JavaClass javaClass : getClasses()) {
|
||||
if (javaClass.getClassNode().equals(cls)) {
|
||||
return javaClass;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return xmlParser;
|
||||
}
|
||||
|
||||
Map<ClassNode, JavaClass> getClassesMap() {
|
||||
return classesMap;
|
||||
}
|
||||
|
||||
Map<MethodNode, JavaMethod> getMethodsMap() {
|
||||
return methodsMap;
|
||||
}
|
||||
|
||||
Map<FieldNode, JavaField> getFieldsMap() {
|
||||
return fieldsMap;
|
||||
}
|
||||
|
||||
public IJadxArgs getArgs() {
|
||||
return args;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "jadx decompiler " + getVersion();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,9 +11,12 @@ import jadx.core.dex.nodes.MethodNode;
|
||||
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 org.jetbrains.annotations.Nullable;
|
||||
|
||||
public final class JavaClass implements JavaNode {
|
||||
|
||||
private final JadxDecompiler decompiler;
|
||||
@@ -44,14 +47,14 @@ public final class JavaClass implements JavaNode {
|
||||
if (code == null) {
|
||||
decompile();
|
||||
code = cls.getCode();
|
||||
if (code == null) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
if (code == null) {
|
||||
return "";
|
||||
}
|
||||
return code.toString();
|
||||
return code.getCodeStr();
|
||||
}
|
||||
|
||||
public void decompile() {
|
||||
public synchronized void decompile() {
|
||||
if (decompiler == null) {
|
||||
return;
|
||||
}
|
||||
@@ -66,6 +69,7 @@ public final class JavaClass implements JavaNode {
|
||||
}
|
||||
|
||||
private void load() {
|
||||
JadxDecompiler rootDecompiler = getRootDecompiler();
|
||||
int inClsCount = cls.getInnerClasses().size();
|
||||
if (inClsCount != 0) {
|
||||
List<JavaClass> list = new ArrayList<JavaClass>(inClsCount);
|
||||
@@ -74,6 +78,7 @@ public final class JavaClass implements JavaNode {
|
||||
JavaClass javaClass = new JavaClass(inner, this);
|
||||
javaClass.load();
|
||||
list.add(javaClass);
|
||||
rootDecompiler.getClassesMap().put(inner, javaClass);
|
||||
}
|
||||
}
|
||||
this.innerClasses = Collections.unmodifiableList(list);
|
||||
@@ -84,7 +89,9 @@ public final class JavaClass implements JavaNode {
|
||||
List<JavaField> flds = new ArrayList<JavaField>(fieldsCount);
|
||||
for (FieldNode f : cls.getFields()) {
|
||||
if (!f.contains(AFlag.DONT_GENERATE)) {
|
||||
flds.add(new JavaField(f, this));
|
||||
JavaField javaField = new JavaField(f, this);
|
||||
flds.add(javaField);
|
||||
rootDecompiler.getFieldsMap().put(f, javaField);
|
||||
}
|
||||
}
|
||||
this.fields = Collections.unmodifiableList(flds);
|
||||
@@ -95,7 +102,9 @@ public final class JavaClass implements JavaNode {
|
||||
List<JavaMethod> mths = new ArrayList<JavaMethod>(methodsCount);
|
||||
for (MethodNode m : cls.getMethods()) {
|
||||
if (!m.contains(AFlag.DONT_GENERATE)) {
|
||||
mths.add(new JavaMethod(this, m));
|
||||
JavaMethod javaMethod = new JavaMethod(this, m);
|
||||
mths.add(javaMethod);
|
||||
rootDecompiler.getMethodsMap().put(m, javaMethod);
|
||||
}
|
||||
}
|
||||
Collections.sort(mths, new Comparator<JavaMethod>() {
|
||||
@@ -108,38 +117,87 @@ public final class JavaClass implements JavaNode {
|
||||
}
|
||||
}
|
||||
|
||||
private JadxDecompiler getRootDecompiler() {
|
||||
if (parent != null) {
|
||||
return parent.getRootDecompiler();
|
||||
}
|
||||
return decompiler;
|
||||
}
|
||||
|
||||
private Map<CodePosition, Object> getCodeAnnotations() {
|
||||
decompile();
|
||||
return cls.getCode().getAnnotations();
|
||||
}
|
||||
|
||||
public CodePosition getDefinitionPosition(int line, int offset) {
|
||||
public Map<CodePosition, JavaNode> getUsageMap() {
|
||||
Map<CodePosition, Object> map = getCodeAnnotations();
|
||||
Object obj = map.get(new CodePosition(line, offset));
|
||||
if (obj instanceof LineAttrNode) {
|
||||
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 (map.isEmpty() || decompiler == null) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
Map<CodePosition, JavaNode> resultMap = new HashMap<CodePosition, JavaNode>(map.size());
|
||||
for (Map.Entry<CodePosition, Object> entry : map.entrySet()) {
|
||||
CodePosition codePosition = entry.getKey();
|
||||
Object obj = entry.getValue();
|
||||
if (obj instanceof LineAttrNode) {
|
||||
JavaNode node = convertNode(obj);
|
||||
if (node != null) {
|
||||
resultMap.put(codePosition, node);
|
||||
}
|
||||
}
|
||||
if (clsNode == null) {
|
||||
return null;
|
||||
}
|
||||
clsNode = clsNode.getParentClass();
|
||||
JavaClass jCls = decompiler.findJavaClass(clsNode);
|
||||
if (jCls == null) {
|
||||
return null;
|
||||
}
|
||||
jCls.decompile();
|
||||
int defLine = ((LineAttrNode) obj).getDecompiledLine();
|
||||
return new CodePosition(jCls, defLine, 0);
|
||||
}
|
||||
return resultMap;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private JavaNode convertNode(Object obj) {
|
||||
if (!(obj instanceof LineAttrNode)) {
|
||||
return null;
|
||||
}
|
||||
if (obj instanceof ClassNode) {
|
||||
return getRootDecompiler().getClassesMap().get(obj);
|
||||
}
|
||||
if (obj instanceof MethodNode) {
|
||||
return getRootDecompiler().getMethodsMap().get(obj);
|
||||
}
|
||||
if (obj instanceof FieldNode) {
|
||||
return getRootDecompiler().getFieldsMap().get(obj);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public JavaNode getJavaNodeAtPosition(int line, int offset) {
|
||||
Map<CodePosition, Object> map = getCodeAnnotations();
|
||||
if (map.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
Object obj = map.get(new CodePosition(line, offset));
|
||||
if (obj == null) {
|
||||
return null;
|
||||
}
|
||||
return convertNode(obj);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public CodePosition getDefinitionPosition(int line, int offset) {
|
||||
JavaNode javaNode = getJavaNodeAtPosition(line, offset);
|
||||
if (javaNode == null) {
|
||||
return null;
|
||||
}
|
||||
return getDefinitionPosition(javaNode);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public CodePosition getDefinitionPosition(JavaNode javaNode) {
|
||||
JavaClass jCls = javaNode.getTopParentClass();
|
||||
jCls.decompile();
|
||||
int defLine = javaNode.getDecompiledLine();
|
||||
if (defLine == 0) {
|
||||
return null;
|
||||
}
|
||||
return new CodePosition(jCls, defLine, 0);
|
||||
}
|
||||
|
||||
public Integer getSourceLine(int decompiledLine) {
|
||||
decompile();
|
||||
return cls.getCode().getLineMapping().get(decompiledLine);
|
||||
@@ -164,6 +222,11 @@ public final class JavaClass implements JavaNode {
|
||||
return parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaClass getTopParentClass() {
|
||||
return parent == null ? this : parent.getTopParentClass();
|
||||
}
|
||||
|
||||
public AccessInfo getAccessInfo() {
|
||||
return cls.getAccessFlags();
|
||||
}
|
||||
@@ -199,6 +262,6 @@ public final class JavaClass implements JavaNode {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getFullName();
|
||||
return cls.getFullName() + "[ " + getFullName() + " ]";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,12 +16,12 @@ public final class JavaField implements JavaNode {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return field.getName();
|
||||
return field.getAlias();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFullName() {
|
||||
return parent.getFullName() + "." + field.getName();
|
||||
return parent.getFullName() + "." + getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -29,6 +29,11 @@ public final class JavaField implements JavaNode {
|
||||
return parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaClass getTopParentClass() {
|
||||
return parent.getTopParentClass();
|
||||
}
|
||||
|
||||
public AccessInfo getAccessFlags() {
|
||||
return field.getAccessFlags();
|
||||
}
|
||||
@@ -40,4 +45,19 @@ public final class JavaField implements JavaNode {
|
||||
public int getDecompiledLine() {
|
||||
return field.getDecompiledLine();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return field.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return this == o || o instanceof JavaField && field.equals(((JavaField) o).field);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return field.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ public final class JavaMethod implements JavaNode {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return mth.getName();
|
||||
return mth.getAlias();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -30,6 +30,11 @@ public final class JavaMethod implements JavaNode {
|
||||
return parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaClass getTopParentClass() {
|
||||
return parent.getTopParentClass();
|
||||
}
|
||||
|
||||
public AccessInfo getAccessFlags() {
|
||||
return mth.getAccessFlags();
|
||||
}
|
||||
@@ -53,4 +58,19 @@ public final class JavaMethod implements JavaNode {
|
||||
public int getDecompiledLine() {
|
||||
return mth.getDecompiledLine();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return mth.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return this == o || o instanceof JavaMethod && mth.equals(((JavaMethod) o).mth);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return mth.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,4 +7,8 @@ public interface JavaNode {
|
||||
String getFullName();
|
||||
|
||||
JavaClass getDeclaringClass();
|
||||
|
||||
JavaClass getTopParentClass();
|
||||
|
||||
int getDecompiledLine();
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ 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;
|
||||
@@ -32,7 +34,17 @@ public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(JavaPackage o) {
|
||||
public JavaClass getTopParentClass() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDecompiledLine() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull JavaPackage o) {
|
||||
return name.compareTo(o.name);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package jadx.api;
|
||||
|
||||
import jadx.core.xmlgen.ResContainer;
|
||||
|
||||
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 ResContainer loadContent() {
|
||||
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,19 @@
|
||||
package jadx.api;
|
||||
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
import jadx.core.xmlgen.ResContainer;
|
||||
|
||||
public class ResourceFileContent extends ResourceFile {
|
||||
|
||||
private final CodeWriter content;
|
||||
|
||||
public ResourceFileContent(String name, ResourceType type, CodeWriter content) {
|
||||
super(null, name, type);
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResContainer loadContent() {
|
||||
return ResContainer.singleFile(getName(), content);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package jadx.api;
|
||||
|
||||
public enum ResourceType {
|
||||
CODE(".dex", ".jar", ".class"),
|
||||
MANIFEST("AndroidManifest.xml"),
|
||||
XML(".xml"),
|
||||
ARSC(".arsc"),
|
||||
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 LIB:
|
||||
case FONT:
|
||||
case UNKNOWN:
|
||||
return false;
|
||||
|
||||
case MANIFEST:
|
||||
case XML:
|
||||
case ARSC:
|
||||
case IMG:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
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.ResContainer;
|
||||
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;
|
||||
|
||||
import static jadx.core.utils.files.FileUtils.READ_BUFFER_SIZE;
|
||||
import static jadx.core.utils.files.FileUtils.close;
|
||||
import static jadx.core.utils.files.FileUtils.copyStream;
|
||||
|
||||
// TODO: move to core package
|
||||
public final class ResourcesLoader {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ResourcesLoader.class);
|
||||
|
||||
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 {
|
||||
ResContainer decode(long size, InputStream is) throws IOException;
|
||||
}
|
||||
|
||||
public static ResContainer decodeStream(ResourceFile rf, ResourceDecoder decoder) throws JadxException {
|
||||
ZipRef zipRef = rf.getZipRef();
|
||||
if (zipRef == null) {
|
||||
return null;
|
||||
}
|
||||
ZipFile zipFile = null;
|
||||
InputStream inputStream = null;
|
||||
ResContainer result = 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));
|
||||
result = decoder.decode(entry.getSize(), inputStream);
|
||||
} catch (Exception e) {
|
||||
throw new JadxException("Error decode: " + zipRef.getEntryName(), e);
|
||||
} finally {
|
||||
try {
|
||||
if (zipFile != null) {
|
||||
zipFile.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error close zip file: {}", zipRef, e);
|
||||
}
|
||||
close(inputStream);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static ResContainer loadContent(final JadxDecompiler jadxRef, final ResourceFile rf) {
|
||||
try {
|
||||
return decodeStream(rf, new ResourceDecoder() {
|
||||
@Override
|
||||
public ResContainer decode(long size, InputStream is) throws IOException {
|
||||
return loadContent(jadxRef, rf, is, size);
|
||||
}
|
||||
});
|
||||
} 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 ResContainer.singleFile(rf.getName(), cw);
|
||||
}
|
||||
}
|
||||
|
||||
private static ResContainer loadContent(JadxDecompiler jadxRef, ResourceFile rf,
|
||||
InputStream inputStream, long size) throws IOException {
|
||||
switch (rf.getType()) {
|
||||
case MANIFEST:
|
||||
case XML:
|
||||
return ResContainer.singleFile(rf.getName(),
|
||||
jadxRef.getXmlParser().parse(inputStream));
|
||||
|
||||
case ARSC:
|
||||
return new ResTableParser().decodeFiles(inputStream);
|
||||
|
||||
case IMG:
|
||||
return ResContainer.singleImageFile(rf.getName(), inputStream);
|
||||
}
|
||||
if (size > LOAD_SIZE_LIMIT) {
|
||||
return ResContainer.singleFile(rf.getName(),
|
||||
new CodeWriter().add("File too big, size: " + String.format("%.2f KB", size / 1024.)));
|
||||
}
|
||||
return ResContainer.singleFile(rf.getName(), 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);
|
||||
}
|
||||
|
||||
public static CodeWriter loadToCodeWriter(InputStream is) throws IOException {
|
||||
CodeWriter cw = new CodeWriter();
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(READ_BUFFER_SIZE);
|
||||
copyStream(is, baos);
|
||||
cw.add(baos.toString("UTF-8"));
|
||||
return cw;
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ public class Consts {
|
||||
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 ANONYMOUS_CLASS_PREFIX = "AnonymousClass";
|
||||
|
||||
public static final String MTH_TOSTRING_SIGNATURE = "toString()Ljava/lang/String;";
|
||||
}
|
||||
|
||||
@@ -1,23 +1,30 @@
|
||||
package jadx.core;
|
||||
|
||||
import jadx.api.IJadxArgs;
|
||||
import jadx.core.codegen.CodeGen;
|
||||
import jadx.core.dex.visitors.BlockMakerVisitor;
|
||||
import jadx.core.dex.visitors.ClassModifier;
|
||||
import jadx.core.dex.visitors.CodeShrinker;
|
||||
import jadx.core.dex.visitors.ConstInlinerVisitor;
|
||||
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.ExtractFieldInit;
|
||||
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;
|
||||
@@ -25,7 +32,6 @@ 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;
|
||||
@@ -44,9 +50,6 @@ public class Jadx {
|
||||
if (Consts.DEBUG) {
|
||||
LOG.info("debug enabled");
|
||||
}
|
||||
if (Jadx.class.desiredAssertionStatus()) {
|
||||
LOG.info("assertions enabled");
|
||||
}
|
||||
}
|
||||
|
||||
public static List<IDexTreeVisitor> getPassesList(IJadxArgs args, File outDir) {
|
||||
@@ -54,25 +57,31 @@ public class Jadx {
|
||||
if (args.isFallbackMode()) {
|
||||
passes.add(new FallbackModeVisitor());
|
||||
} else {
|
||||
passes.add(new BlockMakerVisitor());
|
||||
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(new DotGraphVisitor(outDir, false, true));
|
||||
passes.add(DotGraphVisitor.dumpRaw(outDir));
|
||||
}
|
||||
|
||||
passes.add(new ConstInlinerVisitor());
|
||||
passes.add(new ConstInlineVisitor());
|
||||
passes.add(new FinishTypeInference());
|
||||
passes.add(new EliminatePhiNodes());
|
||||
|
||||
passes.add(new ModVisitor());
|
||||
passes.add(new EnumVisitor());
|
||||
|
||||
passes.add(new CodeShrinker());
|
||||
passes.add(new ReSugarCode());
|
||||
|
||||
if (args.isCFGOutput()) {
|
||||
passes.add(new DotGraphVisitor(outDir, false));
|
||||
passes.add(DotGraphVisitor.dump(outDir));
|
||||
}
|
||||
|
||||
passes.add(new RegionMakerVisitor());
|
||||
@@ -81,24 +90,30 @@ public class Jadx {
|
||||
|
||||
passes.add(new CodeShrinker());
|
||||
passes.add(new SimplifyVisitor());
|
||||
passes.add(new ProcessVariables());
|
||||
passes.add(new CheckRegions());
|
||||
|
||||
if (args.isCFGOutput()) {
|
||||
passes.add(new DotGraphVisitor(outDir, true));
|
||||
passes.add(DotGraphVisitor.dumpRegions(outDir));
|
||||
}
|
||||
|
||||
passes.add(new MethodInlineVisitor());
|
||||
passes.add(new ExtractFieldInit());
|
||||
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());
|
||||
}
|
||||
passes.add(new CodeGen(args));
|
||||
return passes;
|
||||
}
|
||||
|
||||
public static String getVersion() {
|
||||
try {
|
||||
ClassLoader classLoader = Utils.class.getClassLoader();
|
||||
ClassLoader classLoader = Jadx.class.getClassLoader();
|
||||
if (classLoader != null) {
|
||||
Enumeration<URL> resources = classLoader.getResources("META-INF/MANIFEST.MF");
|
||||
while (resources.hasMoreElements()) {
|
||||
|
||||
@@ -1,30 +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) {
|
||||
try {
|
||||
cls.load();
|
||||
for (IDexTreeVisitor visitor : passes) {
|
||||
DepthTraversal.visit(visitor, cls);
|
||||
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);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("Class process exception: " + cls, e);
|
||||
} finally {
|
||||
cls.unload();
|
||||
}
|
||||
}
|
||||
|
||||
static void processDependencies(ClassNode cls, List<IDexTreeVisitor> passes) {
|
||||
for (ClassNode depCls : cls.getDependencies()) {
|
||||
process(depCls, passes, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package jadx.core.clsp;
|
||||
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.Utils;
|
||||
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;
|
||||
@@ -27,6 +27,8 @@ import java.util.zip.ZipOutputStream;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static jadx.core.utils.files.FileUtils.close;
|
||||
|
||||
/**
|
||||
* Classes list for import into classpath graph
|
||||
*/
|
||||
@@ -77,15 +79,15 @@ public class ClsSet {
|
||||
|
||||
public static NClass[] makeParentsArray(ClassNode cls, Map<String, NClass> names) {
|
||||
List<NClass> parents = new ArrayList<NClass>(1 + cls.getInterfaces().size());
|
||||
ClassInfo superClass = cls.getSuperClass();
|
||||
ArgType superClass = cls.getSuperClass();
|
||||
if (superClass != null) {
|
||||
NClass c = getCls(superClass.getRawName(), names);
|
||||
NClass c = getCls(superClass.getObject(), names);
|
||||
if (c != null) {
|
||||
parents.add(c);
|
||||
}
|
||||
}
|
||||
for (ClassInfo iface : cls.getInterfaces()) {
|
||||
NClass c = getCls(iface.getRawName(), names);
|
||||
for (ArgType iface : cls.getInterfaces()) {
|
||||
NClass c = getCls(iface.getObject(), names);
|
||||
if (c != null) {
|
||||
parents.add(c);
|
||||
}
|
||||
@@ -96,13 +98,13 @@ public class ClsSet {
|
||||
private static NClass getCls(String fullName, Map<String, NClass> names) {
|
||||
NClass id = names.get(fullName);
|
||||
if (id == null && !names.containsKey(fullName)) {
|
||||
LOG.warn("Class not found: " + fullName);
|
||||
LOG.debug("Class not found: {}", fullName);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
void save(File output) throws IOException {
|
||||
Utils.makeDirsForFile(output);
|
||||
FileUtils.makeDirsForFile(output);
|
||||
|
||||
BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(output));
|
||||
try {
|
||||
@@ -114,15 +116,14 @@ public class ClsSet {
|
||||
try {
|
||||
out.putNextEntry(new ZipEntry(CLST_PKG_PATH + "/" + CLST_FILENAME));
|
||||
save(out);
|
||||
out.closeEntry();
|
||||
} finally {
|
||||
out.close();
|
||||
close(out);
|
||||
}
|
||||
} else {
|
||||
throw new JadxRuntimeException("Unknown file format: " + outputName);
|
||||
}
|
||||
} finally {
|
||||
outputStream.close();
|
||||
close(outputStream);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,7 +133,7 @@ public class ClsSet {
|
||||
out.writeBytes(JADX_CLS_SET_HEADER);
|
||||
out.writeByte(VERSION);
|
||||
|
||||
LOG.info("Classes count: " + classes.length);
|
||||
LOG.info("Classes count: {}", classes.length);
|
||||
out.writeInt(classes.length);
|
||||
for (NClass cls : classes) {
|
||||
writeString(out, cls.getName());
|
||||
@@ -145,7 +146,7 @@ public class ClsSet {
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
out.close();
|
||||
close(out);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,7 +158,7 @@ public class ClsSet {
|
||||
try {
|
||||
load(input);
|
||||
} finally {
|
||||
input.close();
|
||||
close(input);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,13 +179,13 @@ public class ClsSet {
|
||||
entry = in.getNextEntry();
|
||||
}
|
||||
} finally {
|
||||
in.close();
|
||||
close(in);
|
||||
}
|
||||
} else {
|
||||
throw new JadxRuntimeException("Unknown file format: " + name);
|
||||
}
|
||||
} finally {
|
||||
inputStream.close();
|
||||
close(inputStream);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,7 +215,7 @@ public class ClsSet {
|
||||
classes[i].setParents(parents);
|
||||
}
|
||||
} finally {
|
||||
in.close();
|
||||
close(in);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import jadx.core.utils.exceptions.DecodeException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
@@ -25,6 +26,8 @@ public class ClspGraph {
|
||||
private final Map<String, Set<String>> ancestorCache = new WeakHashMap<String, Set<String>>();
|
||||
private Map<String, NClass> nameMap;
|
||||
|
||||
private final Set<String> missingClasses = new HashSet<String>();
|
||||
|
||||
public void load() throws IOException, DecodeException {
|
||||
ClsSet set = new ClsSet();
|
||||
set.load();
|
||||
@@ -45,16 +48,10 @@ public class ClspGraph {
|
||||
throw new JadxRuntimeException("Classpath must be loaded first");
|
||||
}
|
||||
int size = classes.size();
|
||||
for (ClassNode cls : classes) {
|
||||
size += cls.getInnerClasses().size();
|
||||
}
|
||||
NClass[] nClasses = new NClass[size];
|
||||
int k = 0;
|
||||
for (ClassNode cls : classes) {
|
||||
nClasses[k++] = addClass(cls);
|
||||
for (ClassNode inner : cls.getInnerClasses()) {
|
||||
nClasses[k++] = addClass(inner);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < size; i++) {
|
||||
nClasses[i].setParents(ClsSet.makeParentsArray(classes.get(i), nameMap));
|
||||
@@ -62,8 +59,9 @@ public class ClspGraph {
|
||||
}
|
||||
|
||||
private NClass addClass(ClassNode cls) {
|
||||
NClass nClass = new NClass(cls.getRawName(), -1);
|
||||
nameMap.put(cls.getRawName(), nClass);
|
||||
String rawName = cls.getRawName();
|
||||
NClass nClass = new NClass(rawName, -1);
|
||||
nameMap.put(rawName, nClass);
|
||||
return nClass;
|
||||
}
|
||||
|
||||
@@ -78,7 +76,7 @@ public class ClspGraph {
|
||||
}
|
||||
NClass cls = nameMap.get(implClsName);
|
||||
if (cls == null) {
|
||||
LOG.debug("Missing class: {}", implClsName);
|
||||
missingClasses.add(clsName);
|
||||
return null;
|
||||
}
|
||||
if (isImplements(clsName, implClsName)) {
|
||||
@@ -109,7 +107,7 @@ public class ClspGraph {
|
||||
}
|
||||
NClass cls = nameMap.get(clsName);
|
||||
if (cls == null) {
|
||||
LOG.debug("Missing class: {}", clsName);
|
||||
missingClasses.add(clsName);
|
||||
return Collections.emptySet();
|
||||
}
|
||||
result = new HashSet<String>();
|
||||
@@ -127,4 +125,19 @@ public class ClspGraph {
|
||||
addAncestorsNames(p, result);
|
||||
}
|
||||
}
|
||||
|
||||
public void printMissingClasses() {
|
||||
int count = missingClasses.size();
|
||||
if (count == 0) {
|
||||
return;
|
||||
}
|
||||
LOG.warn("Found {} references to unknown classes", count);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
List<String> clsNames = new ArrayList<String>(missingClasses);
|
||||
Collections.sort(clsNames);
|
||||
for (String cls : clsNames) {
|
||||
LOG.debug(" {}", cls);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package jadx.core.clsp;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.exceptions.DecodeException;
|
||||
import jadx.core.utils.files.InputFile;
|
||||
@@ -35,24 +36,24 @@ public class ConvertToClsSet {
|
||||
if (f.isDirectory()) {
|
||||
addFilesFromDirectory(f, inputFiles);
|
||||
} else {
|
||||
inputFiles.add(new InputFile(f));
|
||||
InputFile.addFilesFrom(f, inputFiles);
|
||||
}
|
||||
}
|
||||
for (InputFile inputFile : inputFiles) {
|
||||
LOG.info("Loaded: " + inputFile.getFile());
|
||||
LOG.info("Loaded: {}", inputFile.getFile());
|
||||
}
|
||||
|
||||
RootNode root = new RootNode();
|
||||
RootNode root = new RootNode(new JadxArgs());
|
||||
root.load(inputFiles);
|
||||
|
||||
ClsSet set = new ClsSet();
|
||||
set.load(root);
|
||||
set.save(output);
|
||||
LOG.info("Output: " + output);
|
||||
LOG.info("Output: {}", output);
|
||||
LOG.info("done");
|
||||
}
|
||||
|
||||
private static void addFilesFromDirectory(File dir, List<InputFile> inputFiles) throws IOException, DecodeException {
|
||||
private static void addFilesFromDirectory(File dir, List<InputFile> inputFiles) {
|
||||
File[] files = dir.listFiles();
|
||||
if (files == null) {
|
||||
return;
|
||||
@@ -60,11 +61,13 @@ public class ConvertToClsSet {
|
||||
for (File file : files) {
|
||||
if (file.isDirectory()) {
|
||||
addFilesFromDirectory(file, inputFiles);
|
||||
}
|
||||
if (file.getName().endsWith(".dex")) {
|
||||
inputFiles.add(new InputFile(file));
|
||||
} else {
|
||||
try {
|
||||
InputFile.addFilesFrom(file, inputFiles);
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Skip file: {}, load error: {}", file, e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -42,8 +42,12 @@ public class AnnotationGen {
|
||||
}
|
||||
|
||||
public void addForParameter(CodeWriter code, MethodParameters paramsAnnotations, int n) {
|
||||
AnnotationsList aList = paramsAnnotations.getParamList().get(n);
|
||||
if (aList == null || aList.size() == 0) {
|
||||
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()) {
|
||||
@@ -54,7 +58,7 @@ public class AnnotationGen {
|
||||
|
||||
private void add(IAttributeNode node, CodeWriter code) {
|
||||
AnnotationsList aList = node.get(AType.ANNOTATION_LIST);
|
||||
if (aList == null || aList.size() == 0) {
|
||||
if (aList == null || aList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (Annotation a : aList.getAll()) {
|
||||
@@ -126,11 +130,11 @@ public class AnnotationGen {
|
||||
return;
|
||||
}
|
||||
if (val instanceof String) {
|
||||
code.add(StringUtils.unescapeString((String) val));
|
||||
code.add(getStringUtils().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));
|
||||
code.add(getStringUtils().unescapeChar((Character) val));
|
||||
} else if (val instanceof Boolean) {
|
||||
code.add(Boolean.TRUE.equals(val) ? "true" : "false");
|
||||
} else if (val instanceof Float) {
|
||||
@@ -150,9 +154,9 @@ public class AnnotationGen {
|
||||
// must be a static field
|
||||
FieldInfo field = (FieldInfo) val;
|
||||
InsnGen.makeStaticFieldAccess(code, field, classGen);
|
||||
} else if (val instanceof List) {
|
||||
} else if (val instanceof Iterable) {
|
||||
code.add('{');
|
||||
Iterator<?> it = ((List) val).iterator();
|
||||
Iterator<?> it = ((Iterable) val).iterator();
|
||||
while (it.hasNext()) {
|
||||
Object obj = it.next();
|
||||
encodeValue(code, obj);
|
||||
@@ -168,4 +172,8 @@ public class AnnotationGen {
|
||||
throw new JadxRuntimeException("Can't decode value: " + val + " (" + val.getClass() + ")");
|
||||
}
|
||||
}
|
||||
|
||||
private StringUtils getStringUtils() {
|
||||
return cls.dex().root().getStringUtils();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package jadx.core.codegen;
|
||||
|
||||
import jadx.core.Consts;
|
||||
import jadx.api.IJadxArgs;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.AttrNode;
|
||||
@@ -10,19 +10,22 @@ 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.InsnArg;
|
||||
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.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.parser.FieldValueAttr;
|
||||
import jadx.core.dex.nodes.parser.FieldInitAttr;
|
||||
import jadx.core.dex.nodes.parser.FieldInitAttr.InitType;
|
||||
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;
|
||||
@@ -38,18 +41,35 @@ 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, ClassGen parentClsGen, boolean fallback) {
|
||||
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);
|
||||
}
|
||||
@@ -71,7 +91,7 @@ public class ClassGen {
|
||||
if (importsCount != 0) {
|
||||
List<String> sortImports = new ArrayList<String>(importsCount);
|
||||
for (ClassInfo ic : imports) {
|
||||
sortImports.add(ic.getFullName());
|
||||
sortImports.add(ic.getAlias().getFullName());
|
||||
}
|
||||
Collections.sort(sortImports);
|
||||
|
||||
@@ -101,15 +121,22 @@ public class ClassGen {
|
||||
public void addClassDeclaration(CodeWriter clsCode) {
|
||||
AccessInfo af = cls.getAccessFlags();
|
||||
if (af.isInterface()) {
|
||||
af = af.remove(AccessFlags.ACC_ABSTRACT);
|
||||
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()) {
|
||||
@@ -121,28 +148,29 @@ public class ClassGen {
|
||||
} else {
|
||||
clsCode.add("class ");
|
||||
}
|
||||
clsCode.attachDefinition(cls);
|
||||
clsCode.add(cls.getShortName());
|
||||
|
||||
addGenericMap(clsCode, cls.getGenericMap());
|
||||
clsCode.add(' ');
|
||||
|
||||
ClassInfo sup = cls.getSuperClass();
|
||||
ArgType sup = cls.getSuperClass();
|
||||
if (sup != null
|
||||
&& !sup.getFullName().equals(Consts.CLASS_OBJECT)
|
||||
&& !sup.getFullName().equals(Consts.CLASS_ENUM)) {
|
||||
&& !sup.equals(ArgType.OBJECT)
|
||||
&& !sup.getObject().equals(ArgType.ENUM.getObject())) {
|
||||
clsCode.add("extends ");
|
||||
useClass(clsCode, sup);
|
||||
clsCode.add(' ');
|
||||
}
|
||||
|
||||
if (cls.getInterfaces().size() > 0 && !af.isAnnotation()) {
|
||||
if (!cls.getInterfaces().isEmpty() && !af.isAnnotation()) {
|
||||
if (cls.getAccessFlags().isInterface()) {
|
||||
clsCode.add("extends ");
|
||||
} else {
|
||||
clsCode.add("implements ");
|
||||
}
|
||||
for (Iterator<ClassInfo> it = cls.getInterfaces().iterator(); it.hasNext(); ) {
|
||||
ClassInfo interf = it.next();
|
||||
for (Iterator<ArgType> it = cls.getInterfaces().iterator(); it.hasNext(); ) {
|
||||
ArgType interf = it.next();
|
||||
useClass(clsCode, interf);
|
||||
if (it.hasNext()) {
|
||||
clsCode.add(", ");
|
||||
@@ -152,7 +180,6 @@ public class ClassGen {
|
||||
clsCode.add(' ');
|
||||
}
|
||||
}
|
||||
clsCode.attachDefinition(cls);
|
||||
}
|
||||
|
||||
public boolean addGenericMap(CodeWriter code, Map<ArgType, List<ArgType>> gmap) {
|
||||
@@ -170,7 +197,7 @@ public class ClassGen {
|
||||
if (type.isGenericType()) {
|
||||
code.add(type.getObject());
|
||||
} else {
|
||||
useClass(code, ClassInfo.fromType(type));
|
||||
useClass(code, type);
|
||||
}
|
||||
if (list != null && !list.isEmpty()) {
|
||||
code.add(" extends ");
|
||||
@@ -179,7 +206,7 @@ public class ClassGen {
|
||||
if (g.isGenericType()) {
|
||||
code.add(g.getObject());
|
||||
} else {
|
||||
useClass(code, ClassInfo.fromType(g));
|
||||
useClass(code, g);
|
||||
}
|
||||
if (it.hasNext()) {
|
||||
code.add(" & ");
|
||||
@@ -206,10 +233,10 @@ public class ClassGen {
|
||||
private void addInnerClasses(CodeWriter code, ClassNode cls) throws CodegenException {
|
||||
for (ClassNode innerCls : cls.getInnerClasses()) {
|
||||
if (innerCls.contains(AFlag.DONT_GENERATE)
|
||||
|| innerCls.isAnonymous()) {
|
||||
|| innerCls.contains(AFlag.ANONYMOUS_CLASS)) {
|
||||
continue;
|
||||
}
|
||||
ClassGen inClGen = new ClassGen(innerCls, getParentGen(), fallback);
|
||||
ClassGen inClGen = new ClassGen(innerCls, getParentGen());
|
||||
code.newLine();
|
||||
inClGen.addClassCode(code);
|
||||
imports.addAll(inClGen.getImports());
|
||||
@@ -218,7 +245,7 @@ public class ClassGen {
|
||||
|
||||
private boolean isInnerClassesPresents() {
|
||||
for (ClassNode innerCls : cls.getInnerClasses()) {
|
||||
if (!innerCls.isAnonymous()) {
|
||||
if (!innerCls.contains(AFlag.ANONYMOUS_CLASS)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -226,7 +253,8 @@ public class ClassGen {
|
||||
}
|
||||
|
||||
private void addMethods(CodeWriter code) {
|
||||
for (MethodNode mth : cls.getMethods()) {
|
||||
List<MethodNode> methods = sortMethodsByLine(cls.getMethods());
|
||||
for (MethodNode mth : methods) {
|
||||
if (mth.contains(AFlag.DONT_GENERATE)) {
|
||||
continue;
|
||||
}
|
||||
@@ -236,12 +264,20 @@ public class ClassGen {
|
||||
try {
|
||||
addMethod(code, mth);
|
||||
} catch (Exception e) {
|
||||
String msg = ErrorsCounter.methodError(mth, "Method generation error", e);
|
||||
code.startLine("/* " + msg + CodeWriter.NL + Utils.getStackTrace(e) + " */");
|
||||
code.newLine().add("/*");
|
||||
code.newLine().add(ErrorsCounter.methodError(mth, "Method generation error", e));
|
||||
code.newLine().add(Utils.getStackTrace(e));
|
||||
code.newLine().add("*/");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)) {
|
||||
@@ -269,9 +305,13 @@ public class ClassGen {
|
||||
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)) {
|
||||
if (badCode || mth.contains(AType.JADX_ERROR) || fallback) {
|
||||
mthGen = MethodGen.getFallbackMethodGen(mth);
|
||||
} else {
|
||||
mthGen = new MethodGen(this, mth);
|
||||
@@ -282,7 +322,11 @@ public class ClassGen {
|
||||
code.add('{');
|
||||
code.incIndent();
|
||||
insertSourceFileInfo(code, mth);
|
||||
mthGen.addInstructions(code);
|
||||
if (fallback) {
|
||||
mthGen.addFallbackMethodCode(code);
|
||||
} else {
|
||||
mthGen.addInstructions(code);
|
||||
}
|
||||
code.decIndent();
|
||||
code.startLine('}');
|
||||
}
|
||||
@@ -298,18 +342,23 @@ public class ClassGen {
|
||||
code.startLine(f.getAccessFlags().makeString());
|
||||
useType(code, f.getType());
|
||||
code.add(' ');
|
||||
code.add(f.getName());
|
||||
FieldValueAttr fv = f.get(AType.FIELD_VALUE);
|
||||
code.attachDefinition(f);
|
||||
code.add(f.getAlias());
|
||||
FieldInitAttr fv = f.get(AType.FIELD_INIT);
|
||||
if (fv != null) {
|
||||
code.add(" = ");
|
||||
if (fv.getValue() == null) {
|
||||
code.add(TypeGen.literalToString(0, f.getType()));
|
||||
code.add(TypeGen.literalToString(0, f.getType(), cls));
|
||||
} else {
|
||||
annotationGen.encodeValue(code, fv.getValue());
|
||||
if (fv.getValueType() == InitType.CONST) {
|
||||
annotationGen.encodeValue(code, fv.getValue());
|
||||
} else if (fv.getValueType() == InitType.INSN) {
|
||||
InsnGen insnGen = makeInsnGen(fv.getInsnMth());
|
||||
addInsnBody(insnGen, code, fv.getInsn());
|
||||
}
|
||||
}
|
||||
}
|
||||
code.add(';');
|
||||
code.attachDefinition(f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -330,26 +379,18 @@ public class ClassGen {
|
||||
InsnGen igen = null;
|
||||
for (Iterator<EnumField> it = enumFields.getFields().iterator(); it.hasNext(); ) {
|
||||
EnumField f = it.next();
|
||||
code.startLine(f.getName());
|
||||
if (f.getArgs().size() != 0) {
|
||||
code.add('(');
|
||||
for (Iterator<InsnArg> aIt = f.getArgs().iterator(); aIt.hasNext(); ) {
|
||||
InsnArg arg = aIt.next();
|
||||
if (igen == null) {
|
||||
// don't init mth gen if this is simple enum
|
||||
MethodGen mthGen = new MethodGen(this, enumFields.getStaticMethod());
|
||||
igen = new InsnGen(mthGen, false);
|
||||
}
|
||||
igen.addArg(code, arg);
|
||||
if (aIt.hasNext()) {
|
||||
code.add(", ");
|
||||
}
|
||||
code.startLine(f.getField().getAlias());
|
||||
ConstructorInsn constrInsn = f.getConstrInsn();
|
||||
if (constrInsn.getArgsCount() > f.getStartArg()) {
|
||||
if (igen == null) {
|
||||
igen = makeInsnGen(enumFields.getStaticMethod());
|
||||
}
|
||||
code.add(')');
|
||||
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, fallback).addClassBody(code);
|
||||
new ClassGen(f.getCls(), this).addClassBody(code);
|
||||
}
|
||||
if (it.hasNext()) {
|
||||
code.add(',');
|
||||
@@ -360,6 +401,22 @@ public class ClassGen {
|
||||
code.startLine();
|
||||
}
|
||||
code.add(';');
|
||||
if (isFieldsPresents()) {
|
||||
code.startLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private InsnGen makeInsnGen(MethodNode mth) {
|
||||
MethodGen mthGen = new MethodGen(this, mth);
|
||||
return new InsnGen(mthGen, false);
|
||||
}
|
||||
|
||||
private void addInsnBody(InsnGen insnGen, CodeWriter code, InsnNode insn) {
|
||||
try {
|
||||
insnGen.makeInsn(insn, code, InsnGen.Flags.BODY_ONLY_NOWRAP);
|
||||
} catch (Exception e) {
|
||||
ErrorsCounter.classError(cls, "Failed to generate init code", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -371,7 +428,7 @@ public class ClassGen {
|
||||
if (type.isGenericType()) {
|
||||
code.add(type.getObject());
|
||||
} else {
|
||||
useClass(code, ClassInfo.fromType(type));
|
||||
useClass(code, type);
|
||||
}
|
||||
} else if (stype == PrimitiveType.ARRAY) {
|
||||
useType(code, type.getArrayElement());
|
||||
@@ -381,14 +438,9 @@ public class ClassGen {
|
||||
}
|
||||
}
|
||||
|
||||
public void useClass(CodeWriter code, ClassInfo classInfo) {
|
||||
ClassNode classNode = cls.dex().resolveClass(classInfo);
|
||||
if (classNode != null) {
|
||||
code.attachAnnotation(classNode);
|
||||
}
|
||||
String baseClass = useClassInternal(cls.getClassInfo(), classInfo);
|
||||
ArgType[] generics = classInfo.getType().getGenericTypes();
|
||||
code.add(baseClass);
|
||||
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;
|
||||
@@ -413,53 +465,67 @@ public class ClassGen {
|
||||
}
|
||||
}
|
||||
|
||||
private String useClassInternal(ClassInfo useCls, ClassInfo classInfo) {
|
||||
String fullName = classInfo.getFullName();
|
||||
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 = classInfo.getShortName();
|
||||
if (classInfo.getPackage().equals("java.lang") && classInfo.getParentClass() == null) {
|
||||
return shortName;
|
||||
} else {
|
||||
// don't add import if this class inner for current class
|
||||
if (isClassInnerFor(classInfo, useCls)) {
|
||||
return shortName;
|
||||
}
|
||||
// don't add import if this class from same package
|
||||
if (classInfo.getPackage().equals(useCls.getPackage()) && !classInfo.isInner()) {
|
||||
return shortName;
|
||||
}
|
||||
// don't add import if class not public (must be accessed using inheritance)
|
||||
ClassNode classNode = cls.dex().resolveClass(classInfo);
|
||||
if (classNode != null && !classNode.getAccessFlags().isPublic()) {
|
||||
return shortName;
|
||||
}
|
||||
if (searchCollision(cls.dex(), useCls, shortName)) {
|
||||
return fullName;
|
||||
}
|
||||
if (classInfo.getPackage().equals(useCls.getPackage())) {
|
||||
fullName = classInfo.getNameWithoutPackage();
|
||||
}
|
||||
for (ClassInfo importCls : getImports()) {
|
||||
if (!importCls.equals(classInfo)
|
||||
&& importCls.getShortName().equals(shortName)) {
|
||||
if (classInfo.isInner()) {
|
||||
String parent = useClassInternal(useCls, classInfo.getParentClass());
|
||||
return parent + "." + shortName;
|
||||
} else {
|
||||
return fullName;
|
||||
}
|
||||
}
|
||||
}
|
||||
addImport(classInfo);
|
||||
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;
|
||||
}
|
||||
// ignore classes from default package
|
||||
if (extClsInfo.isDefaultPackage()) {
|
||||
return shortName;
|
||||
}
|
||||
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);
|
||||
parentGen.addImport(classInfo.getAlias());
|
||||
} else {
|
||||
imports.add(classInfo);
|
||||
}
|
||||
@@ -473,6 +539,16 @@ public class ClassGen {
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
@@ -481,28 +557,38 @@ public class ClassGen {
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean searchCollision(DexNode dex, ClassInfo useCls, String shortName) {
|
||||
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)) {
|
||||
if (inner.getShortName().equals(shortName)
|
||||
&& !inner.getAlias().equals(searchCls)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return searchCollision(dex, useCls.getParentClass(), shortName);
|
||||
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());
|
||||
code.startLine("/* compiled from: ").add(sourceFileAttr.getFileName()).add(" */");
|
||||
}
|
||||
}
|
||||
|
||||
private void insertRenameInfo(CodeWriter code, ClassNode cls) {
|
||||
ClassInfo classInfo = cls.getClassInfo();
|
||||
if (classInfo.isRenamed()
|
||||
&& !cls.getShortName().equals(cls.getAlias().getShortName())) {
|
||||
code.startLine("/* renamed from: ").add(classInfo.getFullName()).add(" */");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,15 +15,11 @@ public class CodeGen extends AbstractVisitor {
|
||||
|
||||
@Override
|
||||
public boolean visit(ClassNode cls) throws CodegenException {
|
||||
ClassGen clsGen = new ClassGen(cls, null, isFallbackMode());
|
||||
ClassGen clsGen = new ClassGen(cls, args);
|
||||
CodeWriter clsCode = clsGen.makeClass();
|
||||
clsCode.finish();
|
||||
cls.setCode(clsCode);
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isFallbackMode() {
|
||||
return args.isFallbackMode();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package jadx.core.codegen;
|
||||
|
||||
import jadx.api.CodePosition;
|
||||
import jadx.core.dex.attributes.nodes.LineAttrNode;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.PrintWriter;
|
||||
@@ -12,16 +12,20 @@ import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static jadx.core.utils.files.FileUtils.close;
|
||||
|
||||
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,
|
||||
@@ -31,7 +35,9 @@ public class CodeWriter {
|
||||
INDENT + INDENT + INDENT + INDENT + INDENT,
|
||||
};
|
||||
|
||||
private final StringBuilder buf = new StringBuilder();
|
||||
private StringBuilder buf = new StringBuilder();
|
||||
@Nullable
|
||||
private String code;
|
||||
private String indentStr;
|
||||
private int indent;
|
||||
|
||||
@@ -43,6 +49,9 @@ public class CodeWriter {
|
||||
public CodeWriter() {
|
||||
this.indent = 0;
|
||||
this.indentStr = "";
|
||||
if (ADD_LINE_NUMBERS) {
|
||||
incIndent(2);
|
||||
}
|
||||
}
|
||||
|
||||
public CodeWriter startLine() {
|
||||
@@ -65,6 +74,26 @@ public class CodeWriter {
|
||||
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();
|
||||
@@ -88,7 +117,7 @@ public class CodeWriter {
|
||||
}
|
||||
line += code.line;
|
||||
offset = code.offset;
|
||||
buf.append(code);
|
||||
buf.append(code.buf);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -149,6 +178,10 @@ public class CodeWriter {
|
||||
updateIndent();
|
||||
}
|
||||
|
||||
public int getIndent() {
|
||||
return indent;
|
||||
}
|
||||
|
||||
public int getLine() {
|
||||
return line;
|
||||
}
|
||||
@@ -165,12 +198,13 @@ public class CodeWriter {
|
||||
}
|
||||
}
|
||||
|
||||
public Object attachDefinition(LineAttrNode obj) {
|
||||
return attachAnnotation(new DefinitionWrapper(obj), new CodePosition(line, offset));
|
||||
public void attachDefinition(LineAttrNode obj) {
|
||||
attachAnnotation(obj);
|
||||
attachAnnotation(new DefinitionWrapper(obj), new CodePosition(line, offset));
|
||||
}
|
||||
|
||||
public Object attachAnnotation(Object obj) {
|
||||
return attachAnnotation(obj, new CodePosition(line, offset + 1));
|
||||
public void attachAnnotation(Object obj) {
|
||||
attachAnnotation(obj, new CodePosition(line, offset + 1));
|
||||
}
|
||||
|
||||
private Object attachAnnotation(Object obj, CodePosition pos) {
|
||||
@@ -203,7 +237,11 @@ public class CodeWriter {
|
||||
}
|
||||
|
||||
public void finish() {
|
||||
removeFirstEmptyLine();
|
||||
buf.trimToSize();
|
||||
code = buf.toString();
|
||||
buf = null;
|
||||
|
||||
Iterator<Map.Entry<CodePosition, Object>> it = annotations.entrySet().iterator();
|
||||
while (it.hasNext()) {
|
||||
Map.Entry<CodePosition, Object> entry = it.next();
|
||||
@@ -216,28 +254,23 @@ public class CodeWriter {
|
||||
}
|
||||
}
|
||||
|
||||
private static String removeFirstEmptyLine(String str) {
|
||||
if (str.startsWith(NL)) {
|
||||
return str.substring(NL.length());
|
||||
private void removeFirstEmptyLine() {
|
||||
if (buf.indexOf(NL) == 0) {
|
||||
buf.delete(0, NL.length());
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
public int length() {
|
||||
public int bufLength() {
|
||||
return buf.length();
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return buf.length() == 0;
|
||||
}
|
||||
|
||||
public boolean notEmpty() {
|
||||
return buf.length() != 0;
|
||||
public String getCodeStr() {
|
||||
return code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return buf.toString();
|
||||
return buf == null ? code : buf.toString();
|
||||
}
|
||||
|
||||
public void save(File dir, String subDir, String fileName) {
|
||||
@@ -249,48 +282,19 @@ public class CodeWriter {
|
||||
}
|
||||
|
||||
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);
|
||||
if (code == null) {
|
||||
finish();
|
||||
}
|
||||
|
||||
File outFile = FileUtils.prepareFile(file);
|
||||
PrintWriter out = null;
|
||||
try {
|
||||
Utils.makeDirsForFile(file);
|
||||
out = new PrintWriter(file, "UTF-8");
|
||||
String code = buf.toString();
|
||||
code = removeFirstEmptyLine(code);
|
||||
out.print(code);
|
||||
out = new PrintWriter(outFile, "UTF-8");
|
||||
out.println(code);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Save file error", e);
|
||||
} finally {
|
||||
if (out != null) {
|
||||
out.close();
|
||||
}
|
||||
close(out);
|
||||
}
|
||||
}
|
||||
|
||||
@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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,14 +8,16 @@ 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.Compare;
|
||||
import jadx.core.dex.regions.IfCondition;
|
||||
import jadx.core.dex.regions.IfCondition.Mode;
|
||||
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;
|
||||
@@ -23,42 +25,83 @@ 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, condition.getCompare());
|
||||
addCompare(code, stack, condition.getCompare());
|
||||
break;
|
||||
|
||||
case TERNARY:
|
||||
addTernary(code, stack, condition);
|
||||
break;
|
||||
|
||||
case NOT:
|
||||
addNot(code, condition);
|
||||
addNot(code, stack, condition);
|
||||
break;
|
||||
|
||||
case AND:
|
||||
case OR:
|
||||
addAndOr(code, condition);
|
||||
addAndOr(code, stack, condition);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new JadxRuntimeException("Unknown condition mode: " + condition);
|
||||
throw new JadxRuntimeException("Unknown condition mode: " + condition.getMode());
|
||||
}
|
||||
stack.pop();
|
||||
}
|
||||
|
||||
void wrap(CodeWriter code, IfCondition cond) throws CodegenException {
|
||||
private void wrap(CodeWriter code, CondStack stack, IfCondition cond) throws CodegenException {
|
||||
boolean wrap = isWrapNeeded(cond);
|
||||
if (wrap) {
|
||||
code.add('(');
|
||||
}
|
||||
add(code, cond);
|
||||
add(code, stack, cond);
|
||||
if (wrap) {
|
||||
code.add(')');
|
||||
}
|
||||
}
|
||||
|
||||
private void addCompare(CodeWriter code, Compare compare) throws CodegenException {
|
||||
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();
|
||||
@@ -71,19 +114,16 @@ public class ConditionGen extends InsnGen {
|
||||
}
|
||||
if (op == IfOp.EQ) {
|
||||
// == true
|
||||
addArg(code, firstArg, false);
|
||||
if (stack.getStack().size() == 1) {
|
||||
addArg(code, firstArg, false);
|
||||
} else {
|
||||
wrap(code, firstArg);
|
||||
}
|
||||
return;
|
||||
} else if (op == IfOp.NE) {
|
||||
// != true
|
||||
code.add('!');
|
||||
boolean wrap = isArgWrapNeeded(firstArg);
|
||||
if (wrap) {
|
||||
code.add('(');
|
||||
}
|
||||
addArg(code, firstArg, false);
|
||||
if (wrap) {
|
||||
code.add(')');
|
||||
}
|
||||
wrap(code, firstArg);
|
||||
return;
|
||||
}
|
||||
LOG.warn(ErrorsCounter.formatErrorMsg(mth, "Unsupported boolean condition " + op.getSymbol()));
|
||||
@@ -94,16 +134,24 @@ public class ConditionGen extends InsnGen {
|
||||
addArg(code, secondArg, isArgWrapNeeded(secondArg));
|
||||
}
|
||||
|
||||
private void addNot(CodeWriter code, IfCondition condition) throws CodegenException {
|
||||
code.add('!');
|
||||
wrap(code, condition.getArgs().get(0));
|
||||
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 addAndOr(CodeWriter code, IfCondition condition) throws CodegenException {
|
||||
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, it.next());
|
||||
wrap(code, stack, it.next());
|
||||
if (it.hasNext()) {
|
||||
code.add(mode);
|
||||
}
|
||||
@@ -111,7 +159,10 @@ public class ConditionGen extends InsnGen {
|
||||
}
|
||||
|
||||
private boolean isWrapNeeded(IfCondition condition) {
|
||||
return !condition.isCompare() && condition.getMode() != Mode.NOT;
|
||||
if (condition.isCompare()) {
|
||||
return false;
|
||||
}
|
||||
return condition.getMode() != Mode.NOT;
|
||||
}
|
||||
|
||||
private static boolean isArgWrapNeeded(InsnArg arg) {
|
||||
|
||||
@@ -3,6 +3,7 @@ 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;
|
||||
@@ -12,19 +13,21 @@ 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.NamedArg;
|
||||
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;
|
||||
@@ -34,22 +37,23 @@ import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.utils.InsnUtils;
|
||||
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;
|
||||
|
||||
import static jadx.core.utils.android.AndroidResourcesUtils.handleAppResField;
|
||||
|
||||
public class InsnGen {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(InsnGen.class);
|
||||
|
||||
@@ -58,9 +62,10 @@ public class InsnGen {
|
||||
protected final RootNode root;
|
||||
protected final boolean fallback;
|
||||
|
||||
private enum Flags {
|
||||
protected enum Flags {
|
||||
BODY_ONLY,
|
||||
BODY_ONLY_NOWRAP,
|
||||
INLINE
|
||||
}
|
||||
|
||||
public InsnGen(MethodGen mgen, boolean fallback) {
|
||||
@@ -75,9 +80,9 @@ public class InsnGen {
|
||||
}
|
||||
|
||||
public void addArgDot(CodeWriter code, InsnArg arg) throws CodegenException {
|
||||
int len = code.length();
|
||||
int len = code.bufLength();
|
||||
addArg(code, arg, true);
|
||||
if (len != code.length()) {
|
||||
if (len != code.bufLength()) {
|
||||
code.add('.');
|
||||
}
|
||||
}
|
||||
@@ -95,7 +100,7 @@ public class InsnGen {
|
||||
Flags flag = wrap ? Flags.BODY_ONLY : Flags.BODY_ONLY_NOWRAP;
|
||||
makeInsn(((InsnWrapArg) arg).getWrapInsn(), code, flag);
|
||||
} else if (arg.isNamed()) {
|
||||
code.add(((NamedArg) arg).getName());
|
||||
code.add(((Named) arg).getName());
|
||||
} else if (arg.isField()) {
|
||||
FieldArg f = (FieldArg) arg;
|
||||
if (f.isStatic()) {
|
||||
@@ -118,47 +123,55 @@ public class InsnGen {
|
||||
}
|
||||
|
||||
public void declareVar(CodeWriter code, RegisterArg arg) {
|
||||
if (arg.getSVar().contains(AFlag.FINAL)) {
|
||||
code.add("final ");
|
||||
}
|
||||
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 String lit(LiteralArg arg) {
|
||||
return TypeGen.literalToString(arg.getLiteral(), arg.getType(), mth);
|
||||
}
|
||||
|
||||
private void instanceField(CodeWriter code, FieldInfo field, InsnArg arg) throws CodegenException {
|
||||
FieldNode fieldNode = mth.getParentClass().searchField(field);
|
||||
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");
|
||||
switch (replace.getReplaceType()) {
|
||||
case CLASS_INSTANCE:
|
||||
useClass(code, replace.getClsRef());
|
||||
code.add(".this");
|
||||
break;
|
||||
case VAR:
|
||||
addArg(code, replace.getVarRef());
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
addArgDot(code, arg);
|
||||
fieldNode = mth.dex().resolveField(field);
|
||||
if (fieldNode != null) {
|
||||
code.attachAnnotation(fieldNode);
|
||||
}
|
||||
code.add(field.getName());
|
||||
code.add(field.getAlias());
|
||||
}
|
||||
|
||||
public static void makeStaticFieldAccess(CodeWriter code, FieldInfo field, ClassGen clsGen) {
|
||||
ClassInfo declClass = field.getDeclClass();
|
||||
boolean fieldFromThisClass = clsGen.getClassNode().getFullName().startsWith(declClass.getFullName());
|
||||
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.getShortName());
|
||||
} else {
|
||||
if (!handleAppResField(code, clsGen, declClass)) {
|
||||
clsGen.useClass(code, declClass);
|
||||
}
|
||||
code.add('.');
|
||||
@@ -167,18 +180,22 @@ public class InsnGen {
|
||||
if (fieldNode != null) {
|
||||
code.attachAnnotation(fieldNode);
|
||||
}
|
||||
code.add(field.getName());
|
||||
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);
|
||||
}
|
||||
|
||||
private void useType(CodeWriter code, ArgType type) {
|
||||
protected void useType(CodeWriter code, ArgType type) {
|
||||
mgen.getClassGen().useType(code, type);
|
||||
}
|
||||
|
||||
@@ -186,26 +203,24 @@ public class InsnGen {
|
||||
return makeInsn(insn, code, null);
|
||||
}
|
||||
|
||||
private boolean makeInsn(InsnNode insn, CodeWriter code, Flags flag) throws CodegenException {
|
||||
protected boolean makeInsn(InsnNode insn, CodeWriter code, Flags flag) throws CodegenException {
|
||||
try {
|
||||
if (insn.getType() == InsnType.NOP) {
|
||||
return false;
|
||||
}
|
||||
EnumSet<Flags> state = EnumSet.noneOf(Flags.class);
|
||||
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 {
|
||||
code.startLine();
|
||||
if (insn.getSourceLine() != 0) {
|
||||
code.attachSourceLine(insn.getSourceLine());
|
||||
if (flag != Flags.INLINE) {
|
||||
code.startLineWithNum(insn.getSourceLine());
|
||||
}
|
||||
if (insn.getResult() != null && insn.getType() != InsnType.ARITH_ONEARG) {
|
||||
if (insn.getResult() != null && !insn.contains(AFlag.ARITH_ONEARG)) {
|
||||
assignVar(code, insn);
|
||||
code.add(" = ");
|
||||
}
|
||||
makeInsnBody(code, insn, state);
|
||||
code.add(';');
|
||||
if (flag != Flags.INLINE) {
|
||||
code.add(';');
|
||||
}
|
||||
}
|
||||
} catch (Throwable th) {
|
||||
throw new CodegenException(mth, "Error generate insn: " + insn, th);
|
||||
@@ -213,11 +228,11 @@ public class InsnGen {
|
||||
return true;
|
||||
}
|
||||
|
||||
private void makeInsnBody(CodeWriter code, InsnNode insn, EnumSet<Flags> state) throws CodegenException {
|
||||
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));
|
||||
code.add(mth.dex().root().getStringUtils().unescapeString(str));
|
||||
break;
|
||||
|
||||
case CONST_CLASS:
|
||||
@@ -255,10 +270,6 @@ public class InsnGen {
|
||||
makeArith((ArithNode) insn, code, state);
|
||||
break;
|
||||
|
||||
case ARITH_ONEARG:
|
||||
makeArithOneArg((ArithNode) insn, code);
|
||||
break;
|
||||
|
||||
case NEG: {
|
||||
boolean wrap = state.contains(Flags.BODY_ONLY);
|
||||
if (wrap) {
|
||||
@@ -283,6 +294,10 @@ public class InsnGen {
|
||||
|
||||
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:
|
||||
@@ -304,7 +319,7 @@ public class InsnGen {
|
||||
addArg(code, insn.getArg(0));
|
||||
code.add(" == ");
|
||||
addArg(code, insn.getArg(1));
|
||||
code.add("? 0 : -1))");
|
||||
code.add(" ? 0 : -1))");
|
||||
break;
|
||||
|
||||
case INSTANCE_OF: {
|
||||
@@ -329,7 +344,7 @@ public class InsnGen {
|
||||
break;
|
||||
|
||||
case NEW_ARRAY: {
|
||||
ArgType arrayType = insn.getResult().getType();
|
||||
ArgType arrayType = ((NewArrayNode) insn).getArrayType();
|
||||
code.add("new ");
|
||||
useType(code, arrayType.getArrayRootElement());
|
||||
code.add('[');
|
||||
@@ -347,12 +362,8 @@ public class InsnGen {
|
||||
code.add(".length");
|
||||
break;
|
||||
|
||||
case FILL_ARRAY:
|
||||
fillArray((FillArrayNode) insn, code);
|
||||
break;
|
||||
|
||||
case FILLED_NEW_ARRAY:
|
||||
filledNewArray(insn, code);
|
||||
filledNewArray((FilledNewArrayNode) insn, code);
|
||||
break;
|
||||
|
||||
case AGET:
|
||||
@@ -425,28 +436,17 @@ public class InsnGen {
|
||||
}
|
||||
break;
|
||||
|
||||
case MOVE_EXCEPTION:
|
||||
if (isFallback()) {
|
||||
code.add("move-exception");
|
||||
} else {
|
||||
addArg(code, insn.getArg(0));
|
||||
}
|
||||
break;
|
||||
|
||||
case TERNARY:
|
||||
makeTernary((TernaryInsn) insn, code, state);
|
||||
break;
|
||||
|
||||
case ARGS:
|
||||
case ONE_ARG:
|
||||
addArg(code, insn.getArg(0));
|
||||
break;
|
||||
|
||||
case PHI:
|
||||
break;
|
||||
|
||||
/* fallback mode instructions */
|
||||
case IF:
|
||||
assert isFallback() : "if insn in not fallback mode";
|
||||
fallbackOnlyInsn(insn);
|
||||
IfNode ifInsn = (IfNode) insn;
|
||||
code.add("if (");
|
||||
addArg(code, insn.getArg(0));
|
||||
@@ -457,12 +457,17 @@ public class InsnGen {
|
||||
break;
|
||||
|
||||
case GOTO:
|
||||
assert isFallback();
|
||||
fallbackOnlyInsn(insn);
|
||||
code.add("goto ").add(MethodGen.getLabelName(((GotoNode) insn).getTarget()));
|
||||
break;
|
||||
|
||||
case MOVE_EXCEPTION:
|
||||
fallbackOnlyInsn(insn);
|
||||
code.add("move-exception");
|
||||
break;
|
||||
|
||||
case SWITCH:
|
||||
assert isFallback();
|
||||
fallbackOnlyInsn(insn);
|
||||
SwitchNode sw = (SwitchNode) insn;
|
||||
code.add("switch(");
|
||||
addArg(code, insn.getArg(0));
|
||||
@@ -479,10 +484,40 @@ public class InsnGen {
|
||||
code.startLine('}');
|
||||
break;
|
||||
|
||||
case FILL_ARRAY:
|
||||
fallbackOnlyInsn(insn);
|
||||
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());
|
||||
fallbackOnlyInsn(insn);
|
||||
code.add("new ").add(insn.getResult().getType().toString());
|
||||
break;
|
||||
|
||||
case PHI:
|
||||
case MERGE:
|
||||
fallbackOnlyInsn(insn);
|
||||
code.add(insn.getType().toString()).add("(");
|
||||
for (InsnArg insnArg : insn.getArguments()) {
|
||||
addArg(code, insnArg);
|
||||
code.add(' ');
|
||||
}
|
||||
code.add(")");
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -490,11 +525,17 @@ public class InsnGen {
|
||||
}
|
||||
}
|
||||
|
||||
private void filledNewArray(InsnNode insn, CodeWriter code) throws CodegenException {
|
||||
int c = insn.getArgsCount();
|
||||
private void fallbackOnlyInsn(InsnNode insn) throws CodegenException {
|
||||
if (!fallback) {
|
||||
throw new CodegenException(insn.getType() + " can be used only in fallback mode");
|
||||
}
|
||||
}
|
||||
|
||||
private void filledNewArray(FilledNewArrayNode insn, CodeWriter code) throws CodegenException {
|
||||
code.add("new ");
|
||||
useType(code, insn.getResult().getType());
|
||||
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) {
|
||||
@@ -504,93 +545,11 @@ public class InsnGen {
|
||||
code.add('}');
|
||||
}
|
||||
|
||||
private void fillArray(FillArrayNode insn, CodeWriter code) throws CodegenException {
|
||||
ArgType insnArrayType = insn.getResult().getType();
|
||||
ArgType insnElementType = insnArrayType.getArrayElement();
|
||||
ArgType elType = insn.getElementType();
|
||||
if (!elType.equals(insnElementType) && !insnArrayType.equals(ArgType.OBJECT)) {
|
||||
ErrorsCounter.methodError(mth,
|
||||
"Incorrect type for fill-array insn " + InsnUtils.formatOffset(insn.getOffset())
|
||||
+ ", element type: " + elType + ", insn element type: " + insnElementType
|
||||
);
|
||||
if (!elType.isTypeKnown()) {
|
||||
elType = insnElementType.isTypeKnown() ? insnElementType : elType.selectFirst();
|
||||
}
|
||||
}
|
||||
StringBuilder str = new StringBuilder();
|
||||
Object data = insn.getData();
|
||||
switch (elType.getPrimitiveType()) {
|
||||
case BOOLEAN:
|
||||
case BYTE:
|
||||
byte[] array = (byte[]) data;
|
||||
for (byte b : array) {
|
||||
str.append(TypeGen.literalToString(b, elType));
|
||||
str.append(", ");
|
||||
}
|
||||
break;
|
||||
case SHORT:
|
||||
case CHAR:
|
||||
short[] sarray = (short[]) data;
|
||||
for (short b : sarray) {
|
||||
str.append(TypeGen.literalToString(b, elType));
|
||||
str.append(", ");
|
||||
}
|
||||
break;
|
||||
case INT:
|
||||
case FLOAT:
|
||||
int[] iarray = (int[]) data;
|
||||
for (int b : iarray) {
|
||||
str.append(TypeGen.literalToString(b, elType));
|
||||
str.append(", ");
|
||||
}
|
||||
break;
|
||||
case LONG:
|
||||
case DOUBLE:
|
||||
long[] larray = (long[]) data;
|
||||
for (long b : larray) {
|
||||
str.append(TypeGen.literalToString(b, elType));
|
||||
str.append(", ");
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new CodegenException(mth, "Unknown type: " + elType);
|
||||
}
|
||||
int len = str.length();
|
||||
str.delete(len - 2, len);
|
||||
code.add("new ");
|
||||
useType(code, elType);
|
||||
code.add("[]{").add(str.toString()).add('}');
|
||||
}
|
||||
|
||||
private void makeConstructor(ConstructorInsn insn, CodeWriter code)
|
||||
throws CodegenException {
|
||||
ClassNode cls = mth.dex().resolveClass(insn.getClassType());
|
||||
if (cls != null && cls.isAnonymous() && !fallback) {
|
||||
// anonymous class construction
|
||||
ClassInfo 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(), fallback).addClassBody(code);
|
||||
if (cls != null && cls.contains(AFlag.ANONYMOUS_CLASS) && !fallback) {
|
||||
inlineAnonymousConstr(code, cls, insn);
|
||||
return;
|
||||
}
|
||||
if (insn.isSelf()) {
|
||||
@@ -604,16 +563,54 @@ public class InsnGen {
|
||||
code.add("new ");
|
||||
useClass(code, insn.getClassType());
|
||||
}
|
||||
generateArguments(code, insn, 0, mth.dex().resolveMethod(insn.getCallMth()));
|
||||
MethodNode callMth = mth.dex().resolveMethod(insn.getCallMth());
|
||||
generateMethodArguments(code, insn, 0, callMth);
|
||||
}
|
||||
|
||||
private void inlineAnonymousConstr(CodeWriter code, ClassNode cls, ConstructorInsn insn) throws CodegenException {
|
||||
// anonymous class construction
|
||||
if (cls.contains(AFlag.DONT_GENERATE)) {
|
||||
code.add("/* anonymous class already generated */");
|
||||
ErrorsCounter.methodError(mth, "Anonymous class already generated: " + cls);
|
||||
return;
|
||||
}
|
||||
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);
|
||||
}
|
||||
MethodNode callMth = mth.dex().resolveMethod(insn.getCallMth());
|
||||
generateMethodArguments(code, insn, 0, callMth);
|
||||
code.add(' ');
|
||||
new ClassGen(cls, mgen.getClassGen().getParentGen()).addClassBody(code);
|
||||
}
|
||||
|
||||
private void makeInvoke(InvokeNode insn, CodeWriter code) throws CodegenException {
|
||||
MethodInfo callMth = insn.getCallMth();
|
||||
|
||||
// inline method
|
||||
MethodNode callMthNode = mth.dex().resolveMethod(callMth);
|
||||
if (callMthNode != null && inlineMethod(callMthNode, insn, code)) {
|
||||
return;
|
||||
MethodNode callMthNode = mth.dex().deepResolveMethod(callMth);
|
||||
if (callMthNode != null) {
|
||||
if (inlineMethod(callMthNode, insn, code)) {
|
||||
return;
|
||||
}
|
||||
callMth = callMthNode.getMethodInfo();
|
||||
}
|
||||
|
||||
int k = 0;
|
||||
@@ -637,7 +634,7 @@ public class InsnGen {
|
||||
break;
|
||||
|
||||
case STATIC:
|
||||
ClassInfo insnCls = mth.getParentClass().getClassInfo();
|
||||
ClassInfo insnCls = mth.getParentClass().getAlias();
|
||||
ClassInfo declClass = callMth.getDeclClass();
|
||||
if (!insnCls.equals(declClass)) {
|
||||
useClass(code, declClass);
|
||||
@@ -648,48 +645,92 @@ public class InsnGen {
|
||||
if (callMthNode != null) {
|
||||
code.attachAnnotation(callMthNode);
|
||||
}
|
||||
code.add(callMth.getName());
|
||||
generateArguments(code, insn, k, callMthNode);
|
||||
code.add(callMth.getAlias());
|
||||
generateMethodArguments(code, insn, k, callMthNode);
|
||||
}
|
||||
|
||||
private void generateArguments(CodeWriter code, InsnNode insn, int k, MethodNode callMth) throws CodegenException {
|
||||
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();
|
||||
if (callMth != null && callMth.isArgsOverload()) {
|
||||
// add additional argument casts for overloaded methods
|
||||
List<ArgType> originalType = callMth.getMethodInfo().getArgumentsTypes();
|
||||
int origPos = 0;
|
||||
code.add('(');
|
||||
code.add('(');
|
||||
boolean firstArg = true;
|
||||
if (k < argsCount) {
|
||||
boolean overloaded = callMth != null && callMth.isArgsOverload();
|
||||
for (int i = k; i < argsCount; i++) {
|
||||
InsnArg arg = insn.getArg(i);
|
||||
ArgType origType = originalType.get(origPos);
|
||||
if (!arg.getType().equals(origType)) {
|
||||
code.add('(');
|
||||
useType(code, origType);
|
||||
code.add(')');
|
||||
addArg(code, arg, true);
|
||||
} else {
|
||||
addArg(code, arg, false);
|
||||
if (arg.contains(AFlag.SKIP_ARG)) {
|
||||
continue;
|
||||
}
|
||||
if (i < argsCount - 1) {
|
||||
RegisterArg callArg = getCallMthArg(callMth, i - startArgNum);
|
||||
if (callArg != null && callArg.contains(AFlag.SKIP_ARG)) {
|
||||
continue;
|
||||
}
|
||||
if (!firstArg) {
|
||||
code.add(", ");
|
||||
}
|
||||
origPos++;
|
||||
}
|
||||
code.add(')');
|
||||
} else {
|
||||
code.add('(');
|
||||
if (k < argsCount) {
|
||||
addArg(code, insn.getArg(k), false);
|
||||
for (int i = k + 1; i < argsCount; i++) {
|
||||
code.add(", ");
|
||||
addArg(code, insn.getArg(i), false);
|
||||
boolean cast = overloaded && processOverloadedArg(code, callMth, arg, i - startArgNum);
|
||||
if (!cast && i == argsCount - 1 && processVarArg(code, callMth, arg)) {
|
||||
continue;
|
||||
}
|
||||
addArg(code, arg, false);
|
||||
firstArg = false;
|
||||
}
|
||||
code.add(')');
|
||||
}
|
||||
code.add(')');
|
||||
}
|
||||
|
||||
private static RegisterArg getCallMthArg(@Nullable MethodNode callMth, int num) {
|
||||
if (callMth == null) {
|
||||
return null;
|
||||
}
|
||||
List<RegisterArg> args = callMth.getArguments(false);
|
||||
if (args != null && num < args.size()) {
|
||||
return args.get(num);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
@@ -710,9 +751,9 @@ public class InsnGen {
|
||||
regs[callArg.getRegNum()] = arg;
|
||||
}
|
||||
// replace args
|
||||
InsnNode inlCopy = inl.copy();
|
||||
List<RegisterArg> inlArgs = new ArrayList<RegisterArg>();
|
||||
inl.getRegisterArgs(inlArgs);
|
||||
Map<RegisterArg, InsnArg> toRevert = new HashMap<RegisterArg, InsnArg>();
|
||||
inlCopy.getRegisterArgs(inlArgs);
|
||||
for (RegisterArg r : inlArgs) {
|
||||
int regNum = r.getRegNum();
|
||||
if (regNum >= regs.length) {
|
||||
@@ -722,21 +763,16 @@ public class InsnGen {
|
||||
if (repl == null) {
|
||||
LOG.warn("Not passed register {} in method call: {} from {}", r, callMthNode, mth);
|
||||
} else {
|
||||
inl.replaceArg(r, repl);
|
||||
toRevert.put(r, repl);
|
||||
inlCopy.replaceArg(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());
|
||||
}
|
||||
makeInsn(inlCopy, code, Flags.BODY_ONLY);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void makeTernary(TernaryInsn insn, CodeWriter code, EnumSet<Flags> state) throws CodegenException {
|
||||
private void makeTernary(TernaryInsn insn, CodeWriter code, Set<Flags> state) throws CodegenException {
|
||||
boolean wrap = state.contains(Flags.BODY_ONLY);
|
||||
if (wrap) {
|
||||
code.add('(');
|
||||
@@ -758,7 +794,11 @@ public class InsnGen {
|
||||
}
|
||||
}
|
||||
|
||||
private void makeArith(ArithNode insn, CodeWriter code, EnumSet<Flags> state) throws CodegenException {
|
||||
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) {
|
||||
@@ -776,7 +816,7 @@ public class InsnGen {
|
||||
|
||||
private void makeArithOneArg(ArithNode insn, CodeWriter code) throws CodegenException {
|
||||
ArithOp op = insn.getOp();
|
||||
InsnArg arg = insn.getArg(0);
|
||||
InsnArg arg = insn.getArg(1);
|
||||
// "++" or "--"
|
||||
if (arg.isLiteral() && (op == ArithOp.ADD || op == ArithOp.SUB)) {
|
||||
LiteralArg lit = (LiteralArg) arg;
|
||||
|
||||
@@ -5,8 +5,10 @@ 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.instructions.args.SSAVar;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.trycatch.CatchAttr;
|
||||
@@ -38,7 +40,7 @@ public class MethodGen {
|
||||
this.mth = mth;
|
||||
this.classGen = classGen;
|
||||
this.annotationGen = classGen.getAnnotationGen();
|
||||
this.nameGen = new NameGen(classGen.isFallbackMode());
|
||||
this.nameGen = new NameGen(mth, classGen.isFallbackMode());
|
||||
}
|
||||
|
||||
public ClassGen getClassGen() {
|
||||
@@ -55,8 +57,8 @@ public class MethodGen {
|
||||
|
||||
public boolean addDefinition(CodeWriter code) {
|
||||
if (mth.getMethodInfo().isClassInit()) {
|
||||
code.startLine("static");
|
||||
code.attachDefinition(mth);
|
||||
code.startLine("static");
|
||||
return true;
|
||||
}
|
||||
if (mth.contains(AFlag.ANONYMOUS_CONSTRUCTOR)) {
|
||||
@@ -78,18 +80,20 @@ public class MethodGen {
|
||||
if (clsAccFlags.isAnnotation()) {
|
||||
ai = ai.remove(AccessFlags.ACC_PUBLIC);
|
||||
}
|
||||
code.startLine(ai.makeString());
|
||||
code.attachSourceLine(mth.getSourceLine());
|
||||
code.startLineWithNum(mth.getSourceLine());
|
||||
code.add(ai.makeString());
|
||||
|
||||
if (classGen.addGenericMap(code, mth.getGenericMap())) {
|
||||
code.add(' ');
|
||||
}
|
||||
if (mth.getAccessFlags().isConstructor()) {
|
||||
code.attachDefinition(mth);
|
||||
code.add(classGen.getClassNode().getShortName()); // constructor
|
||||
} else {
|
||||
classGen.useType(code, mth.getReturnType());
|
||||
code.add(' ');
|
||||
code.add(mth.getName());
|
||||
code.attachDefinition(mth);
|
||||
code.add(mth.getAlias());
|
||||
}
|
||||
code.add('(');
|
||||
|
||||
@@ -111,7 +115,6 @@ public class MethodGen {
|
||||
code.add(')');
|
||||
|
||||
annotationGen.addThrows(mth, code);
|
||||
code.attachDefinition(mth);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -125,6 +128,10 @@ public class MethodGen {
|
||||
if (paramsAnnotation != null) {
|
||||
annotationGen.addForParameter(argsCode, paramsAnnotation, i);
|
||||
}
|
||||
SSAVar argSVar = arg.getSVar();
|
||||
if (argSVar!= null && argSVar.contains(AFlag.FINAL)) {
|
||||
argsCode.add("final ");
|
||||
}
|
||||
if (!it.hasNext() && mth.getAccessFlags().isVarArgs()) {
|
||||
// change last array argument to varargs
|
||||
ArgType type = arg.getType();
|
||||
@@ -153,24 +160,24 @@ public class MethodGen {
|
||||
if (mth.contains(AType.JADX_ERROR)
|
||||
|| mth.contains(AFlag.INCONSISTENT_CODE)
|
||||
|| mth.getRegion() == null) {
|
||||
code.startLine("throw new UnsupportedOperationException(\"Method not decompiled: ")
|
||||
.add(mth.toString())
|
||||
.add("\");");
|
||||
|
||||
if (mth.contains(AType.JADX_ERROR)) {
|
||||
JadxErrorAttr err = mth.get(AType.JADX_ERROR);
|
||||
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.newLine().add("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());
|
||||
@@ -179,14 +186,19 @@ public class MethodGen {
|
||||
|
||||
public void addFallbackMethodCode(CodeWriter code) {
|
||||
if (mth.getInstructions() == null) {
|
||||
// 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 loadFile method instructions: " + e.getMessage());
|
||||
return;
|
||||
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();
|
||||
@@ -203,16 +215,13 @@ public class MethodGen {
|
||||
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) {
|
||||
if (insn == null || insn.getType() == InsnType.NOP) {
|
||||
continue;
|
||||
}
|
||||
if (addLabels) {
|
||||
if (insn.contains(AType.JUMP)
|
||||
|| insn.contains(AType.EXC_HANDLER)) {
|
||||
code.decIndent();
|
||||
code.startLine(getLabelName(insn.getOffset()) + ":");
|
||||
code.incIndent();
|
||||
}
|
||||
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)) {
|
||||
@@ -231,8 +240,8 @@ public class MethodGen {
|
||||
/**
|
||||
* Return fallback variant of method codegen
|
||||
*/
|
||||
static MethodGen getFallbackMethodGen(MethodNode mth) {
|
||||
ClassGen clsGen = new ClassGen(mth.getParentClass(), null, true);
|
||||
public static MethodGen getFallbackMethodGen(MethodNode mth) {
|
||||
ClassGen clsGen = new ClassGen(mth.getParentClass(), null, true, true);
|
||||
return new MethodGen(clsGen, mth);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,9 @@ 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;
|
||||
@@ -12,7 +14,8 @@ 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.utils.Utils;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.utils.StringUtils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
@@ -24,6 +27,7 @@ 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 {
|
||||
@@ -43,7 +47,8 @@ public class NameGen {
|
||||
OBJ_ALIAS.put("java.lang.Double", "d");
|
||||
}
|
||||
|
||||
public NameGen(boolean fallback) {
|
||||
public NameGen(MethodNode mth, boolean fallback) {
|
||||
this.mth = mth;
|
||||
this.fallback = fallback;
|
||||
}
|
||||
|
||||
@@ -53,10 +58,7 @@ public class NameGen {
|
||||
return name;
|
||||
}
|
||||
name = getUniqueVarName(name);
|
||||
SSAVar sVar = arg.getSVar();
|
||||
if (sVar != null) {
|
||||
sVar.setName(name);
|
||||
}
|
||||
arg.setName(name);
|
||||
return name;
|
||||
}
|
||||
|
||||
@@ -71,7 +73,16 @@ public class NameGen {
|
||||
}
|
||||
|
||||
public String useArg(RegisterArg arg) {
|
||||
String name = makeArgName(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;
|
||||
}
|
||||
@@ -88,14 +99,10 @@ public class NameGen {
|
||||
}
|
||||
|
||||
private String makeArgName(RegisterArg arg) {
|
||||
String name = arg.getName();
|
||||
if (fallback) {
|
||||
String base = "r" + arg.getRegNum();
|
||||
if (name != null && !name.equals("this")) {
|
||||
return base + "_" + name;
|
||||
}
|
||||
return base;
|
||||
return getFallbackName(arg);
|
||||
}
|
||||
String name = arg.getName();
|
||||
String varName;
|
||||
if (name != null) {
|
||||
if ("this".equals(name)) {
|
||||
@@ -103,7 +110,7 @@ public class NameGen {
|
||||
}
|
||||
varName = name;
|
||||
} else {
|
||||
varName = makeNameForType(arg.getType());
|
||||
varName = guessName(arg);
|
||||
}
|
||||
if (NameMapper.isReserved(varName)) {
|
||||
return varName + "R";
|
||||
@@ -111,7 +118,27 @@ public class NameGen {
|
||||
return varName;
|
||||
}
|
||||
|
||||
private static String makeNameForType(ArgType type) {
|
||||
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()) {
|
||||
@@ -125,20 +152,20 @@ public class NameGen {
|
||||
return type.getPrimitiveType().getShortName().toLowerCase();
|
||||
}
|
||||
|
||||
private static String makeNameForObject(ArgType type) {
|
||||
private String makeNameForObject(ArgType type) {
|
||||
if (type.isObject()) {
|
||||
String alias = getAliasForObject(type.getObject());
|
||||
if (alias != null) {
|
||||
return alias;
|
||||
}
|
||||
ClassInfo clsInfo = ClassInfo.fromType(type);
|
||||
String shortName = clsInfo.getShortName();
|
||||
ClassInfo extClsInfo = ClassInfo.extCls(mth.dex(), type);
|
||||
String shortName = extClsInfo.getShortName();
|
||||
String vName = fromName(shortName);
|
||||
if (vName != null) {
|
||||
return vName;
|
||||
}
|
||||
}
|
||||
return Utils.escape(type.toString());
|
||||
return StringUtils.escape(type.toString());
|
||||
}
|
||||
|
||||
private static String fromName(String name) {
|
||||
@@ -159,35 +186,15 @@ public class NameGen {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void guessName(RegisterArg arg) {
|
||||
SSAVar sVar = arg.getSVar();
|
||||
if (sVar == null || sVar.getName() != null) {
|
||||
return;
|
||||
}
|
||||
RegisterArg assignArg = sVar.getAssign();
|
||||
InsnNode assignInsn = assignArg.getParentInsn();
|
||||
String name = makeNameFromInsn(assignInsn);
|
||||
if (name != null && !NameMapper.isReserved(name)) {
|
||||
assignArg.setName(name);
|
||||
}
|
||||
}
|
||||
|
||||
private static String getAliasForObject(String name) {
|
||||
return OBJ_ALIAS.get(name);
|
||||
}
|
||||
|
||||
private static String makeNameFromInsn(InsnNode insn) {
|
||||
private String makeNameFromInsn(InsnNode insn) {
|
||||
switch (insn.getType()) {
|
||||
case INVOKE:
|
||||
InvokeNode inv = (InvokeNode) insn;
|
||||
String name = inv.getCallMth().getName();
|
||||
if (name.startsWith("get") || name.startsWith("set")) {
|
||||
return fromName(name.substring(3));
|
||||
}
|
||||
if ("iterator".equals(name)) {
|
||||
return "it";
|
||||
}
|
||||
return name;
|
||||
return makeNameFromInvoke(inv.getCallMth());
|
||||
|
||||
case CONSTRUCTOR:
|
||||
ConstructorInsn co = (ConstructorInsn) insn;
|
||||
@@ -215,4 +222,22 @@ public class NameGen {
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,29 +4,36 @@ 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.info.FieldInfo;
|
||||
import jadx.core.dex.instructions.IndexInsnNode;
|
||||
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.regions.IfCondition;
|
||||
import jadx.core.dex.regions.IfRegion;
|
||||
import jadx.core.dex.regions.LoopRegion;
|
||||
import jadx.core.dex.nodes.parser.FieldInitAttr;
|
||||
import jadx.core.dex.regions.Region;
|
||||
import jadx.core.dex.regions.SwitchRegion;
|
||||
import jadx.core.dex.regions.SynchronizedRegion;
|
||||
import jadx.core.dex.trycatch.CatchAttr;
|
||||
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.dex.trycatch.TryCatchBlock;
|
||||
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;
|
||||
@@ -52,6 +59,8 @@ public class RegionGen extends InsnGen {
|
||||
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);
|
||||
}
|
||||
@@ -73,14 +82,9 @@ public class RegionGen extends InsnGen {
|
||||
}
|
||||
|
||||
private void makeSimpleRegion(CodeWriter code, Region region) throws CodegenException {
|
||||
CatchAttr tc = region.get(AType.CATCH_BLOCK);
|
||||
if (tc != null) {
|
||||
makeTryCatch(region, tc.getTryBlock(), code);
|
||||
} else {
|
||||
declareVars(code, region);
|
||||
for (IContainer c : region.getSubBlocks()) {
|
||||
makeRegion(code, c);
|
||||
}
|
||||
declareVars(code, region);
|
||||
for (IContainer c : region.getSubBlocks()) {
|
||||
makeRegion(code, c);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,7 +96,9 @@ public class RegionGen extends InsnGen {
|
||||
|
||||
private void makeSimpleBlock(IBlock block, CodeWriter code) throws CodegenException {
|
||||
for (InsnNode insn : block.getInstructions()) {
|
||||
makeInsn(insn, code);
|
||||
if (!insn.contains(AFlag.SKIP)) {
|
||||
makeInsn(insn, code);
|
||||
}
|
||||
}
|
||||
ForceReturnAttr retAttr = block.get(AType.FORCE_RETURN);
|
||||
if (retAttr != null) {
|
||||
@@ -101,14 +107,11 @@ public class RegionGen extends InsnGen {
|
||||
}
|
||||
|
||||
private void makeIf(IfRegion region, CodeWriter code, boolean newLine) throws CodegenException {
|
||||
if (region.getTernRegion() != null) {
|
||||
makeSimpleBlock(region.getTernRegion().getBlock(), code);
|
||||
return;
|
||||
}
|
||||
if (newLine) {
|
||||
code.startLine();
|
||||
code.startLineWithNum(region.getSourceLine());
|
||||
} else {
|
||||
code.attachSourceLine(region.getSourceLine());
|
||||
}
|
||||
code.attachSourceLine(region.getSourceLine());
|
||||
code.add("if (");
|
||||
new ConditionGen(this).add(code, region.getCondition());
|
||||
code.add(") {");
|
||||
@@ -151,8 +154,7 @@ public class RegionGen extends InsnGen {
|
||||
if (header != null) {
|
||||
List<InsnNode> headerInsns = header.getInstructions();
|
||||
if (headerInsns.size() > 1) {
|
||||
// write not inlined instructions from header
|
||||
mth.add(AFlag.INCONSISTENT_CODE);
|
||||
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);
|
||||
@@ -160,6 +162,10 @@ public class RegionGen extends InsnGen {
|
||||
}
|
||||
}
|
||||
}
|
||||
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) {
|
||||
@@ -169,8 +175,35 @@ public class RegionGen extends InsnGen {
|
||||
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());
|
||||
@@ -209,77 +242,82 @@ public class RegionGen extends InsnGen {
|
||||
IContainer c = sw.getCases().get(i);
|
||||
for (Object k : keys) {
|
||||
code.startLine("case ");
|
||||
if (k instanceof IndexInsnNode) {
|
||||
staticField(code, (FieldInfo) ((IndexInsnNode) k).getIndex());
|
||||
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
|
||||
FieldInitAttr valueAttr = fn.get(AType.FIELD_INIT);
|
||||
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(), mth));
|
||||
} else {
|
||||
code.add(TypeGen.literalToString((Integer) k, arg.getType()));
|
||||
throw new JadxRuntimeException("Unexpected key in switch: " + (k != null ? k.getClass() : null));
|
||||
}
|
||||
code.add(':');
|
||||
}
|
||||
makeCaseBlock(c, code);
|
||||
makeRegionIndent(code, c);
|
||||
}
|
||||
if (sw.getDefaultCase() != null) {
|
||||
code.startLine("default:");
|
||||
makeCaseBlock(sw.getDefaultCase(), code);
|
||||
makeRegionIndent(code, sw.getDefaultCase());
|
||||
}
|
||||
code.decIndent();
|
||||
code.startLine('}');
|
||||
return code;
|
||||
}
|
||||
|
||||
private void makeCaseBlock(IContainer c, CodeWriter code) throws CodegenException {
|
||||
boolean addBreak = true;
|
||||
if (RegionUtils.notEmpty(c)) {
|
||||
makeRegionIndent(code, c);
|
||||
if (!RegionUtils.hasExitEdge(c)) {
|
||||
addBreak = false;
|
||||
}
|
||||
}
|
||||
if (addBreak) {
|
||||
code.startLine().addIndent().add("break;");
|
||||
}
|
||||
}
|
||||
|
||||
private void makeTryCatch(IContainer region, TryCatchBlock tryCatchBlock, CodeWriter code)
|
||||
throws CodegenException {
|
||||
private void makeTryCatch(TryCatchRegion region, CodeWriter code) throws CodegenException {
|
||||
code.startLine("try {");
|
||||
region.remove(AType.CATCH_BLOCK);
|
||||
makeRegionIndent(code, region);
|
||||
makeRegionIndent(code, region.getTryRegion());
|
||||
// TODO: move search of 'allHandler' to 'TryCatchRegion'
|
||||
ExceptionHandler allHandler = null;
|
||||
for (ExceptionHandler handler : tryCatchBlock.getHandlers()) {
|
||||
if (!handler.isCatchAll()) {
|
||||
makeCatchBlock(code, handler);
|
||||
} else {
|
||||
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);
|
||||
LOG.warn("Several 'all' handlers in try/catch block in {}", mth);
|
||||
}
|
||||
allHandler = handler;
|
||||
} else {
|
||||
makeCatchBlock(code, handler);
|
||||
}
|
||||
}
|
||||
if (allHandler != null) {
|
||||
makeCatchBlock(code, allHandler);
|
||||
}
|
||||
if (tryCatchBlock.getFinalRegion() != null) {
|
||||
IContainer finallyRegion = region.getFinallyRegion();
|
||||
if (finallyRegion != null) {
|
||||
code.startLine("} finally {");
|
||||
makeRegionIndent(code, tryCatchBlock.getFinalRegion());
|
||||
makeRegionIndent(code, finallyRegion);
|
||||
}
|
||||
code.startLine('}');
|
||||
}
|
||||
|
||||
private void makeCatchBlock(CodeWriter code, ExceptionHandler handler)
|
||||
throws CodegenException {
|
||||
private void makeCatchBlock(CodeWriter code, ExceptionHandler handler) throws CodegenException {
|
||||
IContainer region = handler.getHandlerRegion();
|
||||
if (region != null) {
|
||||
code.startLine("} catch (");
|
||||
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(handler.getArg()));
|
||||
code.add(") {");
|
||||
makeRegionIndent(code, region);
|
||||
code.add(mgen.getNameGen().assignNamedArg((NamedArg) arg));
|
||||
}
|
||||
code.add(") {");
|
||||
makeRegionIndent(code, region);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,21 @@
|
||||
package jadx.core.codegen;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.PrimitiveType;
|
||||
import jadx.core.dex.nodes.IDexNode;
|
||||
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();
|
||||
@@ -24,7 +33,16 @@ public class TypeGen {
|
||||
*
|
||||
* @throws JadxRuntimeException for incorrect type or literal value
|
||||
*/
|
||||
public static String literalToString(long lit, ArgType type, IDexNode dexNode) {
|
||||
return literalToString(lit, type, dexNode.root().getStringUtils());
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static String literalToString(long lit, ArgType type) {
|
||||
return literalToString(lit, type, new StringUtils(new JadxArgs()));
|
||||
}
|
||||
|
||||
private static String literalToString(long lit, ArgType type, StringUtils stringUtils) {
|
||||
if (type == null || !type.isTypeKnown()) {
|
||||
String n = Long.toString(lit);
|
||||
if (Math.abs(lit) > 100) {
|
||||
@@ -39,7 +57,7 @@ public class TypeGen {
|
||||
case BOOLEAN:
|
||||
return lit == 0 ? "false" : "true";
|
||||
case CHAR:
|
||||
return StringUtils.unescapeChar((char) lit);
|
||||
return stringUtils.unescapeChar((char) lit);
|
||||
case BYTE:
|
||||
return formatByte((byte) lit);
|
||||
case SHORT:
|
||||
@@ -56,7 +74,8 @@ public class TypeGen {
|
||||
case OBJECT:
|
||||
case ARRAY:
|
||||
if (lit != 0) {
|
||||
throw new JadxRuntimeException("Wrong object literal: " + type + " = " + lit);
|
||||
LOG.warn("Wrong object literal: {} for type: {}", lit, type);
|
||||
return Long.toString(lit);
|
||||
}
|
||||
return "null";
|
||||
|
||||
|
||||
@@ -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,546 @@
|
||||
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.instructions.args.ArgType;
|
||||
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.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
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 Map<MethodInfo, OverridedMethodsNode> ovrdMap = new HashMap<MethodInfo, OverridedMethodsNode>();
|
||||
private final List<OverridedMethodsNode> ovrd = new ArrayList<OverridedMethodsNode>();
|
||||
|
||||
private final PackageNode rootPackage = new PackageNode("");
|
||||
private final Set<String> pkgSet = new TreeSet<String>();
|
||||
|
||||
private final int maxLength;
|
||||
private final int minLength;
|
||||
private final boolean useSourceNameAsAlias;
|
||||
|
||||
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.useSourceNameAsAlias = args.useSourceNameAsClassAlias();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
postProcess();
|
||||
}
|
||||
|
||||
private void postProcess() {
|
||||
int id = 1;
|
||||
for (OverridedMethodsNode o : ovrd) {
|
||||
|
||||
Iterator<MethodInfo> it = o.getMethods().iterator();
|
||||
if (it.hasNext()) {
|
||||
MethodInfo mth = it.next();
|
||||
|
||||
if (mth.isRenamed() && !mth.isAliasFromPreset()) {
|
||||
mth.setAlias(String.format("mo%d%s", id, makeName(mth.getName())));
|
||||
}
|
||||
String firstMethodAlias = mth.getAlias();
|
||||
|
||||
while (it.hasNext()) {
|
||||
mth = it.next();
|
||||
if (!mth.getAlias().equals(firstMethodAlias)) {
|
||||
mth.setAlias(firstMethodAlias);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
id++;
|
||||
}
|
||||
}
|
||||
|
||||
void clear() {
|
||||
deobfPresets.clear();
|
||||
clsMap.clear();
|
||||
fldMap.clear();
|
||||
mthMap.clear();
|
||||
|
||||
ovrd.clear();
|
||||
ovrdMap.clear();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static ClassNode resolveOverridingInternal(DexNode dex, ClassNode cls, String signature,
|
||||
Set<MethodInfo> overrideSet, ClassNode rootClass) {
|
||||
ClassNode result = null;
|
||||
|
||||
for (MethodNode m : cls.getMethods()) {
|
||||
if (m.getMethodInfo().getShortId().startsWith(signature)) {
|
||||
result = cls;
|
||||
if (!overrideSet.contains(m.getMethodInfo())) {
|
||||
overrideSet.add(m.getMethodInfo());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ArgType superClass = cls.getSuperClass();
|
||||
if (superClass != null) {
|
||||
ClassNode superNode = dex.resolveClass(superClass);
|
||||
if (superNode != null) {
|
||||
ClassNode clsWithMth = resolveOverridingInternal(dex, superNode, signature, overrideSet, rootClass);
|
||||
if (clsWithMth != null) {
|
||||
if ((result != null) && (result != cls)) {
|
||||
if (clsWithMth != result) {
|
||||
LOG.warn(String.format("Multiple overriding '%s' from '%s' and '%s' in '%s'",
|
||||
signature,
|
||||
result.getFullName(), clsWithMth.getFullName(),
|
||||
rootClass.getFullName()));
|
||||
}
|
||||
} else {
|
||||
result = clsWithMth;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (ArgType iFaceType : cls.getInterfaces()) {
|
||||
ClassNode iFaceNode = dex.resolveClass(iFaceType);
|
||||
if (iFaceNode != null) {
|
||||
ClassNode clsWithMth = resolveOverridingInternal(dex, iFaceNode, signature, overrideSet, rootClass);
|
||||
if (clsWithMth != null) {
|
||||
if ((result != null) && (result != cls)) {
|
||||
if (clsWithMth != result) {
|
||||
LOG.warn(String.format("Multiple overriding '%s' from '%s' and '%s' in '%s'",
|
||||
signature,
|
||||
result.getFullName(), clsWithMth.getFullName(),
|
||||
rootClass.getFullName()));
|
||||
}
|
||||
} else {
|
||||
result = clsWithMth;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void resolveOverriding(DexNode dex, ClassNode cls, MethodNode mth) {
|
||||
Set<MethodInfo> overrideSet = new HashSet<MethodInfo>();
|
||||
resolveOverridingInternal(dex, cls, mth.getMethodInfo().makeSignature(false), overrideSet, cls);
|
||||
|
||||
if (overrideSet.size() > 1) {
|
||||
OverridedMethodsNode overrideNode = null;
|
||||
for (MethodInfo _mth : overrideSet) {
|
||||
if (ovrdMap.containsKey(_mth)) {
|
||||
overrideNode = ovrdMap.get(_mth);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (overrideNode == null) {
|
||||
overrideNode = new OverridedMethodsNode(overrideSet);
|
||||
ovrd.add(overrideNode);
|
||||
}
|
||||
|
||||
for (MethodInfo _mth : overrideSet) {
|
||||
if (!ovrdMap.containsKey(_mth)) {
|
||||
ovrdMap.put(_mth, overrideNode);
|
||||
if (!overrideNode.contains(_mth)) {
|
||||
overrideNode.add(_mth);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
overrideSet.clear();
|
||||
overrideSet = null;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
if (mth.isVirtual()) {
|
||||
resolveOverriding(dex, cls, mth);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 = null;
|
||||
|
||||
if (this.useSourceNameAsAlias) {
|
||||
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);
|
||||
methodInfo.setAliasFromPreset(true);
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,16 @@ 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",
|
||||
@@ -68,4 +75,25 @@ public class NameMapper {
|
||||
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,26 @@
|
||||
package jadx.core.deobf;
|
||||
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/* package */ class OverridedMethodsNode {
|
||||
|
||||
private Set<MethodInfo> methods;
|
||||
|
||||
public OverridedMethodsNode(Set<MethodInfo> methodsSet) {
|
||||
methods = methodsSet;
|
||||
}
|
||||
|
||||
public boolean contains(MethodInfo mth) {
|
||||
return methods.contains(mth);
|
||||
}
|
||||
|
||||
public void add(MethodInfo mth) {
|
||||
methods.add(mth);
|
||||
}
|
||||
|
||||
public Set<MethodInfo> getMethods() {
|
||||
return methods;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
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();
|
||||
|
||||
if (pp.size() > 0) {
|
||||
result.append(pp.pop().getAlias());
|
||||
while (pp.size() > 0) {
|
||||
result.append(SEPARATOR_CHAR);
|
||||
result.append(pp.pop().getAlias());
|
||||
}
|
||||
} else {
|
||||
result.append(this.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;
|
||||
}
|
||||
}
|
||||
@@ -8,20 +8,31 @@ public enum AFlag {
|
||||
LOOP_END,
|
||||
|
||||
SYNTHETIC,
|
||||
FINAL, // SSAVar attribute for make var final
|
||||
|
||||
RETURN, // block contains only return instruction
|
||||
ORIG_RETURN,
|
||||
|
||||
DECLARE_VAR,
|
||||
DONT_WRAP,
|
||||
|
||||
DONT_SHRINK,
|
||||
DONT_INLINE,
|
||||
DONT_GENERATE,
|
||||
SKIP,
|
||||
REMOVE,
|
||||
|
||||
SKIP_FIRST_ARG,
|
||||
SKIP_ARG, // skip argument in invoke call
|
||||
ANONYMOUS_CONSTRUCTOR,
|
||||
ANONYMOUS_CLASS,
|
||||
|
||||
ELSE_IF_CHAIN,
|
||||
|
||||
WRAPPED,
|
||||
ARITH_ONEARG,
|
||||
|
||||
FALL_THROUGH,
|
||||
|
||||
INCONSISTENT_CODE, // warning about incorrect decompilation
|
||||
}
|
||||
|
||||
@@ -3,46 +3,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.EdgeInsnAttr;
|
||||
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.nodes.parser.FieldInitAttr;
|
||||
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 in 'AttributeStorage.get' method
|
||||
* uses generic type for omit cast after 'AttributeStorage.get' method
|
||||
*
|
||||
* @param <T> attribute class implementation
|
||||
*/
|
||||
public class AType<T extends IAttribute> {
|
||||
|
||||
private AType() {
|
||||
}
|
||||
|
||||
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<AttrList<EdgeInsnAttr>> EDGE_INSN = new AType<AttrList<EdgeInsnAttr>>();
|
||||
|
||||
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<FieldInitAttr> FIELD_INIT = new AType<FieldInitAttr>();
|
||||
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>();
|
||||
}
|
||||
|
||||
@@ -12,25 +12,28 @@ public abstract class AttrNode implements IAttributeNode {
|
||||
|
||||
@Override
|
||||
public void add(AFlag flag) {
|
||||
getStorage().add(flag);
|
||||
initStorage().add(flag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAttr(IAttribute attr) {
|
||||
getStorage().add(attr);
|
||||
initStorage().add(attr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void addAttr(AType<AttrList<T>> type, T obj) {
|
||||
getStorage().add(type, obj);
|
||||
initStorage().add(type, obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyAttributesFrom(AttrNode attrNode) {
|
||||
getStorage().addAll(attrNode.storage);
|
||||
AttributeStorage copyFrom = attrNode.storage;
|
||||
if (!copyFrom.isEmpty()) {
|
||||
initStorage().addAll(copyFrom);
|
||||
}
|
||||
}
|
||||
|
||||
AttributeStorage getStorage() {
|
||||
private AttributeStorage initStorage() {
|
||||
AttributeStorage store = storage;
|
||||
if (store == EMPTY_ATTR_STORAGE) {
|
||||
store = new AttributeStorage();
|
||||
|
||||
@@ -7,7 +7,7 @@ import jadx.core.utils.Utils;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@@ -24,7 +24,7 @@ public class AttributeStorage {
|
||||
|
||||
public AttributeStorage() {
|
||||
flags = EnumSet.noneOf(AFlag.class);
|
||||
attributes = new HashMap<AType<?>, IAttribute>(2);
|
||||
attributes = new IdentityHashMap<AType<?>, IAttribute>();
|
||||
}
|
||||
|
||||
public void add(AFlag flag) {
|
||||
@@ -72,7 +72,7 @@ public class AttributeStorage {
|
||||
if (attrList == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return attrList.getList();
|
||||
return Collections.unmodifiableList(attrList.getList());
|
||||
}
|
||||
|
||||
public void remove(AFlag flag) {
|
||||
@@ -111,6 +111,10 @@ public class AttributeStorage {
|
||||
return list;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return flags.isEmpty() && attributes.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
List<String> list = getAttributeStrings();
|
||||
|
||||
@@ -5,7 +5,7 @@ import jadx.core.dex.attributes.annotations.Annotation;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class EmptyAttrStorage extends AttributeStorage {
|
||||
public final class EmptyAttrStorage extends AttributeStorage {
|
||||
|
||||
@Override
|
||||
public boolean contains(AFlag flag) {
|
||||
@@ -52,4 +52,14 @@ public class EmptyAttrStorage extends AttributeStorage {
|
||||
public List<String> getAttributeStrings() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,12 +5,15 @@ import jadx.core.dex.attributes.IAttribute;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class AnnotationsList implements IAttribute {
|
||||
|
||||
public static final AnnotationsList EMPTY = new AnnotationsList(Collections.<Annotation>emptyList());
|
||||
|
||||
private final Map<String, Annotation> map;
|
||||
|
||||
public AnnotationsList(List<Annotation> anList) {
|
||||
@@ -32,6 +35,10 @@ public class AnnotationsList implements IAttribute {
|
||||
return map.size();
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return map.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AType<AnnotationsList> getType() {
|
||||
return AType.ANNOTATION_LIST;
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package jadx.core.dex.attributes.nodes;
|
||||
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.AttrList;
|
||||
import jadx.core.dex.attributes.IAttribute;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
|
||||
public class EdgeInsnAttr implements IAttribute {
|
||||
|
||||
private final BlockNode start;
|
||||
private final BlockNode end;
|
||||
private final InsnNode insn;
|
||||
|
||||
public static void addEdgeInsn(BlockNode start, BlockNode end, InsnNode insn) {
|
||||
EdgeInsnAttr edgeInsnAttr = new EdgeInsnAttr(start, end, insn);
|
||||
start.addAttr(AType.EDGE_INSN, edgeInsnAttr);
|
||||
end.addAttr(AType.EDGE_INSN, edgeInsnAttr);
|
||||
}
|
||||
|
||||
public EdgeInsnAttr(BlockNode start, BlockNode end, InsnNode insn) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
this.insn = insn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AType<AttrList<EdgeInsnAttr>> getType() {
|
||||
return AType.EDGE_INSN;
|
||||
}
|
||||
|
||||
public BlockNode getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
public BlockNode getEnd() {
|
||||
return end;
|
||||
}
|
||||
|
||||
public InsnNode getInsn() {
|
||||
return insn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "EDGE_INSN: " + start + "->" + end + " " + insn;
|
||||
}
|
||||
}
|
||||
@@ -2,37 +2,38 @@ package jadx.core.dex.attributes.nodes;
|
||||
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.IAttribute;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
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 jadx.core.utils.Utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class EnumClassAttr implements IAttribute {
|
||||
|
||||
public static class EnumField {
|
||||
private final String name;
|
||||
private final List<InsnArg> args;
|
||||
private final FieldInfo field;
|
||||
private final ConstructorInsn constrInsn;
|
||||
private final int startArg;
|
||||
private ClassNode cls;
|
||||
|
||||
public EnumField(String name, int argsCount) {
|
||||
this.name = name;
|
||||
if (argsCount != 0) {
|
||||
this.args = new ArrayList<InsnArg>(argsCount);
|
||||
} else {
|
||||
this.args = Collections.emptyList();
|
||||
}
|
||||
public EnumField(FieldInfo field, ConstructorInsn co, int startArg) {
|
||||
this.field = field;
|
||||
this.constrInsn = co;
|
||||
this.startArg = startArg;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
public FieldInfo getField() {
|
||||
return field;
|
||||
}
|
||||
|
||||
public List<InsnArg> getArgs() {
|
||||
return args;
|
||||
public ConstructorInsn getConstrInsn() {
|
||||
return constrInsn;
|
||||
}
|
||||
|
||||
public int getStartArg() {
|
||||
return startArg;
|
||||
}
|
||||
|
||||
public ClassNode getCls() {
|
||||
@@ -45,7 +46,7 @@ public class EnumClassAttr implements IAttribute {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name + "(" + Utils.listToString(args) + ") " + cls;
|
||||
return field + "(" + constrInsn + ") " + cls;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,24 +2,39 @@ 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.info.ClassInfo;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
|
||||
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 enum ReplaceWith {
|
||||
CLASS_INSTANCE,
|
||||
VAR
|
||||
}
|
||||
|
||||
public FieldInfo getFieldInfo() {
|
||||
return fieldInfo;
|
||||
private final ReplaceWith replaceType;
|
||||
private final Object replaceObj;
|
||||
|
||||
public FieldReplaceAttr(ClassInfo cls) {
|
||||
this.replaceType = ReplaceWith.CLASS_INSTANCE;
|
||||
this.replaceObj = cls;
|
||||
}
|
||||
|
||||
public boolean isOuterClass() {
|
||||
return isOuterClass;
|
||||
public FieldReplaceAttr(InsnArg reg) {
|
||||
this.replaceType = ReplaceWith.VAR;
|
||||
this.replaceObj = reg;
|
||||
}
|
||||
|
||||
public ReplaceWith getReplaceType() {
|
||||
return replaceType;
|
||||
}
|
||||
|
||||
public ClassInfo getClsRef() {
|
||||
return (ClassInfo) replaceObj;
|
||||
}
|
||||
|
||||
public InsnArg getVarRef() {
|
||||
return (InsnArg) replaceObj;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -29,6 +44,6 @@ public class FieldReplaceAttr implements IAttribute {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "REPLACE: " + fieldInfo;
|
||||
return "REPLACE: " + replaceType + " " + replaceObj;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -23,4 +23,9 @@ public abstract class LineAttrNode extends AttrNode {
|
||||
public void setDecompiledLine(int decompiledLine) {
|
||||
this.decompiledLine = decompiledLine;
|
||||
}
|
||||
|
||||
public void copyLines(LineAttrNode lineAttrNode) {
|
||||
setSourceLine(lineAttrNode.getSourceLine());
|
||||
setDecompiledLine(lineAttrNode.getDecompiledLine());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,9 @@ public class LoopInfo {
|
||||
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;
|
||||
@@ -69,8 +72,24 @@ public class LoopInfo {
|
||||
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: " + start + "->" + end;
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -25,16 +25,15 @@ public class AccessInfo {
|
||||
|
||||
public AccessInfo remove(int flag) {
|
||||
if (containsFlag(flag)) {
|
||||
return new AccessInfo(accFlags - flag, type);
|
||||
} else {
|
||||
return this;
|
||||
return new AccessInfo(accFlags & ~flag, type);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public AccessInfo getVisibility() {
|
||||
int f = (accFlags & AccessFlags.ACC_PUBLIC)
|
||||
| (accFlags & AccessFlags.ACC_PROTECTED)
|
||||
| (accFlags & AccessFlags.ACC_PRIVATE);
|
||||
int f = accFlags & AccessFlags.ACC_PUBLIC
|
||||
| accFlags & AccessFlags.ACC_PROTECTED
|
||||
| accFlags & AccessFlags.ACC_PRIVATE;
|
||||
return new AccessInfo(f, type);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,69 +1,85 @@
|
||||
package jadx.core.dex.info;
|
||||
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.deobf.NameMapper;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Map;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
public final class ClassInfo {
|
||||
|
||||
private static final Map<ArgType, ClassInfo> CLASSINFO_CACHE = new WeakHashMap<ArgType, 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(ArgType type) {
|
||||
assert type.isObject() : "Not class type: " + type;
|
||||
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(true);
|
||||
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;
|
||||
}
|
||||
ArgType type = dex.getType(clsIndex);
|
||||
if (type.isArray()) {
|
||||
type = ArgType.OBJECT;
|
||||
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;
|
||||
}
|
||||
return fromType(type);
|
||||
}
|
||||
|
||||
public static ClassInfo fromName(String clsName) {
|
||||
return fromType(ArgType.object(clsName));
|
||||
public boolean isRenamed() {
|
||||
return alias != this;
|
||||
}
|
||||
|
||||
public static ClassInfo fromType(ArgType type) {
|
||||
ClassInfo cls = CLASSINFO_CACHE.get(type);
|
||||
if (cls == null) {
|
||||
cls = new ClassInfo(type);
|
||||
CLASSINFO_CACHE.put(type, cls);
|
||||
}
|
||||
return cls;
|
||||
public ClassInfo getAlias() {
|
||||
return alias;
|
||||
}
|
||||
|
||||
public static void clearCache() {
|
||||
CLASSINFO_CACHE.clear();
|
||||
}
|
||||
|
||||
private void splitNames(boolean canBeInner) {
|
||||
private void splitNames(DexNode dex, boolean canBeInner) {
|
||||
String fullObjectName = type.getObject();
|
||||
assert fullObjectName.indexOf('/') == -1 : "Raw type: " + type;
|
||||
|
||||
String clsName;
|
||||
int dot = fullObjectName.lastIndexOf('.');
|
||||
if (dot == -1) {
|
||||
// rename default package if it used from class with package (often for obfuscated apps),
|
||||
pkg = Consts.DEFAULT_PACKAGE_NAME;
|
||||
pkg = "";
|
||||
clsName = fullObjectName;
|
||||
} else {
|
||||
pkg = fullObjectName.substring(0, dot);
|
||||
@@ -73,69 +89,75 @@ public final class ClassInfo {
|
||||
int sep = clsName.lastIndexOf('$');
|
||||
if (canBeInner && sep > 0 && sep != clsName.length() - 1) {
|
||||
String parClsName = pkg + "." + clsName.substring(0, sep);
|
||||
parentClass = fromName(parClsName);
|
||||
parentClass = fromName(dex, parClsName);
|
||||
clsName = clsName.substring(sep + 1);
|
||||
} else {
|
||||
parentClass = null;
|
||||
}
|
||||
|
||||
char firstChar = clsName.charAt(0);
|
||||
if (Character.isDigit(firstChar)) {
|
||||
clsName = Consts.ANONYMOUS_CLASS_PREFIX + clsName;
|
||||
} else if (firstChar == '$') {
|
||||
clsName = "_" + clsName;
|
||||
}
|
||||
if (NameMapper.isReserved(clsName)) {
|
||||
clsName += "_";
|
||||
}
|
||||
this.fullName = (parentClass != null ? parentClass.getFullName() : pkg) + "." + clsName;
|
||||
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() {
|
||||
return pkg.replace('.', File.separatorChar)
|
||||
ClassInfo alias = getAlias();
|
||||
return alias.getPackage().replace('.', File.separatorChar)
|
||||
+ File.separatorChar
|
||||
+ getNameWithoutPackage().replace('.', '_');
|
||||
+ alias.getNameWithoutPackage().replace('.', '_');
|
||||
}
|
||||
|
||||
public String getFullName() {
|
||||
return fullName;
|
||||
}
|
||||
|
||||
public boolean isObject() {
|
||||
return fullName.equals(Consts.CLASS_OBJECT);
|
||||
}
|
||||
|
||||
public String getShortName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getRawName() {
|
||||
return type.getObject();
|
||||
}
|
||||
|
||||
public String getPackage() {
|
||||
return pkg;
|
||||
}
|
||||
|
||||
public boolean isPackageDefault() {
|
||||
return pkg.isEmpty() || pkg.equals(Consts.DEFAULT_PACKAGE_NAME);
|
||||
public boolean isDefaultPackage() {
|
||||
return pkg.isEmpty();
|
||||
}
|
||||
|
||||
public String getRawName() {
|
||||
return type.getObject();
|
||||
}
|
||||
|
||||
public String getNameWithoutPackage() {
|
||||
return (parentClass != null ? parentClass.getNameWithoutPackage() + "." : "") + name;
|
||||
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() {
|
||||
splitNames(false);
|
||||
public void notInner(DexNode dex) {
|
||||
splitNames(dex, false);
|
||||
}
|
||||
|
||||
public ArgType getType() {
|
||||
@@ -149,7 +171,7 @@ public final class ClassInfo {
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return fullName.hashCode();
|
||||
return type.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -159,7 +181,7 @@ public final class ClassInfo {
|
||||
}
|
||||
if (obj instanceof ClassInfo) {
|
||||
ClassInfo other = (ClassInfo) obj;
|
||||
return this.getFullName().equals(other.getFullName());
|
||||
return this.type.equals(other.type);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
package jadx.core.dex.info;
|
||||
|
||||
import jadx.api.IJadxArgs;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.instructions.args.LiteralArg;
|
||||
import jadx.core.dex.instructions.args.PrimitiveType;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.ResRefField;
|
||||
import jadx.core.dex.nodes.parser.FieldInitAttr;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class ConstStorage {
|
||||
|
||||
private static final class Values {
|
||||
private final Map<Object, FieldNode> values = new HashMap<Object, FieldNode>();
|
||||
private final Set<Object> duplicates = new HashSet<Object>();
|
||||
|
||||
public Map<Object, FieldNode> getValues() {
|
||||
return values;
|
||||
}
|
||||
|
||||
public FieldNode get(Object key) {
|
||||
return values.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if this value is duplicated
|
||||
*/
|
||||
public boolean put(Object value, FieldNode fld) {
|
||||
FieldNode prev = values.put(value, fld);
|
||||
if (prev != null) {
|
||||
values.remove(value);
|
||||
duplicates.add(value);
|
||||
return true;
|
||||
}
|
||||
if (duplicates.contains(value)) {
|
||||
values.remove(value);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean contains(Object value) {
|
||||
return duplicates.contains(value) || values.containsKey(value);
|
||||
}
|
||||
}
|
||||
|
||||
private final boolean replaceEnabled;
|
||||
private final Values globalValues = new Values();
|
||||
private final Map<ClassNode, Values> classes = new HashMap<ClassNode, Values>();
|
||||
|
||||
private Map<Integer, String> resourcesNames = new HashMap<Integer, String>();
|
||||
|
||||
public ConstStorage(IJadxArgs args) {
|
||||
this.replaceEnabled = args.isReplaceConsts();
|
||||
}
|
||||
|
||||
public void processConstFields(ClassNode cls, List<FieldNode> staticFields) {
|
||||
if (!replaceEnabled || staticFields.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (FieldNode f : staticFields) {
|
||||
AccessInfo accFlags = f.getAccessFlags();
|
||||
if (accFlags.isStatic() && accFlags.isFinal()) {
|
||||
FieldInitAttr fv = f.get(AType.FIELD_INIT);
|
||||
if (fv != null
|
||||
&& fv.getValue() != null
|
||||
&& fv.getValueType() == FieldInitAttr.InitType.CONST
|
||||
&& fv != FieldInitAttr.NULL_VALUE) {
|
||||
addConstField(cls, f, fv.getValue(), accFlags.isPublic());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addConstField(ClassNode cls, FieldNode fld, Object value, boolean isPublic) {
|
||||
if (isPublic) {
|
||||
globalValues.put(value, fld);
|
||||
} else {
|
||||
getClsValues(cls).put(value, fld);
|
||||
}
|
||||
}
|
||||
|
||||
private Values getClsValues(ClassNode cls) {
|
||||
Values classValues = classes.get(cls);
|
||||
if (classValues == null) {
|
||||
classValues = new Values();
|
||||
classes.put(cls, classValues);
|
||||
}
|
||||
return classValues;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public FieldNode getConstField(ClassNode cls, Object value, boolean searchGlobal) {
|
||||
DexNode dex = cls.dex();
|
||||
if (value instanceof Integer) {
|
||||
String str = resourcesNames.get(value);
|
||||
if (str != null) {
|
||||
return new ResRefField(dex, str.replace('/', '.'));
|
||||
}
|
||||
}
|
||||
if (!replaceEnabled) {
|
||||
return null;
|
||||
}
|
||||
boolean foundInGlobal = globalValues.contains(value);
|
||||
if (foundInGlobal && !searchGlobal) {
|
||||
return null;
|
||||
}
|
||||
ClassNode current = cls;
|
||||
while (current != null) {
|
||||
Values classValues = classes.get(current);
|
||||
if (classValues != null) {
|
||||
FieldNode field = classValues.get(value);
|
||||
if (field != null) {
|
||||
if (foundInGlobal) {
|
||||
return null;
|
||||
}
|
||||
return field;
|
||||
}
|
||||
}
|
||||
ClassInfo parentClass = current.getClassInfo().getParentClass();
|
||||
if (parentClass == null) {
|
||||
break;
|
||||
}
|
||||
current = dex.resolveClass(parentClass);
|
||||
}
|
||||
if (searchGlobal) {
|
||||
return globalValues.get(value);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public FieldNode getConstFieldByLiteralArg(ClassNode cls, LiteralArg arg) {
|
||||
PrimitiveType type = arg.getType().getPrimitiveType();
|
||||
if (type == null) {
|
||||
return null;
|
||||
}
|
||||
long literal = arg.getLiteral();
|
||||
switch (type) {
|
||||
case BOOLEAN:
|
||||
return getConstField(cls, literal == 1, false);
|
||||
case CHAR:
|
||||
return getConstField(cls, (char) literal, Math.abs(literal) > 10);
|
||||
case BYTE:
|
||||
return getConstField(cls, (byte) literal, Math.abs(literal) > 10);
|
||||
case SHORT:
|
||||
return getConstField(cls, (short) literal, Math.abs(literal) > 100);
|
||||
case INT:
|
||||
return getConstField(cls, (int) literal, Math.abs(literal) > 100);
|
||||
case LONG:
|
||||
return getConstField(cls, literal, Math.abs(literal) > 1000);
|
||||
case FLOAT:
|
||||
float f = Float.intBitsToFloat((int) literal);
|
||||
return getConstField(cls, f, f != 0.0);
|
||||
case DOUBLE:
|
||||
double d = Double.longBitsToDouble(literal);
|
||||
return getConstField(cls, d, d != 0);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setResourcesNames(Map<Integer, String> resourcesNames) {
|
||||
this.resourcesNames = resourcesNames;
|
||||
}
|
||||
|
||||
public Map<Integer, String> getResourcesNames() {
|
||||
return resourcesNames;
|
||||
}
|
||||
|
||||
public Map<Object, FieldNode> getGlobalConstFields() {
|
||||
return globalValues.getValues();
|
||||
}
|
||||
|
||||
public boolean isReplaceEnabled() {
|
||||
return replaceEnabled;
|
||||
}
|
||||
}
|
||||
@@ -1,34 +1,38 @@
|
||||
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 class FieldInfo {
|
||||
public final class FieldInfo {
|
||||
|
||||
private final ClassInfo declClass;
|
||||
private final String name;
|
||||
private final ArgType type;
|
||||
private String alias;
|
||||
|
||||
public FieldInfo(ClassInfo declClass, String name, ArgType type) {
|
||||
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 new FieldInfo(
|
||||
return from(dex,
|
||||
ClassInfo.fromDex(dex, field.getDeclaringClassIndex()),
|
||||
dex.getString(field.getNameIndex()),
|
||||
dex.getType(field.getTypeIndex()));
|
||||
}
|
||||
|
||||
public static String getNameById(DexNode dex, int ind) {
|
||||
return dex.getString(dex.getFieldId(ind).getNameIndex());
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
@@ -41,6 +45,22 @@ public class FieldInfo {
|
||||
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) {
|
||||
@@ -50,16 +70,9 @@ public class FieldInfo {
|
||||
return false;
|
||||
}
|
||||
FieldInfo fieldInfo = (FieldInfo) o;
|
||||
if (!name.equals(fieldInfo.name)) {
|
||||
return false;
|
||||
}
|
||||
if (!type.equals(fieldInfo.type)) {
|
||||
return false;
|
||||
}
|
||||
if (!declClass.equals(fieldInfo.declClass)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
return name.equals(fieldInfo.name)
|
||||
&& type.equals(fieldInfo.type)
|
||||
&& declClass.equals(fieldInfo.declClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,16 +17,32 @@ public final class MethodInfo {
|
||||
private final List<ArgType> args;
|
||||
private final ClassInfo declClass;
|
||||
private final String shortId;
|
||||
private String alias;
|
||||
private boolean aliasFromPreset;
|
||||
|
||||
private MethodInfo(DexNode dex, int mthIndex) {
|
||||
MethodId mthId = dex.getMethodId(mthIndex);
|
||||
name = dex.getString(mthId.getNameIndex());
|
||||
alias = name;
|
||||
aliasFromPreset = false;
|
||||
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('(');
|
||||
@@ -34,13 +50,10 @@ public final class MethodInfo {
|
||||
signature.append(TypeGen.signature(arg));
|
||||
}
|
||||
signature.append(')');
|
||||
signature.append(TypeGen.signature(retType));
|
||||
|
||||
shortId = signature.toString();
|
||||
}
|
||||
|
||||
public static MethodInfo fromDex(DexNode dex, int mthIndex) {
|
||||
return new MethodInfo(dex, mthIndex);
|
||||
if (includeRetType) {
|
||||
signature.append(TypeGen.signature(retType));
|
||||
}
|
||||
return signature.toString();
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
@@ -86,6 +99,26 @@ public final class MethodInfo {
|
||||
return name.equals("<clinit>");
|
||||
}
|
||||
|
||||
public String getAlias() {
|
||||
return alias;
|
||||
}
|
||||
|
||||
public void setAlias(String alias) {
|
||||
this.alias = alias;
|
||||
}
|
||||
|
||||
public boolean isRenamed() {
|
||||
return !name.equals(alias);
|
||||
}
|
||||
|
||||
public boolean isAliasFromPreset() {
|
||||
return aliasFromPreset;
|
||||
}
|
||||
|
||||
public void setAliasFromPreset(boolean value) {
|
||||
aliasFromPreset = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = declClass.hashCode();
|
||||
@@ -99,23 +132,13 @@ public final class MethodInfo {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
if (!(obj instanceof MethodInfo)) {
|
||||
return false;
|
||||
}
|
||||
MethodInfo other = (MethodInfo) obj;
|
||||
if (!shortId.equals(other.shortId)) {
|
||||
return false;
|
||||
}
|
||||
if (!retType.equals(other.retType)) {
|
||||
return false;
|
||||
}
|
||||
if (!declClass.equals(other.declClass)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
return shortId.equals(other.shortId)
|
||||
&& retType.equals(other.retType)
|
||||
&& declClass.equals(other.declClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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;
|
||||
@@ -39,7 +40,6 @@ public class ArithNode extends InsnNode {
|
||||
addReg(insn, 2, type);
|
||||
}
|
||||
}
|
||||
assert getArgsCount() == 2;
|
||||
}
|
||||
|
||||
public ArithNode(ArithOp op, RegisterArg res, InsnArg a, InsnArg b) {
|
||||
@@ -51,10 +51,8 @@ public class ArithNode extends InsnNode {
|
||||
}
|
||||
|
||||
public ArithNode(ArithOp op, RegisterArg res, InsnArg a) {
|
||||
super(InsnType.ARITH_ONEARG, 1);
|
||||
this.op = op;
|
||||
setResult(res);
|
||||
addArg(a);
|
||||
this(op, res, res, a);
|
||||
add(AFlag.ARITH_ONEARG);
|
||||
}
|
||||
|
||||
public ArithOp getOp() {
|
||||
@@ -62,20 +60,15 @@ public class ArithNode extends InsnNode {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
public boolean isSame(InsnNode obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof ArithNode) || !super.equals(obj)) {
|
||||
if (!(obj instanceof ArithNode) || !super.isSame(obj)) {
|
||||
return false;
|
||||
}
|
||||
ArithNode that = (ArithNode) obj;
|
||||
return op == that.op;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 31 * super.hashCode() + op.hashCode();
|
||||
ArithNode other = (ArithNode) obj;
|
||||
return op == other.op;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -85,7 +78,7 @@ public class ArithNode extends InsnNode {
|
||||
+ getResult() + " = "
|
||||
+ getArg(0) + " "
|
||||
+ op.getSymbol() + " "
|
||||
+ (getArgsCount() == 2 ? getArg(1) : "");
|
||||
+ getArg(1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,20 +17,20 @@ public final class ConstClassNode extends InsnNode {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof ConstClassNode) || !super.equals(obj)) {
|
||||
return false;
|
||||
}
|
||||
ConstClassNode that = (ConstClassNode) obj;
|
||||
return clsType.equals(that.clsType);
|
||||
public InsnNode copy() {
|
||||
return copyCommonParams(new ConstClassNode(clsType));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 31 * super.hashCode() + clsType.hashCode();
|
||||
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
|
||||
|
||||
@@ -16,20 +16,20 @@ public final class ConstStringNode extends InsnNode {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof ConstStringNode) || !super.equals(obj)) {
|
||||
return false;
|
||||
}
|
||||
ConstStringNode that = (ConstStringNode) obj;
|
||||
return str.equals(that.str);
|
||||
public InsnNode copy() {
|
||||
return copyCommonParams(new ConstStringNode(str));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 31 * super.hashCode() + str.hashCode();
|
||||
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
|
||||
|
||||
@@ -2,15 +2,21 @@ 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) {
|
||||
@@ -36,6 +42,7 @@ public final class FillArrayNode extends InsnNode {
|
||||
setResult(InsnArg.reg(resReg, ArgType.array(elType)));
|
||||
|
||||
this.data = payload.getData();
|
||||
this.size = payload.getSize();
|
||||
this.elemType = elType;
|
||||
}
|
||||
|
||||
@@ -43,31 +50,55 @@ public final class FillArrayNode extends InsnNode {
|
||||
return data;
|
||||
}
|
||||
|
||||
public int getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public ArgType getElementType() {
|
||||
return elemType;
|
||||
}
|
||||
|
||||
public void mergeElementType(ArgType foundElemType) {
|
||||
ArgType r = ArgType.merge(elemType, foundElemType);
|
||||
public void mergeElementType(DexNode dex, ArgType foundElemType) {
|
||||
ArgType r = ArgType.merge(dex, elemType, foundElemType);
|
||||
if (r != null) {
|
||||
elemType = r;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
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);
|
||||
}
|
||||
if (!(obj instanceof FillArrayNode) || !super.equals(obj)) {
|
||||
return false;
|
||||
}
|
||||
FillArrayNode that = (FillArrayNode) obj;
|
||||
return elemType.equals(that.elemType) && data == that.data;
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 31 * super.hashCode() + elemType.hashCode() + data.hashCode();
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -20,23 +20,6 @@ public class GotoNode extends InsnNode {
|
||||
return target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof GotoNode) || !super.equals(obj)) {
|
||||
return false;
|
||||
}
|
||||
GotoNode gotoNode = (GotoNode) obj;
|
||||
return target == gotoNode.target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 31 * super.hashCode() + target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + "-> " + InsnUtils.formatOffset(target);
|
||||
|
||||
@@ -4,6 +4,7 @@ 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;
|
||||
@@ -13,9 +14,11 @@ import static jadx.core.utils.BlockUtils.selectOther;
|
||||
|
||||
public class IfNode extends GotoNode {
|
||||
|
||||
// change default types priority
|
||||
private static final ArgType ARG_TYPE = ArgType.unknown(
|
||||
PrimitiveType.INT, PrimitiveType.OBJECT, PrimitiveType.ARRAY,
|
||||
PrimitiveType.BOOLEAN, PrimitiveType.SHORT, PrimitiveType.CHAR);
|
||||
PrimitiveType.INT,
|
||||
PrimitiveType.OBJECT, PrimitiveType.ARRAY,
|
||||
PrimitiveType.BOOLEAN, PrimitiveType.BYTE, PrimitiveType.SHORT, PrimitiveType.CHAR);
|
||||
|
||||
protected IfOp op;
|
||||
|
||||
@@ -71,20 +74,15 @@ public class IfNode extends GotoNode {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
public boolean isSame(InsnNode obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof IfNode) || !super.equals(obj)) {
|
||||
if (!(obj instanceof IfNode) || !super.isSame(obj)) {
|
||||
return false;
|
||||
}
|
||||
IfNode ifNode = (IfNode) obj;
|
||||
return op == ifNode.op;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 31 * super.hashCode() + op.hashCode();
|
||||
IfNode other = (IfNode) obj;
|
||||
return op == other.op;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -17,20 +17,20 @@ public class IndexInsnNode extends InsnNode {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof IndexInsnNode) || !super.equals(obj)) {
|
||||
return false;
|
||||
}
|
||||
IndexInsnNode that = (IndexInsnNode) obj;
|
||||
return index == null ? that.index == null : index.equals(that.index);
|
||||
public IndexInsnNode copy() {
|
||||
return copyCommonParams(new IndexInsnNode(insnType, index, getArgsCount()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 31 * super.hashCode() + (index != null ? index.hashCode() : 0);
|
||||
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
|
||||
|
||||
@@ -4,16 +4,14 @@ import jadx.core.dex.info.FieldInfo;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.NamedArg;
|
||||
import jadx.core.dex.instructions.args.PrimitiveType;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.utils.InsnUtils;
|
||||
import jadx.core.utils.exceptions.DecodeException;
|
||||
|
||||
import java.io.EOFException;
|
||||
|
||||
import com.android.dex.Code;
|
||||
import com.android.dx.io.OpcodeInfo;
|
||||
import com.android.dx.io.Opcodes;
|
||||
@@ -44,7 +42,7 @@ public class InsnDecoder {
|
||||
while (in.hasMore()) {
|
||||
decoded[in.cursor()] = DecodedInstruction.decode(in);
|
||||
}
|
||||
} catch (EOFException e) {
|
||||
} catch (Exception e) {
|
||||
throw new DecodeException(method, "", e);
|
||||
}
|
||||
insnArr = decoded;
|
||||
@@ -58,7 +56,6 @@ public class InsnDecoder {
|
||||
InsnNode insn = decode(rawInsn, i);
|
||||
if (insn != null) {
|
||||
insn.setOffset(i);
|
||||
insn.setInsnHashCode(calcHashCode(rawInsn));
|
||||
}
|
||||
instructions[i] = insn;
|
||||
} else {
|
||||
@@ -69,21 +66,6 @@ public class InsnDecoder {
|
||||
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 {
|
||||
switch (insn.getOpcode()) {
|
||||
case Opcodes.NOP:
|
||||
@@ -406,8 +388,7 @@ public class InsnDecoder {
|
||||
|
||||
case Opcodes.MOVE_EXCEPTION:
|
||||
return insn(InsnType.MOVE_EXCEPTION,
|
||||
InsnArg.reg(insn, 0, ArgType.unknown(PrimitiveType.OBJECT)),
|
||||
new NamedArg("e", ArgType.unknown(PrimitiveType.OBJECT)));
|
||||
InsnArg.reg(insn, 0, ArgType.unknown(PrimitiveType.OBJECT)));
|
||||
|
||||
case Opcodes.RETURN_VOID:
|
||||
return new InsnNode(InsnType.RETURN, 0);
|
||||
@@ -554,8 +535,9 @@ public class InsnDecoder {
|
||||
InsnArg.reg(insn, 0, dex.getType(insn.getIndex())));
|
||||
|
||||
case Opcodes.NEW_ARRAY:
|
||||
return insn(InsnType.NEW_ARRAY,
|
||||
InsnArg.reg(insn, 0, dex.getType(insn.getIndex())),
|
||||
ArgType arrType = dex.getType(insn.getIndex());
|
||||
return new NewArrayNode(arrType,
|
||||
InsnArg.reg(insn, 0, arrType),
|
||||
InsnArg.reg(insn, 1, ArgType.INT));
|
||||
|
||||
case Opcodes.FILL_ARRAY_DATA:
|
||||
@@ -624,21 +606,27 @@ public class InsnDecoder {
|
||||
int resReg = getMoveResultRegister(insnArr, offset);
|
||||
ArgType arrType = dex.getType(insn.getIndex());
|
||||
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) {
|
||||
int r = insn.getA();
|
||||
for (int i = 0; i < insn.getRegisterCount(); i++) {
|
||||
regs[i] = InsnArg.reg(r, elType);
|
||||
for (int i = 0; i < regsCount; i++) {
|
||||
regs[i] = InsnArg.reg(r, elType, typeImmutable);
|
||||
r++;
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < insn.getRegisterCount(); i++) {
|
||||
regs[i] = InsnArg.reg(insn, i, elType);
|
||||
for (int i = 0; i < regsCount; i++) {
|
||||
int regNum = InsnUtils.getArg(insn, i);
|
||||
regs[i] = InsnArg.reg(regNum, elType, typeImmutable);
|
||||
}
|
||||
}
|
||||
return insn(InsnType.FILLED_NEW_ARRAY,
|
||||
resReg == -1 ? null : InsnArg.reg(resReg, arrType),
|
||||
regs);
|
||||
InsnNode node = new FilledNewArrayNode(elType, regs.length);
|
||||
node.setResult(resReg == -1 ? null : InsnArg.reg(resReg, arrType));
|
||||
for (InsnArg arg : regs) {
|
||||
node.addArg(arg);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
private InsnNode cmp(DecodedInstruction insn, InsnType itype, ArgType argType) {
|
||||
@@ -706,17 +694,6 @@ public class InsnDecoder {
|
||||
return node;
|
||||
}
|
||||
|
||||
private InsnNode insn(InsnType type, RegisterArg res, InsnArg... args) {
|
||||
InsnNode node = new InsnNode(type, args == null ? 0 : args.length);
|
||||
node.setResult(res);
|
||||
if (args != null) {
|
||||
for (InsnArg arg : args) {
|
||||
node.addArg(arg);
|
||||
}
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
private int getMoveResultRegister(DecodedInstruction[] insnArr, int offset) {
|
||||
int nextOffset = getNextInsnOffset(insnArr, offset);
|
||||
if (nextOffset >= 0) {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package jadx.core.dex.instructions;
|
||||
|
||||
public enum InsnType {
|
||||
NOP, // replacement for removed instructions
|
||||
|
||||
CONST,
|
||||
CONST_STR,
|
||||
@@ -48,17 +47,27 @@ public enum InsnType {
|
||||
|
||||
INVOKE,
|
||||
|
||||
// additional instructions
|
||||
// *** Additional instructions ***
|
||||
|
||||
// replacement for removed instructions
|
||||
NOP,
|
||||
|
||||
TERNARY,
|
||||
CONSTRUCTOR,
|
||||
|
||||
BREAK,
|
||||
CONTINUE,
|
||||
|
||||
STR_CONCAT, // strings concatenation
|
||||
ARITH_ONEARG,
|
||||
// strings concatenation
|
||||
STR_CONCAT,
|
||||
|
||||
TERNARY,
|
||||
ARGS, // just generate arguments
|
||||
// just generate one argument
|
||||
ONE_ARG,
|
||||
PHI,
|
||||
|
||||
NEW_MULTIDIM_ARRAY // TODO: now multidimensional arrays created using Array.newInstance function
|
||||
// merge all arguments in one
|
||||
MERGE,
|
||||
|
||||
// TODO: now multidimensional arrays created using Array.newInstance function
|
||||
NEW_MULTIDIM_ARRAY
|
||||
}
|
||||
|
||||
@@ -36,6 +36,12 @@ public class InvokeNode extends InsnNode {
|
||||
}
|
||||
}
|
||||
|
||||
private InvokeNode(MethodInfo mth, InvokeType invokeType, int argsCount) {
|
||||
super(InsnType.INVOKE, argsCount);
|
||||
this.mth = mth;
|
||||
this.type = invokeType;
|
||||
}
|
||||
|
||||
public InvokeType getInvokeType() {
|
||||
return type;
|
||||
}
|
||||
@@ -45,23 +51,20 @@ public class InvokeNode extends InsnNode {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof InvokeNode) || !super.equals(obj)) {
|
||||
return false;
|
||||
}
|
||||
InvokeNode that = (InvokeNode) obj;
|
||||
return type == that.type && mth.equals(that.mth);
|
||||
public InsnNode copy() {
|
||||
return copyCommonParams(new InvokeNode(mth, type, getArgsCount()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = super.hashCode();
|
||||
result = 31 * result + type.hashCode();
|
||||
result = 31 * result + mth.hashCode();
|
||||
return result;
|
||||
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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,28 +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;
|
||||
|
||||
public class PhiInsn extends InsnNode {
|
||||
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));
|
||||
for (int i = 0; i < predecessors; i++) {
|
||||
addReg(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());
|
||||
return "PHI: " + getResult() + " = " + Utils.listToString(getArguments())
|
||||
+ " binds: " + blockBinds;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,26 +37,17 @@ public class SwitchNode extends InsnNode {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
public boolean isSame(InsnNode obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof SwitchNode) || !super.equals(obj)) {
|
||||
if (!(obj instanceof SwitchNode) || !super.isSame(obj)) {
|
||||
return false;
|
||||
}
|
||||
SwitchNode that = (SwitchNode) obj;
|
||||
return def == that.def
|
||||
&& Arrays.equals(keys, that.keys)
|
||||
&& Arrays.equals(targets, that.targets);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = super.hashCode();
|
||||
result = 31 * result + Arrays.hashCode(keys);
|
||||
result = 31 * result + Arrays.hashCode(targets);
|
||||
result = 31 * result + def;
|
||||
return result;
|
||||
SwitchNode other = (SwitchNode) obj;
|
||||
return def == other.def
|
||||
&& Arrays.equals(keys, other.keys)
|
||||
&& Arrays.equals(targets, other.targets);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package jadx.core.dex.instructions.args;
|
||||
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.clsp.ClspGraph;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.dex.nodes.parser.SignatureParser;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
@@ -9,6 +9,8 @@ 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);
|
||||
@@ -24,6 +26,7 @@ public abstract class ArgType {
|
||||
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());
|
||||
@@ -42,16 +45,6 @@ public abstract class ArgType {
|
||||
|
||||
protected int hash;
|
||||
|
||||
private static ClspGraph clsp;
|
||||
|
||||
public static void setClsp(ClspGraph clsp) {
|
||||
ArgType.clsp = clsp;
|
||||
}
|
||||
|
||||
public static boolean isClspSet() {
|
||||
return ArgType.clsp != null;
|
||||
}
|
||||
|
||||
private static ArgType primitive(PrimitiveType stype) {
|
||||
return new PrimitiveArg(stype);
|
||||
}
|
||||
@@ -93,10 +86,28 @@ public abstract class ArgType {
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -269,6 +280,7 @@ public abstract class ArgType {
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -291,6 +303,21 @@ public abstract class ArgType {
|
||||
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();
|
||||
@@ -343,8 +370,10 @@ public abstract class ArgType {
|
||||
@Override
|
||||
public ArgType selectFirst() {
|
||||
PrimitiveType f = possibleTypes[0];
|
||||
if (f == PrimitiveType.OBJECT || f == PrimitiveType.ARRAY) {
|
||||
return object(Consts.CLASS_OBJECT);
|
||||
if (contains(PrimitiveType.OBJECT)) {
|
||||
return OBJECT;
|
||||
} else if (contains(PrimitiveType.ARRAY)) {
|
||||
return array(OBJECT);
|
||||
} else {
|
||||
return primitive(f);
|
||||
}
|
||||
@@ -428,43 +457,42 @@ public abstract class ArgType {
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean contains(PrimitiveType type) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
public abstract boolean contains(PrimitiveType type);
|
||||
|
||||
public ArgType selectFirst() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
public abstract ArgType selectFirst();
|
||||
|
||||
public PrimitiveType[] getPossibleTypes() {
|
||||
return null;
|
||||
}
|
||||
public abstract PrimitiveType[] getPossibleTypes();
|
||||
|
||||
public static ArgType merge(ArgType a, ArgType b) {
|
||||
@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(a, b);
|
||||
ArgType res = mergeInternal(dex, a, b);
|
||||
if (res == null) {
|
||||
res = mergeInternal(b, a); // swap
|
||||
res = mergeInternal(dex, b, a); // swap
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private static ArgType mergeInternal(ArgType a, ArgType b) {
|
||||
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;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
} else {
|
||||
// both types unknown
|
||||
List<PrimitiveType> types = new ArrayList<PrimitiveType>();
|
||||
@@ -475,7 +503,8 @@ public abstract class ArgType {
|
||||
}
|
||||
if (types.isEmpty()) {
|
||||
return null;
|
||||
} else if (types.size() == 1) {
|
||||
}
|
||||
if (types.size() == 1) {
|
||||
PrimitiveType nt = types.get(0);
|
||||
if (nt == PrimitiveType.OBJECT || nt == PrimitiveType.ARRAY) {
|
||||
return unknown(nt);
|
||||
@@ -499,31 +528,18 @@ public abstract class ArgType {
|
||||
String bObj = b.getObject();
|
||||
if (aObj.equals(bObj)) {
|
||||
return a.getGenericTypes() != null ? a : b;
|
||||
} else if (aObj.equals(Consts.CLASS_OBJECT)) {
|
||||
return b;
|
||||
} else if (bObj.equals(Consts.CLASS_OBJECT)) {
|
||||
return a;
|
||||
} else {
|
||||
// different objects
|
||||
String obj = clsp.getCommonAncestor(aObj, bObj);
|
||||
return obj == null ? null : object(obj);
|
||||
}
|
||||
}
|
||||
if (a.isArray()) {
|
||||
if (b.isArray()) {
|
||||
ArgType ea = a.getArrayElement();
|
||||
ArgType eb = b.getArrayElement();
|
||||
if (ea.isPrimitive() && eb.isPrimitive()) {
|
||||
return OBJECT;
|
||||
} else {
|
||||
ArgType res = merge(ea, eb);
|
||||
return res == null ? null : array(res);
|
||||
}
|
||||
} else if (b.equals(OBJECT)) {
|
||||
return OBJECT;
|
||||
} else {
|
||||
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()));
|
||||
@@ -532,17 +548,46 @@ public abstract class ArgType {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static boolean isCastNeeded(ArgType from, ArgType to) {
|
||||
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()
|
||||
&& clsp.isImplements(from.getObject(), to.getObject())) {
|
||||
&& 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) {
|
||||
|
||||
@@ -2,14 +2,17 @@ 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, InsnArg reg) {
|
||||
public FieldArg(FieldInfo field, @Nullable InsnArg reg) {
|
||||
super(-1);
|
||||
this.instArg = reg;
|
||||
this.field = field;
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
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;
|
||||
|
||||
@@ -16,6 +21,7 @@ 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) {
|
||||
@@ -26,8 +32,12 @@ public abstract class InsnArg extends Typed {
|
||||
return reg(InsnUtils.getArg(insn, argNum), type);
|
||||
}
|
||||
|
||||
public static MthParameterArg parameterReg(int regNum, ArgType type) {
|
||||
return new MthParameterArg(regNum, 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) {
|
||||
@@ -62,11 +72,12 @@ public abstract class InsnArg extends Typed {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public InsnNode getParentInsn() {
|
||||
return parentInsn;
|
||||
}
|
||||
|
||||
public void setParentInsn(InsnNode parentInsn) {
|
||||
public void setParentInsn(@Nullable InsnNode parentInsn) {
|
||||
this.parentInsn = parentInsn;
|
||||
}
|
||||
|
||||
@@ -76,18 +87,35 @@ public abstract class InsnArg extends Typed {
|
||||
return null;
|
||||
}
|
||||
if (parent == insn) {
|
||||
LOG.debug("Can't wrap instruction info itself: " + 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) == this) {
|
||||
InsnArg arg = wrapArg(insn);
|
||||
parent.setArg(i, arg);
|
||||
return arg;
|
||||
if (parent.getArg(i) == arg) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static InsnArg wrapArg(InsnNode insn) {
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
package jadx.core.dex.instructions.args;
|
||||
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public final class InsnWrapArg extends InsnArg {
|
||||
|
||||
private final InsnNode wrappedInsn;
|
||||
|
||||
public InsnWrapArg(InsnNode insn) {
|
||||
public InsnWrapArg(@NotNull InsnNode insn) {
|
||||
RegisterArg result = insn.getResult();
|
||||
this.type = result != null ? result.getType() : ArgType.VOID;
|
||||
this.wrappedInsn = insn;
|
||||
@@ -18,7 +21,9 @@ public final class InsnWrapArg extends InsnArg {
|
||||
|
||||
@Override
|
||||
public void setParentInsn(InsnNode parentInsn) {
|
||||
assert parentInsn != wrappedInsn : "Can't wrap instruction info itself: " + parentInsn;
|
||||
if (parentInsn == wrappedInsn) {
|
||||
throw new JadxRuntimeException("Can't wrap instruction info itself: " + parentInsn);
|
||||
}
|
||||
this.parentInsn = parentInsn;
|
||||
}
|
||||
|
||||
@@ -27,6 +32,34 @@ public final class InsnWrapArg extends InsnArg {
|
||||
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 + ")";
|
||||
|
||||
@@ -17,7 +17,7 @@ public final class LiteralArg extends InsnArg {
|
||||
} else if (!type.isTypeKnown()
|
||||
&& !type.contains(PrimitiveType.LONG)
|
||||
&& !type.contains(PrimitiveType.DOUBLE)) {
|
||||
ArgType m = ArgType.merge(type, ArgType.NARROW_NUMBERS);
|
||||
ArgType m = ArgType.merge(null, type, ArgType.NARROW_NUMBERS);
|
||||
if (m != null) {
|
||||
type = m;
|
||||
}
|
||||
@@ -38,16 +38,16 @@ public final class LiteralArg extends InsnArg {
|
||||
|
||||
public boolean isInteger() {
|
||||
PrimitiveType type = this.type.getPrimitiveType();
|
||||
return (type == PrimitiveType.INT
|
||||
return type == PrimitiveType.INT
|
||||
|| type == PrimitiveType.BYTE
|
||||
|| type == PrimitiveType.CHAR
|
||||
|| type == PrimitiveType.SHORT
|
||||
|| type == PrimitiveType.LONG);
|
||||
|| type == PrimitiveType.LONG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return (int) (literal ^ (literal >>> 32)) + 31 * getType().hashCode();
|
||||
return (int) (literal ^ literal >>> 32) + 31 * getType().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
package jadx.core.dex.instructions.args;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public final class NamedArg extends InsnArg implements Named {
|
||||
|
||||
@NotNull
|
||||
private String name;
|
||||
|
||||
public NamedArg(String name, ArgType type) {
|
||||
public NamedArg(@NotNull String name, @NotNull ArgType type) {
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
@@ -18,10 +22,27 @@ public final class NamedArg extends InsnArg implements Named {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
public void setName(@NotNull String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof NamedArg)) {
|
||||
return false;
|
||||
}
|
||||
return name.equals(((NamedArg) o).name);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return name.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "(" + name + " " + type + ")";
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
package jadx.core.dex.instructions.args;
|
||||
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
import jadx.core.dex.instructions.ConstClassNode;
|
||||
import jadx.core.dex.instructions.ConstStringNode;
|
||||
import jadx.core.dex.instructions.IndexInsnNode;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.PhiInsn;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.parser.FieldValueAttr;
|
||||
import jadx.core.utils.InsnUtils;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -19,7 +14,8 @@ public class RegisterArg extends InsnArg implements Named {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(RegisterArg.class);
|
||||
|
||||
protected final int regNum;
|
||||
protected SSAVar sVar;
|
||||
// not null after SSATransform pass
|
||||
private SSAVar sVar;
|
||||
|
||||
public RegisterArg(int rn) {
|
||||
this.regNum = rn;
|
||||
@@ -43,7 +39,7 @@ public class RegisterArg extends InsnArg implements Named {
|
||||
return sVar;
|
||||
}
|
||||
|
||||
void setSVar(SSAVar sVar) {
|
||||
void setSVar(@NotNull SSAVar sVar) {
|
||||
this.sVar = sVar;
|
||||
}
|
||||
|
||||
@@ -80,8 +76,17 @@ public class RegisterArg extends InsnArg implements Named {
|
||||
setName(name);
|
||||
}
|
||||
|
||||
public void forceType(ArgType type) {
|
||||
this.type = type;
|
||||
public RegisterArg duplicate() {
|
||||
return duplicate(getRegNum(), sVar);
|
||||
}
|
||||
|
||||
public RegisterArg duplicate(int regNum, SSAVar sVar) {
|
||||
RegisterArg dup = new RegisterArg(regNum, getType());
|
||||
if (sVar != null) {
|
||||
dup.setSVar(sVar);
|
||||
}
|
||||
dup.copyAttributesFrom(this);
|
||||
return dup;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -94,28 +99,7 @@ public class RegisterArg extends InsnArg implements Named {
|
||||
if (parInsn == null) {
|
||||
return null;
|
||||
}
|
||||
InsnType insnType = parInsn.getType();
|
||||
switch (insnType) {
|
||||
case CONST:
|
||||
return parInsn.getArg(0);
|
||||
case CONST_STR:
|
||||
return ((ConstStringNode) parInsn).getString();
|
||||
case CONST_CLASS:
|
||||
return ((ConstClassNode) parInsn).getClsType();
|
||||
case SGET:
|
||||
FieldInfo f = (FieldInfo) ((IndexInsnNode) parInsn).getIndex();
|
||||
FieldNode fieldNode = dex.resolveField(f);
|
||||
if (fieldNode != null) {
|
||||
FieldValueAttr attr = fieldNode.get(AType.FIELD_VALUE);
|
||||
if (attr != null) {
|
||||
return attr.getValue();
|
||||
}
|
||||
} else {
|
||||
LOG.warn("Field {} not found in dex {}", f, dex);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return null;
|
||||
return InsnUtils.getConstValueByInsn(dex, parInsn);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -138,11 +122,7 @@ public class RegisterArg extends InsnArg implements Named {
|
||||
if (sVar == null) {
|
||||
return null;
|
||||
}
|
||||
RegisterArg assign = sVar.getAssign();
|
||||
if (assign != null) {
|
||||
return assign.getParentInsn();
|
||||
}
|
||||
return null;
|
||||
return sVar.getAssign().getParentInsn();
|
||||
}
|
||||
|
||||
public InsnNode getPhiAssignInsn() {
|
||||
@@ -150,12 +130,9 @@ public class RegisterArg extends InsnArg implements Named {
|
||||
if (usePhi != null) {
|
||||
return usePhi;
|
||||
}
|
||||
RegisterArg assign = sVar.getAssign();
|
||||
if (assign != null) {
|
||||
InsnNode parent = assign.getParentInsn();
|
||||
if (parent != null && parent.getType() == InsnType.PHI) {
|
||||
return parent;
|
||||
}
|
||||
InsnNode parent = sVar.getAssign().getParentInsn();
|
||||
if (parent != null && parent.getType() == InsnType.PHI) {
|
||||
return parent;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -166,7 +143,7 @@ public class RegisterArg extends InsnArg implements Named {
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return (regNum * 31 + type.hashCode()) * 31 + (sVar != null ? sVar.hashCode() : 0);
|
||||
return regNum * 31 + type.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,29 +1,79 @@
|
||||
package jadx.core.dex.instructions.args;
|
||||
|
||||
import jadx.core.dex.attributes.AttrNode;
|
||||
import jadx.core.dex.instructions.PhiInsn;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class SSAVar {
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class SSAVar extends AttrNode {
|
||||
|
||||
private final int regNum;
|
||||
private final int version;
|
||||
private VarName varName;
|
||||
|
||||
private int startUseAddr;
|
||||
private int endUseAddr;
|
||||
|
||||
@NotNull
|
||||
private RegisterArg assign;
|
||||
private final List<RegisterArg> useList = new ArrayList<RegisterArg>(2);
|
||||
@Nullable
|
||||
private PhiInsn usedInPhi;
|
||||
|
||||
private ArgType type;
|
||||
private boolean typeImmutable;
|
||||
|
||||
public SSAVar(int regNum, int v, RegisterArg assign) {
|
||||
public SSAVar(int regNum, int v, @NotNull RegisterArg assign) {
|
||||
this.regNum = regNum;
|
||||
this.version = v;
|
||||
this.assign = assign;
|
||||
|
||||
if (assign != null) {
|
||||
assign.setSVar(this);
|
||||
assign.setSVar(this);
|
||||
startUseAddr = -1;
|
||||
endUseAddr = -1;
|
||||
}
|
||||
|
||||
public int getStartAddr() {
|
||||
if (startUseAddr == -1) {
|
||||
calcUsageAddrRange();
|
||||
}
|
||||
return startUseAddr;
|
||||
}
|
||||
|
||||
public int getEndAddr() {
|
||||
if (endUseAddr == -1) {
|
||||
calcUsageAddrRange();
|
||||
}
|
||||
return endUseAddr;
|
||||
}
|
||||
|
||||
private void calcUsageAddrRange() {
|
||||
int start = Integer.MAX_VALUE;
|
||||
int end = Integer.MIN_VALUE;
|
||||
|
||||
if (assign.getParentInsn() != null) {
|
||||
int insnAddr = assign.getParentInsn().getOffset();
|
||||
if (insnAddr >= 0) {
|
||||
start = Math.min(insnAddr, start);
|
||||
end = Math.max(insnAddr, end);
|
||||
}
|
||||
}
|
||||
for (RegisterArg arg : useList) {
|
||||
if (arg.getParentInsn() != null) {
|
||||
int insnAddr = arg.getParentInsn().getOffset();
|
||||
if (insnAddr >= 0) {
|
||||
start = Math.min(insnAddr, start);
|
||||
end = Math.max(insnAddr, end);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (start != Integer.MAX_VALUE && end != Integer.MIN_VALUE) {
|
||||
startUseAddr = start;
|
||||
endUseAddr = end;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,11 +85,12 @@ public class SSAVar {
|
||||
return version;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public RegisterArg getAssign() {
|
||||
return assign;
|
||||
}
|
||||
|
||||
public void setAssign(RegisterArg assign) {
|
||||
public void setAssign(@NotNull RegisterArg assign) {
|
||||
this.assign = assign;
|
||||
}
|
||||
|
||||
@@ -68,10 +119,11 @@ public class SSAVar {
|
||||
}
|
||||
}
|
||||
|
||||
public void setUsedInPhi(PhiInsn usedInPhi) {
|
||||
public void setUsedInPhi(@Nullable PhiInsn usedInPhi) {
|
||||
this.usedInPhi = usedInPhi;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public PhiInsn getUsedInPhi() {
|
||||
return usedInPhi;
|
||||
}
|
||||
@@ -81,24 +133,34 @@ public class SSAVar {
|
||||
}
|
||||
|
||||
public int getVariableUseCount() {
|
||||
if (!isUsedInPhi()) {
|
||||
if (usedInPhi == null) {
|
||||
return useList.size();
|
||||
}
|
||||
return useList.size() + usedInPhi.getResult().getSVar().getUseCount();
|
||||
}
|
||||
|
||||
public ArgType getType() {
|
||||
return type;
|
||||
public void setType(ArgType type) {
|
||||
ArgType acceptedType;
|
||||
if (typeImmutable) {
|
||||
// don't change type, just update types in useList
|
||||
acceptedType = this.type;
|
||||
} else {
|
||||
acceptedType = type;
|
||||
this.type = acceptedType;
|
||||
}
|
||||
assign.type = acceptedType;
|
||||
for (int i = 0, useListSize = useList.size(); i < useListSize; i++) {
|
||||
useList.get(i).type = acceptedType;
|
||||
}
|
||||
}
|
||||
|
||||
public void setType(ArgType type) {
|
||||
this.type = type;
|
||||
if (assign != null) {
|
||||
assign.type = type;
|
||||
}
|
||||
for (int i = 0, useListSize = useList.size(); i < useListSize; i++) {
|
||||
useList.get(i).type = type;
|
||||
}
|
||||
public void setTypeImmutable(ArgType type) {
|
||||
setType(type);
|
||||
this.typeImmutable = true;
|
||||
}
|
||||
|
||||
public boolean isTypeImmutable() {
|
||||
return typeImmutable;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
|
||||
+9
-6
@@ -1,10 +1,12 @@
|
||||
package jadx.core.dex.instructions.args;
|
||||
|
||||
public class MthParameterArg extends RegisterArg {
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class TypeImmutableArg extends RegisterArg {
|
||||
|
||||
private boolean isThis;
|
||||
|
||||
public MthParameterArg(int rn, ArgType type) {
|
||||
public TypeImmutableArg(int rn, ArgType type) {
|
||||
super(rn, type);
|
||||
}
|
||||
|
||||
@@ -15,6 +17,7 @@ public class MthParameterArg extends RegisterArg {
|
||||
|
||||
@Override
|
||||
public void setType(ArgType type) {
|
||||
// not allowed
|
||||
}
|
||||
|
||||
public void markAsThis() {
|
||||
@@ -35,10 +38,11 @@ public class MthParameterArg extends RegisterArg {
|
||||
}
|
||||
|
||||
@Override
|
||||
void setSVar(SSAVar sVar) {
|
||||
void setSVar(@NotNull SSAVar sVar) {
|
||||
if (isThis) {
|
||||
sVar.setName("this");
|
||||
}
|
||||
sVar.setTypeImmutable(type);
|
||||
super.setSVar(sVar);
|
||||
}
|
||||
|
||||
@@ -47,14 +51,13 @@ public class MthParameterArg extends RegisterArg {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof MthParameterArg)) {
|
||||
if (!(obj instanceof TypeImmutableArg)) {
|
||||
return false;
|
||||
}
|
||||
if (!super.equals(obj)) {
|
||||
return false;
|
||||
}
|
||||
MthParameterArg that = (MthParameterArg) obj;
|
||||
return isThis == that.isThis;
|
||||
return isThis == ((TypeImmutableArg) obj).isThis;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1,6 +1,9 @@
|
||||
package jadx.core.dex.instructions.args;
|
||||
|
||||
public abstract class Typed {
|
||||
import jadx.core.dex.attributes.AttrNode;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
|
||||
public abstract class Typed extends AttrNode {
|
||||
|
||||
protected ArgType type;
|
||||
|
||||
@@ -16,8 +19,8 @@ public abstract class Typed {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean merge(ArgType newType) {
|
||||
ArgType m = ArgType.merge(type, newType);
|
||||
public boolean merge(DexNode dex, ArgType newType) {
|
||||
ArgType m = ArgType.merge(dex, type, newType);
|
||||
if (m != null && !m.equals(type)) {
|
||||
setType(m);
|
||||
return true;
|
||||
@@ -25,7 +28,7 @@ public abstract class Typed {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean merge(InsnArg arg) {
|
||||
return merge(arg.getType());
|
||||
public boolean merge(DexNode dex, InsnArg arg) {
|
||||
return merge(dex, arg.getType());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,13 @@ public class ConstructorInsn extends InsnNode {
|
||||
setSourceLine(invoke.getSourceLine());
|
||||
}
|
||||
|
||||
public ConstructorInsn(MethodInfo callMth, CallType callType, RegisterArg instanceArg) {
|
||||
super(InsnType.CONSTRUCTOR, callMth.getArgsCount());
|
||||
this.callMth = callMth;
|
||||
this.callType = callType;
|
||||
this.instanceArg = instanceArg;
|
||||
}
|
||||
|
||||
public MethodInfo getCallMth() {
|
||||
return callMth;
|
||||
}
|
||||
@@ -64,6 +71,14 @@ public class ConstructorInsn extends InsnNode {
|
||||
return callMth.getDeclClass();
|
||||
}
|
||||
|
||||
public CallType getCallType() {
|
||||
return callType;
|
||||
}
|
||||
|
||||
public boolean isNewInstance() {
|
||||
return callType == CallType.CONSTRUCTOR;
|
||||
}
|
||||
|
||||
public boolean isSuper() {
|
||||
return callType == CallType.SUPER;
|
||||
}
|
||||
@@ -77,26 +92,16 @@ public class ConstructorInsn extends InsnNode {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
public boolean isSame(InsnNode obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof ConstructorInsn) || !super.equals(o)) {
|
||||
if (!(obj instanceof ConstructorInsn) || !super.isSame(obj)) {
|
||||
return false;
|
||||
}
|
||||
ConstructorInsn that = (ConstructorInsn) o;
|
||||
return callMth.equals(that.callMth)
|
||||
&& callType == that.callType
|
||||
&& instanceArg.equals(that.instanceArg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = super.hashCode();
|
||||
result = 31 * result + callMth.hashCode();
|
||||
result = 31 * result + callType.hashCode();
|
||||
result = 31 * result + instanceArg.hashCode();
|
||||
return result;
|
||||
ConstructorInsn other = (ConstructorInsn) obj;
|
||||
return callMth.equals(other.callMth)
|
||||
&& callType == other.callType;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -5,13 +5,19 @@ import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.LiteralArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.regions.IfCondition;
|
||||
import jadx.core.dex.regions.conditions.IfCondition;
|
||||
import jadx.core.utils.InsnUtils;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
public class TernaryInsn extends InsnNode {
|
||||
import java.util.Collection;
|
||||
|
||||
private final IfCondition condition;
|
||||
public final class TernaryInsn extends InsnNode {
|
||||
|
||||
private IfCondition condition;
|
||||
|
||||
public TernaryInsn(IfCondition condition, RegisterArg result) {
|
||||
this(condition, result, LiteralArg.TRUE, LiteralArg.FALSE);
|
||||
}
|
||||
|
||||
public TernaryInsn(IfCondition condition, RegisterArg result, InsnArg th, InsnArg els) {
|
||||
super(InsnType.TERNARY, 2);
|
||||
@@ -33,23 +39,38 @@ public class TernaryInsn extends InsnNode {
|
||||
return condition;
|
||||
}
|
||||
|
||||
public void simplifyCondition() {
|
||||
condition = IfCondition.simplify(condition);
|
||||
if (condition.getMode() == IfCondition.Mode.NOT) {
|
||||
invert();
|
||||
}
|
||||
}
|
||||
|
||||
private void invert() {
|
||||
condition = IfCondition.invert(condition);
|
||||
InsnArg tmp = getArg(0);
|
||||
setArg(0, getArg(1));
|
||||
setArg(1, tmp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
public void getRegisterArgs(Collection<RegisterArg> list) {
|
||||
super.getRegisterArgs(list);
|
||||
list.addAll(condition.getRegisterArgs());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSame(InsnNode obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof TernaryInsn) || !super.equals(obj)) {
|
||||
if (!(obj instanceof TernaryInsn) || !super.isSame(obj)) {
|
||||
return false;
|
||||
}
|
||||
TernaryInsn that = (TernaryInsn) obj;
|
||||
return condition.equals(that.condition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 31 * super.hashCode() + condition.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return InsnUtils.formatOffset(offset) + ": TERNARY"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user