Compare commits

...

122 Commits

Author SHA1 Message Date
Skylot 3782aa7d0a core: fix wildcard type in iterable loop 2015-04-07 23:12:39 +03:00
Skylot d5740c1b08 core: fix 'finally' extract in 'if' 2015-04-07 23:12:39 +03:00
Skylot 3357979cc9 core: remove unused method 2015-04-07 23:12:39 +03:00
Skylot 2f548dd9eb core: fix help for jadx-gui, improve code 2015-04-06 22:30:48 +03:00
Skylot f715d6ce68 core: fix inherited methods renaming 2015-04-05 17:43:17 +03:00
Skylot 350b605400 core: use aliased name for save class to file 2015-04-05 16:20:31 +03:00
Skylot 6a99d00487 core: fix enum fields name after obfuscation (fix #51) 2015-04-05 15:56:58 +03:00
Skylot f87bf3f14d core: fix class renaming by source file info 2015-04-05 15:56:58 +03:00
Skylot 87347c0a04 core: move enum restore pass to later stage 2015-04-05 15:10:19 +03:00
Skylot 217737b3e8 core: add jadx visitors annotation for describe dependencies 2015-04-05 15:01:11 +03:00
Skylot efd8bd13da core: rename classes in default package 2015-04-04 21:40:08 +03:00
Skylot 051bb63a81 core: rename classes for case-insensitive systems (fix #24) 2015-04-04 20:56:15 +03:00
Skylot e4f4de6c8d core: fix imports for inner classes 2015-04-04 17:52:13 +03:00
Skylot e6aa85e01d core: skip tests as workaround for java compiler crush 2015-03-31 22:45:06 +03:00
Skylot cc4d94321e core: update android files to 5.1 (fix #58) 2015-03-31 22:25:04 +03:00
Skylot c1292dff75 core refactor: don't use static field in ArgType class 2015-03-29 15:15:56 +03:00
Skylot 1d81cab4a1 core: change anonymous class marking 2015-03-29 14:46:52 +03:00
Skylot 2815cef1bb gui: show info string if no recent files available 2015-03-29 14:45:19 +03:00
Skylot d4523c4e53 core: remove 'static' modifier for inner interfaces 2015-03-29 14:43:44 +03:00
Skylot 5d894b6150 core: don't process dependencies of dependencies 2015-03-29 14:38:15 +03:00
Skylot 2eddbb9119 core: move class renaming code from ClassInfo to RenameVisitor 2015-03-26 23:51:53 +03:00
Skylot a2513240ff core: fix method parameters annotation parsing (fix #57) 2015-03-26 23:50:32 +03:00
Skylot 0d509f94b7 core: fix various processing issues 2015-03-26 23:50:32 +03:00
Skylot e4fbbcf2d6 core: skip annotations parsing if error occurs (#57) 2015-03-25 22:30:22 +03:00
Skylot 9afacf72f8 core: move 'escape' method to string utils 2015-03-25 22:22:09 +03:00
Skylot 78a7e65a2d core: filter out java core classes from printed stacktraces 2015-03-25 22:18:27 +03:00
Skylot 3314de8dde core: rename fields and methods in deobfuscation pass. 2015-03-24 23:24:20 +03:00
Skylot 8dab9b83be core: fix various codegen errors 2015-03-17 23:29:15 +03:00
Skylot 7b264ef2be gui: add font selection dialog 2015-03-16 22:44:21 +03:00
Skylot 5a6600f748 core: fix try/catch wrap logic (fix #47) 2015-03-15 18:47:14 +03:00
Skylot 14ed0c3a3d core: rename classes with unicode characters or reserved names 2015-03-14 20:35:41 +03:00
Skylot 229d78f1ef gui: add preferences dialog 2015-03-14 20:35:37 +03:00
Skylot f770e4ef42 add full license text file 2015-03-14 19:18:59 +03:00
Skylot 66aa2f8f2a fix issues reported by coverity and code style 2015-03-09 14:00:59 +03:00
Skylot 99d831c498 core: use source file information for deobfuscation, fix code style issues 2015-03-08 17:37:24 +03:00
Skylot a532287ddf core: refactor deobfuscator 2015-03-08 14:46:01 +03:00
Skylot 7844e554aa core: refactor info classes for store only one instance 2015-03-08 14:18:12 +03:00
Skylot 10de4ff490 core: process dependant classes before code generation 2015-03-08 14:18:12 +03:00
Skylot eed65421ea core: fix incorrect argument removing in anonymous constructor, inline synthetic field increment method 2015-03-07 20:09:51 +03:00
Skylot 7accc6e516 core: fix synchronized block processing (fix #46) 2015-03-07 17:50:50 +03:00
Skylot fa8f9ccfaa core: move debug code to separate class 2015-03-07 17:50:33 +03:00
Skylot 8a264ca321 update gradle and dependencies 2015-03-07 17:03:53 +03:00
Skylot f366eac7eb core: fix switch in loop (fix #52) 2015-03-01 18:27:30 +03:00
Skylot 46d3992b41 core: fix 'finally' extract (fix #53 and #54) 2015-03-01 15:31:43 +03:00
Skylot 164123f542 core: improve variable names after 'toString' invoke 2015-03-01 15:21:13 +03:00
Skylot 72c301dc54 core: print error on failed method decode 2015-02-25 22:15:26 +03:00
Skylot e8fd1e1dc7 core: fix debug info processing NPE 2015-02-24 23:20:54 +03:00
Skylot 2b7f8931a4 core: fix source line for some return instructions 2015-02-21 18:09:14 +03:00
Skylot ec3b71e5b6 core: don't hardcode attributes count 2015-02-21 17:01:04 +03:00
Skylot f7303881aa core: fix annotations processing for method arguments 2015-02-21 16:58:54 +03:00
Skylot 1b98be0b0a core: fix array type for new-array instruction (fix #50) 2015-02-17 14:53:08 +03:00
Skylot e5b84d942e core: fix types for constant replace 2015-02-15 17:44:05 +03:00
Skylot 22e9ac22ba core: fix field search with obfuscated names 2015-02-14 19:28:42 +03:00
Skylot 8a6cdec796 core: refactor fill-array instruction processing and constants replace (fix #48) 2015-02-14 17:58:46 +03:00
skylot c5c4499a55 Merge pull request #49 from NeoSpb/basic_deobfuscation
core: added deobfuscation feature (basic functionality)
2015-02-10 22:18:29 +03:00
NeoSpb 30138f7a38 core: added deobfuscation feature (basic functionality) 2015-02-10 20:37:12 +03:00
Skylot 883429fa47 core: fix enum class processing for obfuscated code 2015-02-07 21:18:53 +03:00
Skylot 380ee75d9a core: fix constants replace for constructors and other instructions 2015-02-07 21:18:46 +03:00
Skylot 99d9814083 don't use concatenation in logger, fix other small code style issues 2015-02-07 17:58:19 +03:00
Skylot 141398aeac core: replace 'move' instruction instead argument inline 2015-01-31 14:28:01 +03:00
Skylot 07cef6fd62 update dependencies versions 2015-01-31 14:28:01 +03:00
Skylot aac041f960 core: fix logs and code style 2015-01-31 14:28:01 +03:00
skylot 6ef1600041 Merge pull request #44 from NeoSpb/fixdbgparser
Fix processing of debug info
2015-01-17 19:43:18 +03:00
NeoSpb 733836ea2d core: fix processing of debug info (if local variable used before declaring a debug info) 2015-01-13 19:42:57 +03:00
Skylot b4767626d9 core: prevent ClassCastException in StringBuilder chain converter 2015-01-12 23:34:03 +03:00
Skylot 84edfac8fa resources: improve string pool decoding and errors reporting 2015-01-12 23:33:36 +03:00
Skylot 69252ce721 core: fix processing 'try/catch' in 'if' block 2015-01-12 23:32:48 +03:00
Skylot df1152516a core: print original value near replaced with field value in switch 2015-01-10 21:30:21 +03:00
Skylot 02f9c25f52 core: support fall through cases in switch 2015-01-10 21:19:55 +03:00
Skylot 7fb3988173 resources: skip padding zeroes for UTF-8 string pool 2015-01-09 16:13:01 +03:00
Skylot a50352780b core: use resources ids in manifest decoding 2015-01-07 14:47:26 +03:00
Skylot ff093aeebb core: fix strings pool parsing in '.arsc' file 2015-01-07 12:45:08 +03:00
Skylot aa691af664 core: replace resources ids with names from '.arsc' file 2015-01-07 12:18:45 +03:00
Skylot e0ffb01852 core: first implementation of '.arsc' parser 2015-01-06 19:22:45 +03:00
Skylot 53be92c616 core: fix decoding UTF-8 strings in xml resources 2015-01-03 17:06:41 +03:00
Skylot 5f8f454b55 gui: show resources 2015-01-02 20:46:51 +03:00
Skylot 3700ecb717 core: add resources methods to jadx API 2015-01-02 20:46:44 +03:00
Skylot 811b0e7f30 core: fix 'break' insertion for switch/case blocks (fix #41) 2014-12-31 21:25:26 +03:00
Skylot 08ea61f4df core: don't traverse exception handlers twice (includes in TryCatchRegion) 2014-12-31 21:14:50 +03:00
Skylot 1d5368f5a2 core: improve out block detection in switch (issue #38) 2014-12-27 23:28:48 +03:00
Skylot 90fb95e785 core: check arguments for field arithmetic operations (fix #40) 2014-12-27 20:17:03 +03:00
skylot 0f97f07461 Merge pull request #39 from NeoSpb/warn_switch
core: show warning when failed to detect out node in non trivial switch
2014-12-26 23:43:37 +03:00
NeoSpb 7fe6b842a6 core: show warning when failed to detect out node in non trivial switch 2014-12-26 22:13:49 +03:00
skylot 02a97bcb3a Merge pull request #37 from NeoSpb/fix_gui_save_manifest
fix save AndroidManifest.xml when jadx-gui used
2014-12-26 21:48:42 +03:00
NeoSpb fd4289aa64 fix save AndroidManifest.xml when jadx-gui used 2014-12-26 21:14:43 +03:00
Skylot 716db8b964 manifest: restore application references and Android values (enums, flags) 2014-12-24 23:27:26 +03:00
Skylot b55975a35a core: reformat code and fix small issues in BinaryXMLParser 2014-12-23 23:27:50 +03:00
skylot 4cb34394b4 Merge pull request #36 from YASME-Tim/xmlparser
Added first implementation of an AndroidManifest.xml Parser! ;)
2014-12-23 21:50:23 +03:00
YASME-Tim aacb83290e Added option flag to make androidmanifest.xml decompiling optional. 2014-12-22 22:11:04 +01:00
YASME-Tim ddab4c269d Removed debug output. 2014-12-22 14:04:28 +01:00
YASME-Tim 6ddb0036fa Added style decoding and a first decoding for data type 17. 2014-12-22 13:36:10 +01:00
YASME-Tim 0f7ca8cea4 Added a whitespace before oneLiner ends. 2014-12-22 11:56:06 +01:00
YASME-Tim c4367e25a9 Removed call in main method. 2014-12-22 00:44:29 +01:00
YASME-Tim e081aadd27 Added xml header 2014-12-22 00:44:02 +01:00
YASME-Tim 2bacab7dc0 Removed old less-warnings branch commit changes. 2014-12-22 00:40:09 +01:00
YASME-Tim 824db6be2b Removed BinaryXMLParser Call in main method. 2014-12-22 00:15:30 +01:00
YASME-Tim 2fdb26146b Refactored attribute value printing. 2014-12-22 00:14:46 +01:00
YASME-Tim b87d1a7fe1 Fixed XML oneLiners. Added another attribute value data type 2014-12-22 00:11:37 +01:00
YASME-Tim c242a62bcc Write xml to a given output file instead of stdout. 2014-12-21 23:26:02 +01:00
YASME-Tim 6c91bce663 Correct tab numbers. Some little things still missing. 2014-12-21 23:03:15 +01:00
YASME-Tim 7fd46633a3 First near working example for first sample. 2014-12-21 22:37:50 +01:00
YASME-Tim 3c425990f6 Boundaries check. Testing with other given xml binaries. 2014-12-21 22:30:32 +01:00
YASME-Tim 55f16cc3ec Boundaries check. Testing with other given xml binaries. 2014-12-21 22:30:21 +01:00
YASME-Tim e01789bb0d Added first implementation of the AndroidManifest XML Parser 2014-12-21 22:05:44 +01:00
skylot e3696af8ea Merge pull request #34 from YASME-Tim/zip-file
Added support for files ending in .zip.
2014-12-20 11:20:26 +03:00
YASME-Tim a26d7b5a8b Removed some warnings about collections without type specifiers. 2014-12-19 23:40:35 +01:00
YASME-Tim c4fe9150bf Added support for files ending in .zip. 2014-12-19 22:42:14 +01:00
Skylot ffc642048e core: fix type check for loop over iterable. 2014-12-18 22:24:28 +03:00
Skylot 8de6190a81 core: fix type inference for arguments in Phi node (fix #33) 2014-12-17 23:18:44 +03:00
Skylot d6e2c69202 travis: use container based build 2014-12-17 23:18:44 +03:00
Skylot 1a85fa8e3c core: fix complex conditions with mode alternation (fix #31) 2014-12-13 18:24:36 +03:00
skylot c7b8508c6f Merge pull request #29 from riboribo/master
Extended convertInvoke to support more StringBuilder formats
2014-12-03 22:50:27 +03:00
Bob Flavin c35f6e2543 Extended convertInvoke to handle calls to StringBuilder constructor with
arguments, ie: new StringBuilder("str")  or  new
StringBuilder(String.valueOf(var))
2014-12-03 12:34:02 -05:00
Bob Flavin 8052a90d04 Extended string concatenation code to handle arguments in 'new
StringBuilder()' constructer, ie  'new StringBuilder("str")' or 'new
StringBuilder(String.valueof(varName))'
2014-12-03 11:58:04 -05:00
Skylot 3d20d7d330 core: improve 'finally' extraction, refactor instructions 2014-11-29 20:48:04 +03:00
Skylot 5e722c6827 fix issues reported by coverity 2014-11-29 20:48:04 +03:00
Skylot 10198bc87f gui: update RSyntaxTextArea version, refactor new version checks 2014-11-29 20:47:54 +03:00
Skylot a6b4043e8c update gradle and dependencies 2014-11-29 16:08:29 +03:00
Skylot 9cea0163fa core: fix BlockNode hashCode function 2014-11-29 14:43:51 +03:00
Skylot 577176dd31 core: implement 'finally' block extraction 2014-11-26 22:00:47 +03:00
Skylot a135eb44f3 core: check registers numbers, fix fallback mode 2014-11-13 22:42:52 +03:00
Skylot 252ed0e1e4 bump version 2014-11-09 16:48:21 +03:00
257 changed files with 11717 additions and 2548 deletions
+2
View File
@@ -13,6 +13,8 @@ script:
after_success:
- TERM=dumb ./gradlew jacocoTestReport coveralls
sudo: false
cache:
directories:
- $HOME/.gradle
+201
View File
@@ -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.
+11 -10
View File
@@ -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,7 @@ THE POSSIBILITY OF SUCH DAMAGE.
Jadx-gui components
===================
RSyntaxTextArea library licensed under modified BSD liense:
RSyntaxTextArea library licensed under modified BSD license:
*******************************************************************************
Copyright (c) 2012, Robert Futrell
@@ -178,3 +178,4 @@ Icons copied from several places:
- Eclipse Project (JDT UI) - licensed under EPL v1.0 (http://www.eclipse.org/legal/epl-v10.html)
- famfamfam silk icon set (http://www.famfamfam.com/lab/icons/silk/) - licensed under Creative Commons Attribution 2.5 License (http://creativecommons.org/licenses/by/2.5/)
JFontChooser Component - http://sourceforge.jp/projects/jfontchooser/
+20 -12
View File
@@ -4,6 +4,7 @@
[![Build Status](https://drone.io/github.com/skylot/jadx/status.png)](https://drone.io/github.com/skylot/jadx/latest)
[![Coverage Status](https://coveralls.io/repos/skylot/jadx/badge.png)](https://coveralls.io/r/skylot/jadx)
[![Coverity Scan Build Status](https://scan.coverity.com/projects/2166/badge.svg)](https://scan.coverity.com/projects/2166)
[![License](http://img.shields.io/:license-apache-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0.html)
**jadx** - Dex to Java decompiler
@@ -41,22 +42,29 @@ 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
-f, --fallback - make simple dump (using goto instead of 'if', 'for', etc)
-r, --no-res - do not decode resources
-s, --no-src - do not decompile source code
--show-bad-code - show inconsistent code (incorrectly decompiled)
--cfg - save methods control flow graph to dot file
--raw-cfg - save methods control flow graph (use raw instructions)
-v, --verbose - verbose output
--deobf - activate deobfuscation
--deobf-min - min length of name
--deobf-max - max length of name
--deobf-rewrite-cfg - force to save deobfuscation map
-h, --help - print this help
Example:
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"`
@@ -70,12 +78,12 @@ To support this project you can:
* 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
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*
+37 -21
View File
@@ -1,13 +1,26 @@
ext.jadxVersion = file('version').readLines().get(0)
version = jadxVersion
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"
}
apply plugin: 'sonar-runner'
ext.jadxVersion = file('version').readLines().get(0)
version = jadxVersion
subprojects {
apply plugin: 'java'
apply plugin: 'groovy'
apply plugin: 'jacoco'
apply plugin: 'coveralls'
apply plugin: 'com.github.kt3k.coveralls'
version = jadxVersion
@@ -28,17 +41,19 @@ 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.10.10'
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()
}
@@ -50,21 +65,18 @@ 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'
}
/* Sonar runner configuration */
repositories {
mavenCentral()
}
sonarRunner {
toolVersion = '2.4'
}
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
}
}
@@ -81,13 +93,17 @@ task dist(dependsOn: pack) {
task samples(dependsOn: 'jadx-samples:samples') {
}
task build(dependsOn: [dist, samples]) {
task pitest(overwrite: true, dependsOn: 'jadx-core:pitest') {
}
task clean(type: Delete) {
task cleanBuildDir(type: Delete) {
delete buildDir
}
build.dependsOn(dist, samples)
clean.dependsOn(cleanBuildDir)
task wrapper(type: Wrapper) {
gradleVersion = '2.0'
gradleVersion = '2.3'
}
Binary file not shown.
+1 -1
View File
@@ -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.3-all.zip
+2 -1
View File
@@ -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'
}
}
+2 -2
View File
@@ -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);
}
@@ -8,7 +8,9 @@ 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;
@@ -18,7 +20,7 @@ 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;
@@ -32,6 +34,12 @@ public final class JadxCLIArgs implements IJadxArgs {
@Parameter(names = {"-f", "--fallback"}, description = "make simple dump (using goto instead of 'if', 'for', etc)")
protected boolean fallbackMode = false;
@Parameter(names = {"-r", "--no-res"}, description = "do not decode resources")
protected boolean skipResources = false;
@Parameter(names = {"-s", "--no-src"}, description = "do not decompile source code")
protected boolean skipSources = false;
@Parameter(names = {"--show-bad-code"}, description = "show inconsistent code (incorrectly decompiled)")
protected boolean showInconsistentCode = false;
@@ -44,6 +52,18 @@ public final class JadxCLIArgs implements IJadxArgs {
@Parameter(names = {"-v", "--verbose"}, description = "verbose output")
protected boolean verbose = false;
@Parameter(names = {"--deobf"}, description = "activate deobfuscation")
protected boolean deobfuscationOn = false;
@Parameter(names = {"--deobf-min"}, description = "min length of name")
protected int deobfuscationMinLength = 2;
@Parameter(names = {"--deobf-max"}, description = "max length of name")
protected int deobfuscationMaxLength = 64;
@Parameter(names = {"--deobf-rewrite-cfg"}, description = "force to save deobfuscation map")
protected boolean deobfuscationForceSave = false;
@Parameter(names = {"-h", "--help"}, description = "print this help", help = true)
protected boolean printHelp = false;
@@ -114,28 +134,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");
@@ -164,6 +183,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;
@@ -193,4 +222,24 @@ public final class JadxCLIArgs implements IJadxArgs {
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;
}
}
+11 -1
View File
@@ -1,9 +1,12 @@
ext.jadxClasspath = 'clsp-data/android-4.3.jar'
ext.jadxClasspath = 'clsp-data/android-5.1.jar'
apply plugin: "info.solidsoft.pitest"
dependencies {
runtime files(jadxClasspath)
compile files('lib/dx-1.10.jar')
compile 'commons-io:commons-io:2.4'
compile 'org.ow2.asm:asm:5.0.3'
compile 'com.intellij:annotations:12.0'
@@ -15,3 +18,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.
@@ -38,4 +38,34 @@ public class DefaultJadxArgs implements IJadxArgs {
public boolean isVerbose() {
return false;
}
@Override
public boolean isSkipResources() {
return false;
}
@Override
public boolean isSkipSources() {
return false;
}
@Override
public boolean isDeobfuscationOn() {
return false;
}
@Override
public int getDeobfuscationMinLength() {
return Integer.MIN_VALUE + 1;
}
@Override
public int getDeobfuscationMaxLength() {
return Integer.MAX_VALUE - 1;
}
@Override
public boolean isDeobfuscationForceSave() {
return false;
}
}
@@ -16,4 +16,16 @@ public interface IJadxArgs {
boolean isShowInconsistentCode();
boolean isVerbose();
boolean isSkipResources();
boolean isSkipSources();
boolean isDeobfuscationOn();
int getDeobfuscationMinLength();
int getDeobfuscationMaxLength();
boolean isDeobfuscationForceSave();
}
@@ -2,7 +2,8 @@ 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.codegen.CodeWriter;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.IDexTreeVisitor;
@@ -11,6 +12,7 @@ import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.InputFile;
import jadx.core.xmlgen.BinaryXMLParser;
import java.io.File;
import java.io.IOException;
@@ -53,7 +55,12 @@ 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;
public JadxDecompiler() {
this(new DefaultJadxArgs());
@@ -76,12 +83,16 @@ public final class JadxDecompiler {
outDir = new DefaultJadxArgs().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() {
@@ -108,8 +119,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 +141,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,14 +153,31 @@ public final class JadxDecompiler {
LOG.info("processing ...");
ExecutorService executor = Executors.newFixedThreadPool(threadsCount);
for (final JavaClass cls : getClasses()) {
executor.execute(new Runnable() {
@Override
public void run() {
cls.decompile();
SaveCode.save(outDir, args, cls.getClassNode());
}
});
if (saveSources) {
for (final JavaClass cls : getClasses()) {
executor.execute(new Runnable() {
@Override
public void run() {
cls.decompile();
SaveCode.save(outDir, args, cls.getClassNode());
}
});
}
}
if (saveResources) {
for (final ResourceFile resourceFile : getResources()) {
executor.execute(new Runnable() {
@Override
public void run() {
if (ResourceType.isSupportedForUnpack(resourceFile.getType())) {
CodeWriter cw = resourceFile.getContent();
if (cw != null) {
cw.save(new File(outDir, resourceFile.getName()));
}
}
}
});
}
}
return executor;
}
@@ -153,6 +197,16 @@ public final class JadxDecompiler {
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()) {
@@ -200,19 +254,44 @@ public final class JadxDecompiler {
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;
}
BinaryXMLParser getXmlParser() {
if (xmlParser == null) {
xmlParser = new BinaryXMLParser(root);
}
return xmlParser;
}
JavaClass findJavaClass(ClassNode cls) {
if (cls == null) {
return null;
@@ -225,6 +304,10 @@ public final class JadxDecompiler {
return null;
}
public IJadxArgs getArgs() {
return args;
}
@Override
public String toString() {
return "jadx decompiler " + getVersion();
@@ -115,6 +115,9 @@ public final class JavaClass implements JavaNode {
public CodePosition getDefinitionPosition(int line, int offset) {
Map<CodePosition, Object> map = getCodeAnnotations();
if (map.isEmpty()) {
return null;
}
Object obj = map.get(new CodePosition(line, offset));
if (!(obj instanceof LineAttrNode)) {
return null;
@@ -202,6 +205,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
@@ -17,7 +17,7 @@ public final class JavaMethod implements JavaNode {
@Override
public String getName() {
return mth.getName();
return mth.getAlias();
}
@Override
@@ -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,7 @@ public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
}
@Override
public int compareTo(JavaPackage o) {
public int compareTo(@NotNull JavaPackage o) {
return name.compareTo(o.name);
}
@@ -0,0 +1,67 @@
package jadx.api;
import jadx.core.codegen.CodeWriter;
import java.io.File;
public class ResourceFile {
public static final class ZipRef {
private final File zipFile;
private final String entryName;
public ZipRef(File zipFile, String entryName) {
this.zipFile = zipFile;
this.entryName = entryName;
}
public File getZipFile() {
return zipFile;
}
public String getEntryName() {
return entryName;
}
@Override
public String toString() {
return "ZipRef{" + zipFile + ", '" + entryName + "'}";
}
}
private final JadxDecompiler decompiler;
private final String name;
private final ResourceType type;
private ZipRef zipRef;
ResourceFile(JadxDecompiler decompiler, String name, ResourceType type) {
this.decompiler = decompiler;
this.name = name;
this.type = type;
}
public String getName() {
return name;
}
public ResourceType getType() {
return type;
}
public CodeWriter getContent() {
return ResourcesLoader.loadContent(decompiler, this);
}
void setZipRef(ZipRef zipRef) {
this.zipRef = zipRef;
}
ZipRef getZipRef() {
return zipRef;
}
@Override
public String toString() {
return "ResourceFile{name='" + name + '\'' + ", type=" + type + "}";
}
}
@@ -0,0 +1,50 @@
package jadx.api;
public enum ResourceType {
CODE(".dex", ".class"),
MANIFEST("AndroidManifest.xml"),
XML(".xml"), // TODO binary or not?
ARSC(".arsc"), // TODO decompile !!!
FONT(".ttf"),
IMG(".png", ".gif", ".jpg"),
LIB(".so"),
UNKNOWN;
private final String[] exts;
ResourceType(String... exts) {
this.exts = exts;
}
public String[] getExts() {
return exts;
}
public static ResourceType getFileType(String fileName) {
for (ResourceType type : ResourceType.values()) {
for (String ext : type.getExts()) {
if (fileName.endsWith(ext)) {
return type;
}
}
}
return UNKNOWN;
}
public static boolean isSupportedForUnpack(ResourceType type) {
switch (type) {
case CODE:
case ARSC:
case LIB:
case FONT:
case IMG:
case UNKNOWN:
return false;
case MANIFEST:
case XML:
return true;
}
return false;
}
}
@@ -0,0 +1,169 @@
package jadx.api;
import jadx.api.ResourceFile.ZipRef;
import jadx.core.codegen.CodeWriter;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.files.InputFile;
import jadx.core.xmlgen.ResTableParser;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
// TODO: move to core package
public final class ResourcesLoader {
private static final Logger LOG = LoggerFactory.getLogger(ResourcesLoader.class);
private static final int READ_BUFFER_SIZE = 8 * 1024;
private static final int LOAD_SIZE_LIMIT = 10 * 1024 * 1024;
private final JadxDecompiler jadxRef;
ResourcesLoader(JadxDecompiler jadxRef) {
this.jadxRef = jadxRef;
}
List<ResourceFile> load(List<InputFile> inputFiles) {
List<ResourceFile> list = new ArrayList<ResourceFile>(inputFiles.size());
for (InputFile file : inputFiles) {
loadFile(list, file.getFile());
}
return list;
}
public interface ResourceDecoder {
Object decode(long size, InputStream is) throws IOException;
}
public static Object decodeStream(ResourceFile rf, ResourceDecoder decoder) throws JadxException {
ZipRef zipRef = rf.getZipRef();
if (zipRef == null) {
return null;
}
ZipFile zipFile = null;
InputStream inputStream = null;
try {
zipFile = new ZipFile(zipRef.getZipFile());
ZipEntry entry = zipFile.getEntry(zipRef.getEntryName());
if (entry == null) {
throw new IOException("Zip entry not found: " + zipRef);
}
inputStream = new BufferedInputStream(zipFile.getInputStream(entry));
return decoder.decode(entry.getSize(), inputStream);
} catch (Exception e) {
throw new JadxException("Error decode: " + zipRef.getEntryName(), e);
} finally {
try {
if (zipFile != null) {
zipFile.close();
}
if (inputStream != null) {
inputStream.close();
}
} catch (Exception e) {
LOG.debug("Error close zip file: {}", zipRef, e);
}
}
}
static CodeWriter loadContent(final JadxDecompiler jadxRef, final ResourceFile rf) {
try {
return (CodeWriter) decodeStream(rf, new ResourceDecoder() {
@Override
public Object decode(long size, InputStream is) throws IOException {
if (size > LOAD_SIZE_LIMIT) {
return new CodeWriter().add("File too big, size: "
+ String.format("%.2f KB", size / 1024.));
}
return loadContent(jadxRef, rf.getType(), is);
}
});
} catch (JadxException e) {
LOG.error("Decode error", e);
CodeWriter cw = new CodeWriter();
cw.add("Error decode ").add(rf.getType().toString().toLowerCase());
cw.startLine(Utils.getStackTrace(e.getCause()));
return cw;
}
}
private static CodeWriter loadContent(JadxDecompiler jadxRef, ResourceType type,
InputStream inputStream) throws IOException {
switch (type) {
case MANIFEST:
case XML:
return jadxRef.getXmlParser().parse(inputStream);
case ARSC:
return new ResTableParser().decodeToCodeWriter(inputStream);
}
return loadToCodeWriter(inputStream);
}
private void loadFile(List<ResourceFile> list, File file) {
if (file == null) {
return;
}
ZipFile zip = null;
try {
zip = new ZipFile(file);
Enumeration<? extends ZipEntry> entries = zip.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
addEntry(list, file, entry);
}
} catch (IOException e) {
LOG.debug("Not a zip file: {}", file.getAbsolutePath());
} finally {
if (zip != null) {
try {
zip.close();
} catch (Exception e) {
LOG.error("Zip file close error: {}", file.getAbsolutePath(), e);
}
}
}
}
private void addEntry(List<ResourceFile> list, File zipFile, ZipEntry entry) {
if (entry.isDirectory()) {
return;
}
String name = entry.getName();
ResourceType type = ResourceType.getFileType(name);
ResourceFile rf = new ResourceFile(jadxRef, name, type);
rf.setZipRef(new ZipRef(zipFile, name));
list.add(rf);
// LOG.debug("Add resource entry: {}, size: {}", name, entry.getSize());
}
private static CodeWriter loadToCodeWriter(InputStream is) throws IOException {
CodeWriter cw = new CodeWriter();
ByteArrayOutputStream baos = new ByteArrayOutputStream(READ_BUFFER_SIZE);
byte[] buffer = new byte[READ_BUFFER_SIZE];
int count;
try {
while ((count = is.read(buffer)) != -1) {
baos.write(buffer, 0, count);
}
} finally {
try {
is.close();
} catch (Exception ignore) {
}
}
cw.add(baos.toString("UTF-8"));
return cw;
}
}
@@ -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;";
}
+22 -7
View File
@@ -1,12 +1,11 @@
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.FallbackModeVisitor;
@@ -15,7 +14,13 @@ 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;
@@ -55,23 +60,29 @@ 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(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(DotGraphVisitor.dump(outDir));
}
@@ -90,11 +101,15 @@ public class Jadx {
passes.add(new MethodInlineVisitor());
passes.add(new ClassModifier());
passes.add(new EnumVisitor());
passes.add(new PrepareForCodeGen());
passes.add(new LoopRegionVisitor());
passes.add(new ProcessVariables());
passes.add(new DependencyCollector());
passes.add(new RenameVisitor());
}
passes.add(new CodeGen(args));
return passes;
}
@@ -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,6 +1,6 @@
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.exceptions.DecodeException;
@@ -77,15 +77,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,7 +96,7 @@ 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;
}
@@ -114,7 +114,6 @@ public class ClsSet {
try {
out.putNextEntry(new ZipEntry(CLST_PKG_PATH + "/" + CLST_FILENAME));
save(out);
out.closeEntry();
} finally {
out.close();
}
@@ -45,16 +45,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 +56,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;
}
@@ -1,5 +1,6 @@
package jadx.core.clsp;
import jadx.api.DefaultJadxArgs;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.files.InputFile;
@@ -42,7 +43,7 @@ public class ConvertToClsSet {
LOG.info("Loaded: {}", inputFile.getFile());
}
RootNode root = new RootNode();
RootNode root = new RootNode(new DefaultJadxArgs());
root.load(inputFiles);
ClsSet set = new ClsSet();
@@ -52,7 +53,8 @@ public class ConvertToClsSet {
LOG.info("done");
}
private static void addFilesFromDirectory(File dir, List<InputFile> inputFiles) throws IOException, DecodeException {
private static void addFilesFromDirectory(File dir,
List<InputFile> inputFiles) throws IOException, DecodeException {
File[] files = dir.listFiles();
if (files == null) {
return;
@@ -61,7 +63,10 @@ public class ConvertToClsSet {
if (file.isDirectory()) {
addFilesFromDirectory(file, inputFiles);
}
if (file.getName().endsWith(".dex")) {
String fileName = file.getName();
if (fileName.endsWith(".dex")
|| fileName.endsWith(".jar")
|| fileName.endsWith(".apk")) {
inputFiles.add(new InputFile(file));
}
}
@@ -42,7 +42,11 @@ public class AnnotationGen {
}
public void addForParameter(CodeWriter code, MethodParameters paramsAnnotations, int n) {
AnnotationsList aList = paramsAnnotations.getParamList().get(n);
List<AnnotationsList> paramList = paramsAnnotations.getParamList();
if (n >= paramList.size()) {
return;
}
AnnotationsList aList = paramList.get(n);
if (aList == null || aList.isEmpty()) {
return;
}
@@ -1,7 +1,6 @@
package jadx.core.codegen;
import jadx.api.IJadxArgs;
import jadx.core.Consts;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.AttrNode;
@@ -11,8 +10,8 @@ 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;
@@ -51,21 +50,24 @@ public class ClassGen {
private final ClassGen parentGen;
private final AnnotationGen annotationGen;
private final boolean fallback;
private boolean showInconsistentCode = false;
private final boolean showInconsistentCode;
private final Set<ClassInfo> imports = new HashSet<ClassInfo>();
private int clsDeclLine;
public ClassGen(ClassNode cls, ClassGen parentClsGen, IJadxArgs jadxArgs) {
this(cls, parentClsGen, jadxArgs.isFallbackMode());
this.showInconsistentCode = jadxArgs.isShowInconsistentCode();
public ClassGen(ClassNode cls, IJadxArgs jadxArgs) {
this(cls, null, jadxArgs.isFallbackMode(), jadxArgs.isShowInconsistentCode());
}
public ClassGen(ClassNode cls, ClassGen parentClsGen, boolean fallback) {
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);
}
@@ -87,7 +89,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);
@@ -117,20 +119,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' modifier not allowed for top classes (not inner)
if (!cls.getClassInfo().isInner()) {
af = af.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()) {
@@ -147,10 +151,10 @@ public class ClassGen {
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(' ');
@@ -162,8 +166,8 @@ public class ClassGen {
} 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(", ");
@@ -191,7 +195,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 ");
@@ -200,7 +204,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(" & ");
@@ -227,10 +231,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());
@@ -239,7 +243,7 @@ public class ClassGen {
private boolean isInnerClassesPresents() {
for (ClassNode innerCls : cls.getInnerClasses()) {
if (!innerCls.isAnonymous()) {
if (!innerCls.contains(AFlag.ANONYMOUS_CLASS)) {
return true;
}
}
@@ -299,10 +303,11 @@ public class ClassGen {
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);
@@ -313,7 +318,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('}');
}
@@ -329,7 +338,7 @@ public class ClassGen {
code.startLine(f.getAccessFlags().makeString());
useType(code, f.getType());
code.add(' ');
code.add(f.getName());
code.add(f.getAlias());
FieldValueAttr fv = f.get(AType.FIELD_VALUE);
if (fv != null) {
code.add(" = ");
@@ -361,26 +370,19 @@ 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().isEmpty()) {
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) {
MethodGen mthGen = new MethodGen(this, enumFields.getStaticMethod());
igen = new InsnGen(mthGen, false);
}
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(',');
@@ -391,6 +393,9 @@ public class ClassGen {
code.startLine();
}
code.add(';');
if (isFieldsPresents()) {
code.startLine();
}
}
}
@@ -402,7 +407,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());
@@ -412,14 +417,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);
code.add(baseClass);
ArgType[] generics = classInfo.getType().getGenericTypes();
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;
@@ -444,53 +444,63 @@ 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, classInfo)) {
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;
}
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);
}
@@ -504,6 +514,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();
@@ -524,7 +544,7 @@ public class ClassGen {
if (classNode != null) {
for (ClassNode inner : classNode.getInnerClasses()) {
if (inner.getShortName().equals(shortName)
&& !inner.getClassInfo().equals(searchCls)) {
&& !inner.getAlias().equals(searchCls)) {
return true;
}
}
@@ -535,7 +555,14 @@ public class ClassGen {
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()) {
code.startLine("/* renamed from: ").add(classInfo.getFullName()).add(" */");
}
}
@@ -15,7 +15,7 @@ public class CodeGen extends AbstractVisitor {
@Override
public boolean visit(ClassNode cls) throws CodegenException {
ClassGen clsGen = new ClassGen(cls, null, args);
ClassGen clsGen = new ClassGen(cls, args);
CodeWriter clsCode = clsGen.makeClass();
clsCode.finish();
cls.setCode(clsCode);
@@ -174,6 +174,10 @@ public class CodeWriter {
updateIndent();
}
public int getIndent() {
return indent;
}
public int getLine() {
return line;
}
@@ -162,10 +162,7 @@ public class ConditionGen extends InsnGen {
if (condition.isCompare()) {
return false;
}
if (condition.getMode() != Mode.NOT) {
return true;
}
return false;
return condition.getMode() != Mode.NOT;
}
private static boolean isArgWrapNeeded(InsnArg arg) {
@@ -13,12 +13,14 @@ 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;
@@ -34,14 +36,13 @@ import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.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;
@@ -156,19 +157,19 @@ public class InsnGen {
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());
code.add(declClass.getAlias().getShortName());
} else {
clsGen.useClass(code, declClass);
}
@@ -178,13 +179,17 @@ 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);
}
@@ -199,9 +204,6 @@ public class InsnGen {
protected boolean makeInsn(InsnNode insn, CodeWriter code, Flags flag) throws CodegenException {
try {
if (insn.getType() == InsnType.NOP) {
return false;
}
Set<Flags> state = EnumSet.noneOf(Flags.class);
if (flag == Flags.BODY_ONLY || flag == Flags.BODY_ONLY_NOWRAP) {
state.add(flag);
@@ -341,7 +343,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('[');
@@ -359,12 +361,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:
@@ -445,9 +443,6 @@ public class InsnGen {
addArg(code, insn.getArg(0));
break;
case PHI:
break;
/* fallback mode instructions */
case IF:
assert isFallback() : "if insn in not fallback mode";
@@ -488,6 +483,25 @@ public class InsnGen {
code.startLine('}');
break;
case FILL_ARRAY:
assert isFallback();
FillArrayNode arrayNode = (FillArrayNode) insn;
Object data = arrayNode.getData();
String arrStr;
if (data instanceof int[]) {
arrStr = Arrays.toString((int[]) data);
} else if (data instanceof short[]) {
arrStr = Arrays.toString((short[]) data);
} else if (data instanceof byte[]) {
arrStr = Arrays.toString((byte[]) data);
} else if (data instanceof long[]) {
arrStr = Arrays.toString((long[]) data);
} else {
arrStr = "?";
}
code.add('{').add(arrStr.substring(1, arrStr.length() - 1)).add('}');
break;
case NEW_INSTANCE:
// only fallback - make new instance in constructor invoke
assert isFallback();
@@ -499,11 +513,11 @@ public class InsnGen {
}
}
private void filledNewArray(InsnNode insn, CodeWriter code) throws CodegenException {
int c = insn.getArgsCount();
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) {
@@ -513,79 +527,12 @@ public class InsnGen {
code.add('}');
}
private void fillArray(FillArrayNode insn, CodeWriter code) throws CodegenException {
String filledArray = makeArrayElements(insn);
code.add("new ");
useType(code, insn.getElementType());
code.add("[]{").add(filledArray).add('}');
}
private String makeArrayElements(FillArrayNode insn) 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()) {
LOG.warn("Unknown array element type: {} in mth: {}", elType, mth);
elType = insnElementType.isTypeKnown() ? insnElementType : elType.selectFirst();
}
insn.mergeElementType(elType);
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);
return str.toString();
}
private void makeConstructor(ConstructorInsn insn, CodeWriter code)
throws CodegenException {
ClassNode cls = mth.dex().resolveClass(insn.getClassType());
if (cls != null && cls.isAnonymous() && !fallback) {
if (cls != null && cls.contains(AFlag.ANONYMOUS_CLASS) && !fallback) {
// anonymous class construction
ClassInfo parent;
ArgType parent;
if (cls.getInterfaces().size() == 1) {
parent = cls.getInterfaces().get(0);
} else {
@@ -607,7 +554,7 @@ public class InsnGen {
useClass(code, parent);
}
code.add("() ");
new ClassGen(cls, mgen.getClassGen().getParentGen(), fallback).addClassBody(code);
new ClassGen(cls, mgen.getClassGen().getParentGen()).addClassBody(code);
return;
}
if (insn.isSelf()) {
@@ -628,9 +575,12 @@ public class InsnGen {
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;
@@ -654,7 +604,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);
@@ -665,11 +615,11 @@ public class InsnGen {
if (callMthNode != null) {
code.attachAnnotation(callMthNode);
}
code.add(callMth.getName());
code.add(callMth.getAlias());
generateMethodArguments(code, insn, k, callMthNode);
}
private void generateMethodArguments(CodeWriter code, InsnNode insn, int startArgNum,
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)) {
@@ -729,9 +679,6 @@ public class InsnGen {
}
}
return true;
} else if (insn.getType() == InsnType.FILL_ARRAY) {
code.add(makeArrayElements((FillArrayNode) insn));
return true;
}
return false;
}
@@ -5,6 +5,7 @@ import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.annotations.MethodParameters;
import jadx.core.dex.attributes.nodes.JadxErrorAttr;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.InsnNode;
@@ -38,7 +39,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() {
@@ -89,7 +90,7 @@ public class MethodGen {
} else {
classGen.useType(code, mth.getReturnType());
code.add(' ');
code.add(mth.getName());
code.add(mth.getAlias());
}
code.add('(');
@@ -153,12 +154,8 @@ 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) {
@@ -171,6 +168,10 @@ public class MethodGen {
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 +180,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,7 +209,7 @@ 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 && (insn.contains(AType.JUMP) || insn.contains(AType.EXC_HANDLER))) {
@@ -228,8 +234,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);
}
@@ -4,6 +4,7 @@ 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;
@@ -13,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;
@@ -25,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 {
@@ -44,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;
}
@@ -70,7 +74,7 @@ public class NameGen {
public String useArg(RegisterArg arg) {
String name = arg.getName();
if (name == null) {
if (name == null || fallback) {
return getFallbackName(arg);
}
return name;
@@ -106,7 +110,7 @@ public class NameGen {
}
varName = name;
} else {
varName = makeNameForType(arg.getType());
varName = guessName(arg);
}
if (NameMapper.isReserved(varName)) {
return varName + "R";
@@ -115,15 +119,26 @@ public class NameGen {
}
private String getFallbackName(RegisterArg arg) {
String name = arg.getName();
String base = "r" + arg.getRegNum();
if (name != null && !name.equals("this")) {
return base + "_" + name;
}
return base;
return "r" + arg.getRegNum();
}
private static String makeNameForType(ArgType type) {
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()) {
@@ -137,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) {
@@ -171,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;
@@ -227,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;
}
}
@@ -5,8 +5,6 @@ import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
import jadx.core.dex.attributes.nodes.ForceReturnAttr;
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.SwitchNode;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.NamedArg;
@@ -17,6 +15,7 @@ import jadx.core.dex.nodes.IBlock;
import jadx.core.dex.nodes.IContainer;
import jadx.core.dex.nodes.IRegion;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.parser.FieldValueAttr;
import jadx.core.dex.regions.Region;
import jadx.core.dex.regions.SwitchRegion;
import jadx.core.dex.regions.SynchronizedRegion;
@@ -28,13 +27,13 @@ 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;
@@ -246,63 +245,56 @@ public class RegionGen extends InsnGen {
if (k instanceof FieldNode) {
FieldNode fn = (FieldNode) k;
if (fn.getParentClass().isEnum()) {
code.add(fn.getName());
code.add(fn.getAlias());
} else {
staticField(code, fn.getFieldInfo());
// print original value, sometimes replace with incorrect field
FieldValueAttr valueAttr = fn.get(AType.FIELD_VALUE);
if (valueAttr != null && valueAttr.getValue() != null) {
code.add(" /*").add(valueAttr.getValue().toString()).add("*/");
}
}
} else if (k instanceof IndexInsnNode) {
staticField(code, (FieldInfo) ((IndexInsnNode) k).getIndex());
} else {
} else if (k instanceof Integer) {
code.add(TypeGen.literalToString((Integer) k, arg.getType()));
} else {
throw new JadxRuntimeException("Unexpected key in switch: " + (k != null ? k.getClass() : null));
}
code.add(':');
}
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(TryCatchRegion region, CodeWriter code) throws CodegenException {
TryCatchBlock tryCatchBlock = region.geTryCatchBlock();
code.startLine("try {");
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);
}
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('}');
}
@@ -63,7 +63,7 @@ public class TypeGen {
case OBJECT:
case ARRAY:
if (lit != 0) {
LOG.warn("Wrong object literal: " + lit + " for type: " + type);
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,408 @@
package jadx.core.deobf;
import jadx.api.IJadxArgs;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.SourceFileAttr;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Deobfuscator {
private static final Logger LOG = LoggerFactory.getLogger(Deobfuscator.class);
private static final boolean DEBUG = false;
public static final String CLASS_NAME_SEPARATOR = ".";
public static final String INNER_CLASS_SEPARATOR = "$";
private final IJadxArgs args;
@NotNull
private final List<DexNode> dexNodes;
private final DeobfPresets deobfPresets;
private final Map<ClassInfo, DeobfClsInfo> clsMap = new HashMap<ClassInfo, DeobfClsInfo>();
private final Map<FieldInfo, String> fldMap = new HashMap<FieldInfo, String>();
private final Map<MethodInfo, String> mthMap = new HashMap<MethodInfo, String>();
private final PackageNode rootPackage = new PackageNode("");
private final Set<String> pkgSet = new TreeSet<String>();
private final int maxLength;
private final int minLength;
private int pkgIndex = 0;
private int clsIndex = 0;
private int fldIndex = 0;
private int mthIndex = 0;
public Deobfuscator(IJadxArgs args, @NotNull List<DexNode> dexNodes, File deobfMapFile) {
this.args = args;
this.dexNodes = dexNodes;
this.minLength = args.getDeobfuscationMinLength();
this.maxLength = args.getDeobfuscationMaxLength();
this.deobfPresets = new DeobfPresets(this, deobfMapFile);
}
public void execute() {
if (!args.isDeobfuscationForceSave()) {
deobfPresets.load();
initIndexes();
}
process();
deobfPresets.save(args.isDeobfuscationForceSave());
clear();
}
private void initIndexes() {
pkgIndex = pkgSet.size();
clsIndex = deobfPresets.getClsPresetMap().size();
fldIndex = deobfPresets.getFldPresetMap().size();
mthIndex = deobfPresets.getMthPresetMap().size();
}
private void preProcess() {
for (DexNode dexNode : dexNodes) {
for (ClassNode cls : dexNode.getClasses()) {
doClass(cls);
}
}
}
private void process() {
preProcess();
if (DEBUG) {
dumpAlias();
}
for (DexNode dexNode : dexNodes) {
for (ClassNode cls : dexNode.getClasses()) {
processClass(dexNode, cls);
}
}
}
void clear() {
deobfPresets.clear();
clsMap.clear();
fldMap.clear();
mthMap.clear();
}
private void processClass(DexNode dex, ClassNode cls) {
ClassInfo clsInfo = cls.getClassInfo();
String fullName = getClassFullName(clsInfo);
if (!fullName.equals(clsInfo.getFullName())) {
clsInfo.rename(dex, fullName);
}
for (FieldNode field : cls.getFields()) {
FieldInfo fieldInfo = field.getFieldInfo();
String alias = getFieldAlias(field);
if (alias != null) {
fieldInfo.setAlias(alias);
}
}
for (MethodNode mth : cls.getMethods()) {
MethodInfo methodInfo = mth.getMethodInfo();
String alias = getMethodAlias(mth);
if (alias != null) {
methodInfo.setAlias(alias);
}
}
}
public void addPackagePreset(String origPkgName, String pkgAlias) {
PackageNode pkg = getPackageNode(origPkgName, true);
pkg.setAlias(pkgAlias);
}
/**
* Gets package node for full package name
*
* @param fullPkgName full package name
* @param create if {@code true} then will create all absent objects
* @return package node object or {@code null} if no package found and <b>create</b> set to {@code false}
*/
private PackageNode getPackageNode(String fullPkgName, boolean create) {
if (fullPkgName.isEmpty() || fullPkgName.equals(CLASS_NAME_SEPARATOR)) {
return rootPackage;
}
PackageNode result = rootPackage;
PackageNode parentNode;
do {
String pkgName;
int idx = fullPkgName.indexOf(CLASS_NAME_SEPARATOR);
if (idx > -1) {
pkgName = fullPkgName.substring(0, idx);
fullPkgName = fullPkgName.substring(idx + 1);
} else {
pkgName = fullPkgName;
fullPkgName = "";
}
parentNode = result;
result = result.getInnerPackageByName(pkgName);
if (result == null && create) {
result = new PackageNode(pkgName);
parentNode.addInnerPackage(result);
}
} while (!fullPkgName.isEmpty() && result != null);
return result;
}
String getNameWithoutPackage(ClassInfo clsInfo) {
String prefix;
ClassInfo parentClsInfo = clsInfo.getParentClass();
if (parentClsInfo != null) {
DeobfClsInfo parentDeobfClsInfo = clsMap.get(parentClsInfo);
if (parentDeobfClsInfo != null) {
prefix = parentDeobfClsInfo.makeNameWithoutPkg();
} else {
prefix = getNameWithoutPackage(parentClsInfo);
}
prefix += INNER_CLASS_SEPARATOR;
} else {
prefix = "";
}
return prefix + clsInfo.getShortName();
}
private void doClass(ClassNode cls) {
ClassInfo classInfo = cls.getClassInfo();
String pkgFullName = classInfo.getPackage();
PackageNode pkg = getPackageNode(pkgFullName, true);
doPkg(pkg, pkgFullName);
String alias = deobfPresets.getForCls(classInfo);
if (alias != null) {
clsMap.put(classInfo, new DeobfClsInfo(this, cls, pkg, alias));
return;
}
if (clsMap.containsKey(classInfo)) {
return;
}
if (shouldRename(classInfo.getShortName())) {
makeClsAlias(cls);
}
}
public String getClsAlias(ClassNode cls) {
DeobfClsInfo deobfClsInfo = clsMap.get(cls.getClassInfo());
if (deobfClsInfo != null) {
return deobfClsInfo.getAlias();
}
return makeClsAlias(cls);
}
private String makeClsAlias(ClassNode cls) {
ClassInfo classInfo = cls.getClassInfo();
String alias = getAliasFromSourceFile(cls);
if (alias == null) {
String clsName = classInfo.getShortName();
alias = String.format("C%04d%s", clsIndex++, makeName(clsName));
}
PackageNode pkg = getPackageNode(classInfo.getPackage(), true);
clsMap.put(classInfo, new DeobfClsInfo(this, cls, pkg, alias));
return alias;
}
@Nullable
private String getAliasFromSourceFile(ClassNode cls) {
SourceFileAttr sourceFileAttr = cls.get(AType.SOURCE_FILE);
if (sourceFileAttr == null) {
return null;
}
String name = sourceFileAttr.getFileName();
if (name.endsWith(".java")) {
name = name.substring(0, name.length() - ".java".length());
}
if (NameMapper.isValidIdentifier(name)
&& !NameMapper.isReserved(name)) {
// TODO: check if no class with this name exists or already renamed
cls.remove(AType.SOURCE_FILE);
return name;
}
return null;
}
@Nullable
public String getFieldAlias(FieldNode field) {
FieldInfo fieldInfo = field.getFieldInfo();
String alias = fldMap.get(fieldInfo);
if (alias != null) {
return alias;
}
alias = deobfPresets.getForFld(fieldInfo);
if (alias != null) {
fldMap.put(fieldInfo, alias);
return alias;
}
if (shouldRename(field.getName())) {
return makeFieldAlias(field);
}
return null;
}
@Nullable
public String getMethodAlias(MethodNode mth) {
MethodInfo methodInfo = mth.getMethodInfo();
String alias = mthMap.get(methodInfo);
if (alias != null) {
return alias;
}
alias = deobfPresets.getForMth(methodInfo);
if (alias != null) {
mthMap.put(methodInfo, alias);
return alias;
}
if (shouldRename(mth.getName())) {
return makeMethodAlias(mth);
}
return null;
}
public String makeFieldAlias(FieldNode field) {
String alias = String.format("f%d%s", fldIndex++, makeName(field.getName()));
fldMap.put(field.getFieldInfo(), alias);
return alias;
}
public String makeMethodAlias(MethodNode mth) {
String alias = String.format("m%d%s", mthIndex++, makeName(mth.getName()));
mthMap.put(mth.getMethodInfo(), alias);
return alias;
}
private void doPkg(PackageNode pkg, String fullName) {
if (pkgSet.contains(fullName)) {
return;
}
pkgSet.add(fullName);
// doPkg for all parent packages except root that not hasAliases
PackageNode parentPkg = pkg.getParentPackage();
while (!parentPkg.getName().isEmpty()) {
if (!parentPkg.hasAlias()) {
doPkg(parentPkg, parentPkg.getFullName());
}
parentPkg = parentPkg.getParentPackage();
}
final String pkgName = pkg.getName();
if (!pkg.hasAlias() && shouldRename(pkgName)) {
final String pkgAlias = String.format("p%03d%s", pkgIndex++, makeName(pkgName));
pkg.setAlias(pkgAlias);
}
}
private boolean shouldRename(String s) {
return s.length() > maxLength
|| s.length() < minLength
|| NameMapper.isReserved(s)
|| !NameMapper.isAllCharsPrintable(s);
}
private String makeName(String name) {
if (name.length() > maxLength) {
return "x" + Integer.toHexString(name.hashCode());
}
if (NameMapper.isReserved(name)) {
return name;
}
if (!NameMapper.isAllCharsPrintable(name)) {
return removeInvalidChars(name);
}
return name;
}
private String removeInvalidChars(String name) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < name.length(); i++) {
int ch = name.charAt(i);
if (NameMapper.isPrintableChar(ch)) {
sb.append((char) ch);
}
}
return sb.toString();
}
private void dumpClassAlias(ClassNode cls) {
PackageNode pkg = getPackageNode(cls.getPackage(), false);
if (pkg != null) {
if (!cls.getFullName().equals(getClassFullName(cls))) {
LOG.info("Alias name for class '{}' is '{}'", cls.getFullName(), getClassFullName(cls));
}
} else {
LOG.error("Can't find package node for '{}'", cls.getPackage());
}
}
private void dumpAlias() {
for (DexNode dexNode : dexNodes) {
for (ClassNode cls : dexNode.getClasses()) {
dumpClassAlias(cls);
}
}
}
private String getPackageName(String packageName) {
final PackageNode pkg = getPackageNode(packageName, false);
if (pkg != null) {
return pkg.getFullAlias();
}
return packageName;
}
private String getClassName(ClassInfo clsInfo) {
final DeobfClsInfo deobfClsInfo = clsMap.get(clsInfo);
if (deobfClsInfo != null) {
return deobfClsInfo.makeNameWithoutPkg();
}
return getNameWithoutPackage(clsInfo);
}
private String getClassFullName(ClassNode cls) {
return getClassFullName(cls.getClassInfo());
}
private String getClassFullName(ClassInfo clsInfo) {
final DeobfClsInfo deobfClsInfo = clsMap.get(clsInfo);
if (deobfClsInfo != null) {
return deobfClsInfo.getFullName();
}
return getPackageName(clsInfo.getPackage()) + CLASS_NAME_SEPARATOR + getClassName(clsInfo);
}
public Map<ClassInfo, DeobfClsInfo> getClsMap() {
return clsMap;
}
public Map<FieldInfo, String> getFldMap() {
return fldMap;
}
public Map<MethodInfo, String> getMthMap() {
return mthMap;
}
public PackageNode getRootPackage() {
return rootPackage;
}
}
@@ -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,125 @@
package jadx.core.deobf;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Stack;
public class PackageNode {
private static final char SEPARATOR_CHAR = '.';
private PackageNode parentPackage;
private List<PackageNode> innerPackages = Collections.emptyList();
private final String packageName;
private String packageAlias;
private String cachedPackageFullName;
private String cachedPackageFullAlias;
public PackageNode(String packageName) {
this.packageName = packageName;
this.parentPackage = this;
}
public String getName() {
return packageName;
}
public String getFullName() {
if (cachedPackageFullName == null) {
Stack<PackageNode> pp = getParentPackages();
StringBuilder result = new StringBuilder();
result.append(pp.pop().getName());
while (pp.size() > 0) {
result.append(SEPARATOR_CHAR);
result.append(pp.pop().getName());
}
cachedPackageFullName = result.toString();
}
return cachedPackageFullName;
}
public String getAlias() {
if (packageAlias != null) {
return packageAlias;
}
return packageName;
}
public void setAlias(String alias) {
packageAlias = alias;
}
public boolean hasAlias() {
return packageAlias != null;
}
public String getFullAlias() {
if (cachedPackageFullAlias == null) {
Stack<PackageNode> pp = getParentPackages();
StringBuilder result = new StringBuilder();
result.append(pp.pop().getAlias());
while (pp.size() > 0) {
result.append(SEPARATOR_CHAR);
result.append(pp.pop().getAlias());
}
cachedPackageFullAlias = result.toString();
}
return cachedPackageFullAlias;
}
public PackageNode getParentPackage() {
return parentPackage;
}
public List<PackageNode> getInnerPackages() {
return innerPackages;
}
public void addInnerPackage(PackageNode pkg) {
if (innerPackages.isEmpty()) {
innerPackages = new ArrayList<PackageNode>();
}
innerPackages.add(pkg);
pkg.parentPackage = this;
}
/**
* Gets inner package node by name
*
* @param name inner package name
* @return package node or {@code null}
*/
public PackageNode getInnerPackageByName(String name) {
PackageNode result = null;
for (PackageNode p : innerPackages) {
if (p.getName().equals(name)) {
result = p;
break;
}
}
return result;
}
/**
* Fills stack with parent packages exclude root node
*
* @return stack with parent packages
*/
private Stack<PackageNode> getParentPackages() {
Stack<PackageNode> pp = new Stack<PackageNode>();
PackageNode currentP = this;
PackageNode parentP = currentP.getParentPackage();
while (currentP != parentP) {
pp.push(currentP);
currentP = parentP;
parentP = currentP.getParentPackage();
}
return pp;
}
}
@@ -10,6 +10,7 @@ public enum AFlag {
SYNTHETIC,
RETURN, // block contains only return instruction
ORIG_RETURN,
DECLARE_VAR,
DONT_WRAP,
@@ -18,14 +19,18 @@ public enum AFlag {
DONT_INLINE,
DONT_GENERATE,
SKIP,
REMOVE,
SKIP_FIRST_ARG,
ANONYMOUS_CONSTRUCTOR,
ANONYMOUS_CLASS,
ELSE_IF_CHAIN,
WRAPPED,
ARITH_ONEARG,
FALL_THROUGH,
INCONSISTENT_CODE, // warning about incorrect decompilation
}
@@ -7,6 +7,7 @@ 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;
@@ -21,17 +22,12 @@ 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 int FIELDS_COUNT = 18;
public static final AType<AttrList<JumpInfo>> JUMP = new AType<AttrList<JumpInfo>>();
public static final AType<AttrList<LoopInfo>> LOOP = new AType<AttrList<LoopInfo>>();
@@ -51,4 +47,5 @@ public class AType<T extends IAttribute> {
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>();
}
@@ -24,7 +24,7 @@ public class AttributeStorage {
public AttributeStorage() {
flags = EnumSet.noneOf(AFlag.class);
attributes = new IdentityHashMap<AType<?>, IAttribute>(AType.FIELDS_COUNT);
attributes = new IdentityHashMap<AType<?>, IAttribute>();
}
public void add(AFlag flag) {
@@ -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) {
@@ -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;
}
}
@@ -10,7 +10,7 @@ import java.util.Map;
public class EnumMapAttr implements IAttribute {
public static class KeyValueMap {
private Map<Object, Object> map = new HashMap<Object, Object>();
private final Map<Object, Object> map = new HashMap<Object, Object>();
public Object get(Object key) {
return map.get(key);
@@ -21,7 +21,7 @@ public class EnumMapAttr implements IAttribute {
}
}
private Map<FieldNode, KeyValueMap> fieldsMap = new HashMap<FieldNode, KeyValueMap>();
private final Map<FieldNode, KeyValueMap> fieldsMap = new HashMap<FieldNode, KeyValueMap>();
public KeyValueMap getMap(FieldNode field) {
return fieldsMap.get(field);
@@ -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);
}
}
@@ -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,71 @@ 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 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() {
@@ -159,7 +177,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;
}
@@ -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,30 @@ public final class MethodInfo {
private final List<ArgType> args;
private final ClassInfo declClass;
private final String shortId;
private String alias;
private MethodInfo(DexNode dex, int mthIndex) {
MethodId mthId = dex.getMethodId(mthIndex);
name = dex.getString(mthId.getNameIndex());
alias = name;
declClass = ClassInfo.fromDex(dex, mthId.getDeclaringClassIndex());
ProtoId proto = dex.getProtoId(mthId.getProtoIndex());
retType = dex.getType(proto.getReturnTypeIndex());
args = dex.readParamList(proto.getParametersOffset());
shortId = makeSignature(true);
}
public static MethodInfo fromDex(DexNode dex, int mthIndex) {
MethodInfo mth = dex.getInfoStorage().getMethod(mthIndex);
if (mth != null) {
return mth;
}
mth = new MethodInfo(dex, mthIndex);
return dex.getInfoStorage().putMethod(mthIndex, mth);
}
public String makeSignature(boolean includeRetType) {
StringBuilder signature = new StringBuilder();
signature.append(name);
signature.append('(');
@@ -34,13 +48,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 +97,18 @@ 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);
}
@Override
public int hashCode() {
int result = declClass.hashCode();
@@ -106,16 +129,9 @@ public final class 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
@@ -61,20 +61,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
@@ -17,20 +17,15 @@ public final class ConstClassNode extends InsnNode {
}
@Override
public boolean equals(Object obj) {
public boolean isSame(InsnNode obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof ConstClassNode) || !super.equals(obj)) {
if (!(obj instanceof ConstClassNode) || !super.isSame(obj)) {
return false;
}
ConstClassNode that = (ConstClassNode) obj;
return clsType.equals(that.clsType);
}
@Override
public int hashCode() {
return 31 * super.hashCode() + clsType.hashCode();
ConstClassNode other = (ConstClassNode) obj;
return clsType.equals(other.clsType);
}
@Override
@@ -16,20 +16,15 @@ public final class ConstStringNode extends InsnNode {
}
@Override
public boolean equals(Object obj) {
public boolean isSame(InsnNode obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof ConstStringNode) || !super.equals(obj)) {
if (!(obj instanceof ConstStringNode) || !super.isSame(obj)) {
return false;
}
ConstStringNode that = (ConstStringNode) obj;
return str.equals(that.str);
}
@Override
public int hashCode() {
return 31 * super.hashCode() + str.hashCode();
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;
@@ -71,20 +72,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,15 @@ public class IndexInsnNode extends InsnNode {
}
@Override
public boolean equals(Object obj) {
public boolean isSame(InsnNode obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof IndexInsnNode) || !super.equals(obj)) {
if (!(obj instanceof IndexInsnNode) || !super.isSame(obj)) {
return false;
}
IndexInsnNode that = (IndexInsnNode) obj;
return index == null ? that.index == null : index.equals(that.index);
}
@Override
public int hashCode() {
return 31 * super.hashCode() + (index != null ? index.hashCode() : 0);
IndexInsnNode other = (IndexInsnNode) obj;
return index == null ? other.index == null : index.equals(other.index);
}
@Override
@@ -56,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 {
@@ -67,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:
@@ -551,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:
@@ -636,9 +621,12 @@ public class InsnDecoder {
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) {
@@ -45,23 +45,15 @@ public class InvokeNode extends InsnNode {
}
@Override
public boolean equals(Object obj) {
public boolean isSame(InsnNode obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof InvokeNode) || !super.equals(obj)) {
if (!(obj instanceof InvokeNode) || !super.isSame(obj)) {
return false;
}
InvokeNode that = (InvokeNode) obj;
return type == that.type && mth.equals(that.mth);
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + type.hashCode();
result = 31 * result + mth.hashCode();
return result;
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;
}
}
@@ -4,35 +4,93 @@ 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);
}
public boolean removeArg(RegisterArg arg) {
boolean isRemoved = super.removeArg(arg);
if (isRemoved) {
arg.getSVar().setUsedInPhi(null);
@Override
public boolean removeArg(InsnArg arg) {
if (!(arg instanceof RegisterArg)) {
return false;
}
return isRemoved;
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;
@@ -26,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());
@@ -44,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);
}
@@ -473,29 +464,28 @@ public abstract class ArgType {
public abstract PrimitiveType[] getPossibleTypes();
@Nullable
public static ArgType merge(ArgType a, ArgType b) {
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((ArrayArg) a, b);
return mergeArrays(dex, (ArrayArg) a, b);
} else if (b.isArray()) {
return mergeArrays((ArrayArg) b, a);
return mergeArrays(dex, (ArrayArg) b, a);
}
if (!a.isTypeKnown()) {
if (b.isTypeKnown()) {
@@ -545,7 +535,10 @@ public abstract class ArgType {
if (bObj.equals(Consts.CLASS_OBJECT)) {
return a;
}
String obj = clsp.getCommonAncestor(aObj, bObj);
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()) {
@@ -555,14 +548,14 @@ public abstract class ArgType {
return null;
}
private static ArgType mergeArrays(ArrayArg array, ArgType b) {
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(ea, eb);
ArgType res = merge(dex, ea, eb);
return res == null ? null : array(res);
}
if (b.contains(PrimitiveType.ARRAY)) {
@@ -574,25 +567,25 @@ public abstract class ArgType {
return null;
}
public static boolean isCastNeeded(ArgType from, ArgType to) {
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(ArgType type, ArgType of) {
public static boolean isInstanceOf(DexNode dex, ArgType type, ArgType of) {
if (type.equals(of)) {
return true;
}
if (!type.isObject() || !of.isObject()) {
return false;
}
return clsp.isImplements(type.getObject(), of.getObject());
return dex.root().getClsp().isImplements(type.getObject(), of.getObject());
}
public static ArgType parse(String type) {
@@ -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;
@@ -7,6 +7,7 @@ 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;
@@ -20,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) {
@@ -70,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;
}
@@ -85,7 +88,6 @@ public abstract class InsnArg extends Typed {
}
if (parent == insn) {
LOG.debug("Can't wrap instruction info itself: {}", insn);
Thread.dumpStack();
return null;
}
int i = getArgIndex(parent, this);
@@ -2,11 +2,13 @@ package jadx.core.dex.instructions.args;
import jadx.core.dex.nodes.InsnNode;
import org.jetbrains.annotations.NotNull;
public final class InsnWrapArg extends InsnArg {
private final InsnNode wrappedInsn;
public InsnWrapArg(InsnNode insn) {
public InsnWrapArg(@NotNull InsnNode insn) {
RegisterArg result = insn.getResult();
this.type = result != null ? result.getType() : ArgType.VOID;
this.wrappedInsn = insn;
@@ -27,6 +29,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;
}
@@ -47,7 +47,7 @@ public final class LiteralArg extends InsnArg {
@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;
@@ -44,7 +39,7 @@ public class RegisterArg extends InsnArg implements Named {
return sVar;
}
void setSVar(SSAVar sVar) {
void setSVar(@NotNull SSAVar sVar) {
this.sVar = sVar;
}
@@ -97,28 +92,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
@@ -162,7 +136,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,5 +1,7 @@
package jadx.core.dex.instructions.args;
import org.jetbrains.annotations.NotNull;
public class TypeImmutableArg extends RegisterArg {
private boolean isThis;
@@ -36,7 +38,7 @@ public class TypeImmutableArg extends RegisterArg {
}
@Override
void setSVar(SSAVar sVar) {
void setSVar(@NotNull SSAVar sVar) {
if (isThis) {
sVar.setName("this");
}
@@ -55,8 +57,7 @@ public class TypeImmutableArg extends RegisterArg {
if (!super.equals(obj)) {
return false;
}
TypeImmutableArg that = (TypeImmutableArg) obj;
return isThis == that.isThis;
return isThis == ((TypeImmutableArg) obj).isThis;
}
@Override
@@ -1,5 +1,7 @@
package jadx.core.dex.instructions.args;
import jadx.core.dex.nodes.DexNode;
public abstract class Typed {
protected ArgType type;
@@ -16,8 +18,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 +27,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());
}
}
@@ -92,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
@@ -60,22 +60,17 @@ public final class TernaryInsn extends InsnNode {
}
@Override
public boolean equals(Object obj) {
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"
@@ -3,7 +3,9 @@ package jadx.core.dex.nodes;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.AttrNode;
import jadx.core.dex.attributes.nodes.IgnoreEdgeAttr;
import jadx.core.dex.attributes.nodes.LoopInfo;
import jadx.core.utils.EmptyBitSet;
import jadx.core.utils.InsnUtils;
import java.util.ArrayList;
@@ -23,7 +25,7 @@ public class BlockNode extends AttrNode implements IBlock {
private List<BlockNode> cleanSuccessors;
// all dominators
private BitSet doms;
private BitSet doms = EmptyBitSet.EMPTY;
// dominance frontier
private BitSet domFrontier;
// immediate dominator
@@ -99,6 +101,10 @@ public class BlockNode extends AttrNode implements IBlock {
toRemove.add(loop.getStart());
}
}
IgnoreEdgeAttr ignoreEdgeAttr = block.get(AType.IGNORE_EDGE);
if (ignoreEdgeAttr != null) {
toRemove.addAll(ignoreEdgeAttr.getBlocks());
}
if (toRemove.isEmpty()) {
return sucList;
}
@@ -174,7 +180,7 @@ public class BlockNode extends AttrNode implements IBlock {
@Override
public int hashCode() {
return id; // TODO id can change during reindex
return startOffset;
}
@Override
@@ -182,23 +188,11 @@ public class BlockNode extends AttrNode implements IBlock {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (hashCode() != obj.hashCode()) {
return false;
}
if (!(obj instanceof BlockNode)) {
return false;
}
BlockNode other = (BlockNode) obj;
if (id != other.id) {
return false;
}
if (startOffset != other.startOffset) {
return false;
}
return true;
return id == other.id && startOffset == other.startOffset;
}
@Override
@@ -24,10 +24,14 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -35,6 +39,7 @@ import com.android.dex.ClassData;
import com.android.dex.ClassData.Field;
import com.android.dex.ClassData.Method;
import com.android.dex.ClassDef;
import com.android.dx.rop.code.AccessFlags;
public class ClassNode extends LineAttrNode implements ILoadable {
private static final Logger LOG = LoggerFactory.getLogger(ClassNode.class);
@@ -42,8 +47,8 @@ public class ClassNode extends LineAttrNode implements ILoadable {
private final DexNode dex;
private final ClassInfo clsInfo;
private final AccessInfo accessFlags;
private ClassInfo superClass;
private List<ClassInfo> interfaces;
private ArgType superClass;
private List<ArgType> interfaces;
private Map<ArgType, List<ArgType>> genericMap;
private final List<MethodNode> methods;
@@ -56,6 +61,9 @@ public class ClassNode extends LineAttrNode implements ILoadable {
// store parent for inner classes or 'this' otherwise
private ClassNode parentClass;
private ProcessState state = ProcessState.NOT_LOADED;
private final Set<ClassNode> dependencies = new HashSet<ClassNode>();
public ClassNode(DexNode dex, ClassDef cls) throws DecodeException {
this.dex = dex;
this.clsInfo = ClassInfo.fromDex(dex, cls.getTypeIndex());
@@ -63,11 +71,11 @@ public class ClassNode extends LineAttrNode implements ILoadable {
if (cls.getSupertypeIndex() == DexNode.NO_INDEX) {
this.superClass = null;
} else {
this.superClass = ClassInfo.fromDex(dex, cls.getSupertypeIndex());
this.superClass = dex.getType(cls.getSupertypeIndex());
}
this.interfaces = new ArrayList<ClassInfo>(cls.getInterfaces().length);
this.interfaces = new ArrayList<ArgType>(cls.getInterfaces().length);
for (short interfaceIdx : cls.getInterfaces()) {
this.interfaces.add(ClassInfo.fromDex(dex, interfaceIdx));
this.interfaces.add(dex.getType(interfaceIdx));
}
if (cls.getClassDataOffset() != 0) {
ClassData clsData = dex.readClassData(cls);
@@ -104,11 +112,7 @@ public class ClassNode extends LineAttrNode implements ILoadable {
int sfIdx = cls.getSourceFileIndex();
if (sfIdx != DexNode.NO_INDEX) {
String fileName = dex.getString(sfIdx);
if (!this.getFullName().contains(fileName.replace(".java", ""))
&& !fileName.equals("SourceFile")) {
this.addAttr(new SourceFileAttr(fileName));
LOG.debug("Class '{}' compiled from '{}'", this, fileName);
}
addSourceFilenameAttr(fileName);
}
// restore original access flags from dalvik annotation if present
@@ -122,16 +126,27 @@ public class ClassNode extends LineAttrNode implements ILoadable {
this.accessFlags = new AccessInfo(accFlagsValue, AFType.CLASS);
} catch (Exception e) {
throw new DecodeException("Error decode class: " + getFullName(), e);
throw new DecodeException("Error decode class: " + clsInfo, e);
}
}
// empty synthetic class
public ClassNode(DexNode dex, ClassInfo clsInfo) {
this.dex = dex;
this.clsInfo = clsInfo;
this.interfaces = Collections.emptyList();
this.methods = Collections.emptyList();
this.fields = Collections.emptyList();
this.accessFlags = new AccessInfo(AccessFlags.ACC_PUBLIC | AccessFlags.ACC_SYNTHETIC, AFType.CLASS);
this.parentClass = this;
}
private void loadAnnotations(ClassDef cls) {
int offset = cls.getAnnotationsOffset();
if (offset != 0) {
try {
new AnnotationsParser(this).parse(offset);
} catch (DecodeException e) {
} catch (Exception e) {
LOG.error("Error parsing annotations in {}", this, e);
}
}
@@ -173,12 +188,12 @@ public class ClassNode extends LineAttrNode implements ILoadable {
// parse class generic map
genericMap = sp.consumeGenericMap();
// parse super class signature
superClass = ClassInfo.fromType(sp.consumeType());
superClass = sp.consumeType();
// parse interfaces signatures
for (int i = 0; i < interfaces.size(); i++) {
ArgType type = sp.consumeType();
if (type != null) {
interfaces.set(i, ClassInfo.fromType(type));
interfaces.set(i, type);
} else {
break;
}
@@ -204,13 +219,43 @@ public class ClassNode extends LineAttrNode implements ILoadable {
}
}
private void addSourceFilenameAttr(String fileName) {
if (fileName == null) {
return;
}
if (fileName.endsWith(".java")) {
fileName = fileName.substring(0, fileName.length() - 5);
}
if (fileName.isEmpty()
|| fileName.equals("SourceFile")
|| fileName.equals("\"")) {
return;
}
if (clsInfo != null) {
String name = clsInfo.getShortName();
if (fileName.equals(name)) {
return;
}
if (fileName.contains("$")
&& fileName.endsWith("$" + name)) {
return;
}
ClassInfo parentClass = clsInfo.getTopParentClass();
if (parentClass != null && fileName.equals(parentClass.getShortName())) {
return;
}
}
this.addAttr(new SourceFileAttr(fileName));
LOG.debug("Class '{}' compiled from '{}'", this, fileName);
}
@Override
public void load() {
for (MethodNode mth : getMethods()) {
try {
mth.load();
} catch (DecodeException e) {
LOG.error("Method load error", e);
} catch (Exception e) {
LOG.error("Method load error: {}", mth, e);
mth.addAttr(new JadxErrorAttr(e));
}
}
@@ -229,11 +274,12 @@ public class ClassNode extends LineAttrNode implements ILoadable {
}
}
public ClassInfo getSuperClass() {
@Nullable
public ArgType getSuperClass() {
return superClass;
}
public List<ClassInfo> getInterfaces() {
public List<ArgType> getInterfaces() {
return interfaces;
}
@@ -260,12 +306,24 @@ public class ClassNode extends LineAttrNode implements ILoadable {
field = cn.constFields.get(obj);
}
while (field == null
&& (cn.clsInfo.getParentClass() != null)
&& cn.clsInfo.getParentClass() != null
&& (cn = dex.resolveClass(cn.clsInfo.getParentClass())) != null);
if (field == null && searchGlobal) {
field = dex.getConstFields().get(obj);
}
if (obj instanceof Integer) {
String str = dex.root().getResourcesNames().get(obj);
if (str != null) {
ResRefField resField = new ResRefField(dex, str.replace('/', '.'));
if (field == null) {
return resField;
}
if (!field.getName().equals(resField.getName())) {
field = resField;
}
}
}
return field;
}
@@ -279,37 +337,39 @@ public class ClassNode extends LineAttrNode implements ILoadable {
case BOOLEAN:
return getConstField(literal == 1, false);
case CHAR:
return getConstField((char) literal, Math.abs(literal) > 1);
return getConstField((char) literal, Math.abs(literal) > 10);
case BYTE:
return getConstField((byte) literal, Math.abs(literal) > 1);
return getConstField((byte) literal, Math.abs(literal) > 10);
case SHORT:
return getConstField((short) literal, Math.abs(literal) > 1);
return getConstField((short) literal, Math.abs(literal) > 100);
case INT:
return getConstField((int) literal, Math.abs(literal) > 1);
return getConstField((int) literal, Math.abs(literal) > 100);
case LONG:
return getConstField(literal, Math.abs(literal) > 1);
return getConstField(literal, Math.abs(literal) > 1000);
case FLOAT:
return getConstField(Float.intBitsToFloat((int) literal), true);
float f = Float.intBitsToFloat((int) literal);
return getConstField(f, f != 0.0);
case DOUBLE:
return getConstField(Double.longBitsToDouble(literal), true);
double d = Double.longBitsToDouble(literal);
return getConstField(d, d != 0);
}
return null;
}
public FieldNode searchFieldById(int id) {
String name = FieldInfo.getNameById(dex, id);
return searchField(FieldInfo.fromDex(dex, id));
}
public FieldNode searchField(FieldInfo field) {
for (FieldNode f : fields) {
if (f.getName().equals(name)) {
if (f.getFieldInfo().equals(field)) {
return f;
}
}
return null;
}
public FieldNode searchField(FieldInfo field) {
return searchFieldByName(field.getName());
}
@TestOnly
public FieldNode searchFieldByName(String name) {
for (FieldNode f : fields) {
if (f.getName().equals(name)) {
@@ -371,12 +431,14 @@ public class ClassNode extends LineAttrNode implements ILoadable {
}
public boolean isEnum() {
return getAccessFlags().isEnum() && getSuperClass().getFullName().equals(Consts.CLASS_ENUM);
return getAccessFlags().isEnum()
&& getSuperClass() != null
&& getSuperClass().getObject().equals(ArgType.ENUM.getObject());
}
public boolean isAnonymous() {
return clsInfo.isInner()
&& getShortName().startsWith(Consts.ANONYMOUS_CLASS_PREFIX)
&& clsInfo.getAlias().getShortName().startsWith(Consts.ANONYMOUS_CLASS_PREFIX)
&& getDefaultConstructor() != null;
}
@@ -397,24 +459,34 @@ public class ClassNode extends LineAttrNode implements ILoadable {
return dex;
}
public String getRawName() {
return clsInfo.getRawName();
}
/**
* Internal class info (don't use in code generation and external api).
*/
public ClassInfo getClassInfo() {
return clsInfo;
}
/**
* Class info for external usage (code generation and external api).
*/
public ClassInfo getAlias() {
return clsInfo.getAlias();
}
public String getShortName() {
return clsInfo.getShortName();
return clsInfo.getAlias().getShortName();
}
public String getFullName() {
return clsInfo.getFullName();
return clsInfo.getAlias().getFullName();
}
public String getPackage() {
return clsInfo.getPackage();
}
public String getRawName() {
return clsInfo.getRawName();
return clsInfo.getAlias().getPackage();
}
public void setCode(CodeWriter code) {
@@ -425,8 +497,20 @@ public class ClassNode extends LineAttrNode implements ILoadable {
return code;
}
public ProcessState getState() {
return state;
}
public void setState(ProcessState state) {
this.state = state;
}
public Set<ClassNode> getDependencies() {
return dependencies;
}
@Override
public String toString() {
return getFullName();
return clsInfo.getFullName();
}
}
@@ -2,6 +2,7 @@ package jadx.core.dex.nodes;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.InfoStorage;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.utils.exceptions.DecodeException;
@@ -13,6 +14,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import com.android.dex.ClassData;
@@ -32,18 +34,47 @@ public class DexNode {
private final RootNode root;
private final Dex dexBuf;
private final InputFile file;
private final List<ClassNode> classes = new ArrayList<ClassNode>();
private final Map<ClassInfo, ClassNode> clsMap = new HashMap<ClassInfo, ClassNode>();
private final Map<Object, FieldNode> constFields = new HashMap<Object, FieldNode>();
private final InfoStorage infoStorage = new InfoStorage();
public DexNode(RootNode root, InputFile input) {
this.root = root;
this.file = input;
this.dexBuf = input.getDexBuffer();
}
public void loadClasses() throws DecodeException {
for (ClassDef cls : dexBuf.classDefs()) {
classes.add(new ClassNode(this, cls));
ClassNode clsNode = new ClassNode(this, cls);
classes.add(clsNode);
clsMap.put(clsNode.getClassInfo(), clsNode);
}
}
void initInnerClasses() {
// move inner classes
List<ClassNode> inner = new ArrayList<ClassNode>();
for (ClassNode cls : classes) {
if (cls.getClassInfo().isInner()) {
inner.add(cls);
}
}
for (ClassNode cls : inner) {
ClassInfo clsInfo = cls.getClassInfo();
ClassNode parent = resolveClass(clsInfo.getParentClass());
if (parent == null) {
clsMap.remove(clsInfo);
clsInfo.notInner(cls.dex());
clsMap.put(clsInfo, cls);
} else {
parent.addInnerClass(cls);
}
}
}
@@ -53,11 +84,19 @@ public class DexNode {
@Nullable
public ClassNode resolveClass(ClassInfo clsInfo) {
return root.resolveClass(clsInfo);
return clsMap.get(clsInfo);
}
@Nullable
public MethodNode resolveMethod(MethodInfo mth) {
public ClassNode resolveClass(@NotNull ArgType type) {
if (type.isGeneric()) {
type = ArgType.object(type.getObject());
}
return resolveClass(ClassInfo.fromType(this, type));
}
@Nullable
public MethodNode resolveMethod(@NotNull MethodInfo mth) {
ClassNode cls = resolveClass(mth.getDeclClass());
if (cls != null) {
return cls.searchMethod(mth);
@@ -65,6 +104,48 @@ public class DexNode {
return null;
}
/**
* Search method in class hierarchy.
*/
@Nullable
public MethodNode deepResolveMethod(@NotNull MethodInfo mth) {
ClassNode cls = resolveClass(mth.getDeclClass());
if (cls == null) {
return null;
}
return deepResolveMethod(cls, mth.makeSignature(false));
}
@Nullable
private MethodNode deepResolveMethod(@NotNull ClassNode cls, String signature) {
for (MethodNode m : cls.getMethods()) {
if (m.getMethodInfo().getShortId().startsWith(signature)) {
return m;
}
}
MethodNode found;
ArgType superClass = cls.getSuperClass();
if (superClass != null) {
ClassNode superNode = resolveClass(superClass);
if (superNode != null) {
found = deepResolveMethod(superNode, signature);
if (found != null) {
return found;
}
}
}
for (ArgType iFaceType : cls.getInterfaces()) {
ClassNode iFaceNode = resolveClass(iFaceType);
if (iFaceNode != null) {
found = deepResolveMethod(iFaceNode, signature);
if (found != null) {
return found;
}
}
}
return null;
}
@Nullable
public FieldNode resolveField(FieldInfo field) {
ClassNode cls = resolveClass(field.getDeclClass());
@@ -78,6 +159,14 @@ public class DexNode {
return constFields;
}
public InfoStorage getInfoStorage() {
return infoStorage;
}
public InputFile getInputFile() {
return file;
}
// DexBuffer wrappers
public String getString(int index) {
@@ -23,6 +23,13 @@ public class FieldNode extends LineAttrNode {
this.accFlags = new AccessInfo(field.getAccessFlags(), AFType.FIELD);
}
public FieldNode(ClassNode cls, FieldInfo fieldInfo, int accessFlags) {
this.parent = cls;
this.fieldInfo = fieldInfo;
this.type = fieldInfo.getType();
this.accFlags = new AccessInfo(accessFlags, AFType.FIELD);
}
public FieldInfo getFieldInfo() {
return fieldInfo;
}
@@ -35,6 +42,10 @@ public class FieldNode extends LineAttrNode {
return fieldInfo.getName();
}
public String getAlias() {
return fieldInfo.getAlias();
}
public ArgType getType() {
return type;
}
@@ -0,0 +1,13 @@
package jadx.core.dex.nodes;
import java.util.List;
public interface IBranchRegion extends IRegion {
/**
* Return list of branches in this region.
* NOTE: Contains 'null' elements for indicate empty branches.
*/
List<IContainer> getBranches();
}
@@ -22,7 +22,6 @@ public class InsnNode extends LineAttrNode {
private RegisterArg result;
private final List<InsnArg> arguments;
protected int offset;
protected int insnHashCode = super.hashCode();
public InsnNode(InsnType type, int argsCount) {
this.insnType = type;
@@ -75,7 +74,8 @@ public class InsnNode extends LineAttrNode {
public boolean containsArg(RegisterArg arg) {
for (InsnArg a : arguments) {
if (a == arg || (a.isRegister() && ((RegisterArg) a).getRegNum() == arg.getRegNum())) {
if (a == arg
|| a.isRegister() && ((RegisterArg) a).getRegNum() == arg.getRegNum()) {
return true;
}
}
@@ -112,6 +112,10 @@ public class InsnNode extends LineAttrNode {
for (int i = 0; i < count; i++) {
if (arg == arguments.get(i)) {
arguments.remove(i);
if (arg instanceof RegisterArg) {
RegisterArg reg = (RegisterArg) arg;
reg.getSVar().removeUse(reg);
}
return true;
}
}
@@ -197,39 +201,31 @@ public class InsnNode extends LineAttrNode {
+ Utils.listToString(arguments);
}
public void setInsnHashCode(int insnHashCode) {
this.insnHashCode = insnHashCode;
/**
* Compare instruction only by identity.
*/
@Override
public final int hashCode() {
return super.hashCode();
}
/**
* Compare instruction only by identity.
*/
@Override
public int hashCode() {
return insnHashCode;
public final boolean equals(Object obj) {
return super.equals(obj);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
/**
* 'Soft' equals, don't compare arguments, only instruction specific parameters.
*/
public boolean isSame(InsnNode other) {
if (this == other) {
return true;
}
if (obj == null) {
return false;
}
if (hashCode() != obj.hashCode()) {
return false;
}
if (!(obj instanceof InsnNode)) {
return false;
}
InsnNode other = (InsnNode) obj;
if (insnType != other.insnType) {
return false;
}
if (arguments.size() != other.arguments.size()) {
return false;
}
// TODO !!! finish equals
return true;
return insnType == other.insnType
&& arguments.size() == other.arguments.size();
}
}
@@ -75,8 +75,8 @@ public class MethodNode extends LineAttrNode implements ILoadable {
this.mthInfo = MethodInfo.fromDex(classNode.dex(), mthData.getMethodIndex());
this.parentClass = classNode;
this.accFlags = new AccessInfo(mthData.getAccessFlags(), AFType.METHOD);
this.noCode = (mthData.getCodeOffset() == 0);
this.methodData = (noCode ? null : mthData);
this.noCode = mthData.getCodeOffset() == 0;
this.methodData = noCode ? null : mthData;
}
@Override
@@ -114,6 +114,28 @@ public class MethodNode extends LineAttrNode implements ILoadable {
}
}
public void checkInstructions() {
List<RegisterArg> list = new ArrayList<RegisterArg>();
for (InsnNode insnNode : instructions) {
if (insnNode == null) {
continue;
}
list.clear();
RegisterArg resultArg = insnNode.getResult();
if (resultArg != null) {
list.add(resultArg);
}
insnNode.getRegisterArgs(list);
int argsCount = list.size();
for (int i = 0; i < argsCount; i++) {
if (list.get(i).getRegNum() >= regsCount) {
throw new JadxRuntimeException("Incorrect register number in instruction: " + insnNode
+ ", expected to be less than " + regsCount);
}
}
}
}
private void initMethodTypes() {
if (!parseSignature()) {
retType = mthInfo.getReturnType();
@@ -349,6 +371,10 @@ public class MethodNode extends LineAttrNode implements ILoadable {
return mthInfo.getName();
}
public String getAlias() {
return mthInfo.getAlias();
}
public ClassNode getParentClass() {
return parentClass;
}
@@ -507,7 +533,7 @@ public class MethodNode extends LineAttrNode implements ILoadable {
defaultArgCount = 1;
}
}
result = (argsList == null) || (argsList.size() == defaultArgCount);
result = argsList == null || argsList.size() == defaultArgCount;
}
return result;
}
@@ -577,7 +603,7 @@ public class MethodNode extends LineAttrNode implements ILoadable {
@Override
public String toString() {
return parentClass.getFullName() + "." + mthInfo.getName()
return parentClass + "." + mthInfo.getName()
+ "(" + Utils.listToString(mthInfo.getArgumentsTypes()) + "):"
+ retType;
}
@@ -0,0 +1,9 @@
package jadx.core.dex.nodes;
public enum ProcessState {
NOT_LOADED,
STARTED,
PROCESSED,
GENERATED,
UNLOADED
}
@@ -0,0 +1,15 @@
package jadx.core.dex.nodes;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.args.ArgType;
import com.android.dx.rop.code.AccessFlags;
public class ResRefField extends FieldNode {
public ResRefField(DexNode dex, String str) {
super(dex.root().getAppResClass(),
FieldInfo.from(dex, dex.root().getAppResClass().getClassInfo(), str, ArgType.INT),
AccessFlags.ACC_PUBLIC);
}
}
@@ -1,22 +1,45 @@
package jadx.core.dex.nodes;
import jadx.api.IJadxArgs;
import jadx.api.ResourceFile;
import jadx.api.ResourceType;
import jadx.api.ResourcesLoader;
import jadx.core.clsp.ClspGraph;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.files.InputFile;
import jadx.core.xmlgen.ResTableParser;
import jadx.core.xmlgen.ResourceStorage;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RootNode {
private final Map<String, ClassNode> names = new HashMap<String, ClassNode>();
private static final Logger LOG = LoggerFactory.getLogger(RootNode.class);
private final ErrorsCounter errorsCounter = new ErrorsCounter();
private final IJadxArgs args;
private List<DexNode> dexNodes;
private Map<Integer, String> resourcesNames = new HashMap<Integer, String>();
@Nullable
private String appPackage;
private ClassNode appResClass;
private ClspGraph clsp;
public RootNode(IJadxArgs args) {
this.args = args;
}
public void load(List<InputFile> dexFiles) throws DecodeException {
dexNodes = new ArrayList<DexNode>(dexFiles.size());
@@ -32,60 +55,93 @@ public class RootNode {
for (DexNode dexNode : dexNodes) {
dexNode.loadClasses();
}
initInnerClasses();
}
List<ClassNode> classes = new ArrayList<ClassNode>();
for (DexNode dexNode : dexNodes) {
for (ClassNode cls : dexNode.getClasses()) {
names.put(cls.getFullName(), cls);
public void loadResources(List<ResourceFile> resources) {
ResourceFile arsc = null;
for (ResourceFile rf : resources) {
if (rf.getType() == ResourceType.ARSC) {
arsc = rf;
break;
}
classes.addAll(dexNode.getClasses());
}
if (arsc == null) {
LOG.debug("'.arsc' file not found");
return;
}
final ResTableParser parser = new ResTableParser();
try {
ResourcesLoader.decodeStream(arsc, new ResourcesLoader.ResourceDecoder() {
@Override
public Object decode(long size, InputStream is) throws IOException {
parser.decode(is);
return null;
}
});
} catch (JadxException e) {
LOG.error("Failed to parse '.arsc' file", e);
return;
}
ResourceStorage resStorage = parser.getResStorage();
resourcesNames = resStorage.getResourcesNames();
appPackage = resStorage.getAppPackage();
}
public void initAppResClass() {
ClassNode resCls;
if (appPackage == null) {
appResClass = makeClass("R");
return;
}
String fullName = appPackage + ".R";
resCls = searchClassByName(fullName);
if (resCls != null) {
appResClass = resCls;
} else {
appResClass = makeClass(fullName);
}
}
private ClassNode makeClass(String clsName) {
DexNode firstDex = dexNodes.get(0);
ClassInfo r = ClassInfo.fromName(firstDex, clsName);
return new ClassNode(firstDex, r);
}
public void initClassPath() throws DecodeException {
try {
initClassPath(classes);
if (this.clsp == null) {
ClspGraph clsp = new ClspGraph();
clsp.load();
List<ClassNode> classes = new ArrayList<ClassNode>();
for (DexNode dexNode : dexNodes) {
classes.addAll(dexNode.getClasses());
}
clsp.addApp(classes);
this.clsp = clsp;
}
} catch (IOException e) {
throw new DecodeException("Error loading classpath", e);
}
initInnerClasses(classes);
}
private static void initClassPath(List<ClassNode> classes) throws IOException, DecodeException {
if (!ArgType.isClspSet()) {
ClspGraph clsp = new ClspGraph();
clsp.load();
clsp.addApp(classes);
ArgType.setClsp(clsp);
}
}
private void initInnerClasses(List<ClassNode> classes) {
// move inner classes
List<ClassNode> inner = new ArrayList<ClassNode>();
for (ClassNode cls : classes) {
if (cls.getClassInfo().isInner()) {
inner.add(cls);
}
}
for (ClassNode cls : inner) {
ClassNode parent = resolveClass(cls.getClassInfo().getParentClass());
if (parent == null) {
names.remove(cls.getFullName());
cls.getClassInfo().notInner();
names.put(cls.getFullName(), cls);
} else {
parent.addInnerClass(cls);
}
private void initInnerClasses() {
for (DexNode dexNode : dexNodes) {
dexNode.initInnerClasses();
}
}
public List<ClassNode> getClasses(boolean includeInner) {
List<ClassNode> classes = new ArrayList<ClassNode>();
for (DexNode dexNode : dexNodes) {
for (ClassNode cls : dexNode.getClasses()) {
if (includeInner) {
classes.add(cls);
} else {
for (DexNode dex : dexNodes) {
if (includeInner) {
classes.addAll(dex.getClasses());
} else {
for (ClassNode cls : dex.getClasses()) {
if (!cls.getClassInfo().isInner()) {
classes.add(cls);
}
@@ -96,15 +152,42 @@ public class RootNode {
}
public ClassNode searchClassByName(String fullName) {
return names.get(fullName);
for (DexNode dexNode : dexNodes) {
ClassInfo clsInfo = ClassInfo.fromName(dexNode, fullName);
ClassNode cls = dexNode.resolveClass(clsInfo);
if (cls != null) {
return cls;
}
}
return null;
}
public ClassNode resolveClass(ClassInfo cls) {
String fullName = cls.getFullName();
return searchClassByName(fullName);
public List<DexNode> getDexNodes() {
return dexNodes;
}
public ClspGraph getClsp() {
return clsp;
}
public ErrorsCounter getErrorsCounter() {
return errorsCounter;
}
public Map<Integer, String> getResourcesNames() {
return resourcesNames;
}
@Nullable
public String getAppPackage() {
return appPackage;
}
public ClassNode getAppResClass() {
return appResClass;
}
public IJadxArgs getArgs() {
return args;
}
}
@@ -71,8 +71,14 @@ public class AnnotationsParser {
}
private AnnotationsList readAnnotationSet(int offset) throws DecodeException {
if (offset == 0) {
return AnnotationsList.EMPTY;
}
Section section = dex.openSection(offset);
int size = section.readInt();
if (size == 0) {
return AnnotationsList.EMPTY;
}
List<Annotation> list = new ArrayList<Annotation>(size);
for (int i = 0; i < size; i++) {
Section anSection = dex.openSection(section.readInt());
@@ -86,7 +92,8 @@ public class AnnotationsParser {
EncValueParser parser = new EncValueParser(dex, s);
Visibility visibility = null;
if (readVisibility) {
visibility = VISIBILITIES[s.readByte()];
byte v = s.readByte();
visibility = VISIBILITIES[v];
}
int typeIndex = s.readUleb128();
int size = s.readUleb128();
@@ -149,7 +149,7 @@ public class DebugInfoParser {
int adjustedOpcode = c - DBG_FIRST_SPECIAL;
int addrInc = adjustedOpcode / DBG_LINE_RANGE;
addr = addrChange(addr, addrInc, line);
line += DBG_LINE_BASE + (adjustedOpcode % DBG_LINE_RANGE);
line += DBG_LINE_BASE + adjustedOpcode % DBG_LINE_RANGE;
setLine(addr, line);
} else {
throw new DecodeException("Unknown debug insn code: " + c);
@@ -212,6 +212,16 @@ public class DebugInfoParser {
prev.end(addr, line);
setVar(prev);
}
InsnArg activeReg = activeRegisters[var.getRegNum()];
if (activeReg instanceof RegisterArg) {
SSAVar ssaVar = ((RegisterArg) activeReg).getSVar();
if (ssaVar != null && ssaVar.getStartAddr() != -1) {
InsnNode parentInsn = ssaVar.getAssign().getParentInsn();
if (parentInsn != null && parentInsn.getOffset() >= 0) {
addr = parentInsn.getOffset();
}
}
}
var.start(addr, line);
locals[regNum] = var;
}
@@ -237,31 +247,32 @@ public class DebugInfoParser {
}
private static void merge(InsnArg arg, LocalVar var) {
if (arg != null && arg.isRegister()) {
RegisterArg reg = (RegisterArg) arg;
if (var.getRegNum() == reg.getRegNum()) {
SSAVar ssaVar = reg.getSVar();
if (arg == null || !arg.isRegister()) {
return;
}
RegisterArg reg = (RegisterArg) arg;
if (var.getRegNum() != reg.getRegNum()) {
return;
}
boolean mergeRequired = false;
boolean mergeRequired = false;
SSAVar ssaVar = reg.getSVar();
if (ssaVar != null) {
int ssaEnd = ssaVar.getEndAddr();
int ssaStart = ssaVar.getStartAddr();
int localStart = var.getStartAddr();
int localEnd = var.getEndAddr();
if (ssaVar != null) {
int ssaEnd = ssaVar.getEndAddr();
int ssaStart = ssaVar.getStartAddr();
int localStart = var.getStartAddr();
int localEnd = var.getEndAddr();
boolean isIntersected = !((localEnd < ssaStart) || (ssaEnd < localStart));
if (isIntersected && (ssaEnd <= localEnd)) {
mergeRequired = true;
}
} else {
mergeRequired = true;
}
if (mergeRequired) {
reg.mergeDebugInfo(var.getType(), var.getName());
}
boolean isIntersected = !(localEnd < ssaStart || ssaEnd < localStart);
if (isIntersected && ssaEnd <= localEnd) {
mergeRequired = true;
}
} else {
mergeRequired = true;
}
if (mergeRequired) {
reg.mergeDebugInfo(var.getType(), var.getName());
}
}
}
@@ -1,6 +1,7 @@
package jadx.core.dex.regions;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.IBranchRegion;
import jadx.core.dex.nodes.IContainer;
import jadx.core.dex.nodes.IRegion;
@@ -8,7 +9,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public final class SwitchRegion extends AbstractRegion {
public final class SwitchRegion extends AbstractRegion implements IBranchRegion {
private final BlockNode header;
@@ -59,6 +60,14 @@ public final class SwitchRegion extends AbstractRegion {
return Collections.unmodifiableList(all);
}
@Override
public List<IContainer> getBranches() {
List<IContainer> branches = new ArrayList<IContainer>(cases.size() + 1);
branches.addAll(cases);
branches.add(defCase);
return Collections.unmodifiableList(branches);
}
@Override
public String baseString() {
return header.baseString();
@@ -1,5 +1,6 @@
package jadx.core.dex.regions;
import jadx.core.dex.nodes.IBranchRegion;
import jadx.core.dex.nodes.IContainer;
import jadx.core.dex.nodes.IRegion;
import jadx.core.dex.trycatch.ExceptionHandler;
@@ -8,12 +9,15 @@ import jadx.core.utils.Utils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public final class TryCatchRegion extends AbstractRegion {
public final class TryCatchRegion extends AbstractRegion implements IBranchRegion {
private final IContainer tryRegion;
private List<IContainer> catchRegions = Collections.emptyList();
private Map<ExceptionHandler, IContainer> catchRegions = Collections.emptyMap();
private IContainer finallyRegion;
private TryCatchBlock tryCatchBlock;
public TryCatchRegion(IRegion parent, IContainer tryRegion) {
@@ -21,34 +25,58 @@ public final class TryCatchRegion extends AbstractRegion {
this.tryRegion = tryRegion;
}
public void setTryCatchBlock(TryCatchBlock tryCatchBlock) {
this.tryCatchBlock = tryCatchBlock;
int count = tryCatchBlock.getHandlersCount();
this.catchRegions = new LinkedHashMap<ExceptionHandler, IContainer>(count);
for (ExceptionHandler handler : tryCatchBlock.getHandlers()) {
IContainer handlerRegion = handler.getHandlerRegion();
if (handlerRegion != null) {
if (handler.isFinally()) {
finallyRegion = handlerRegion;
} else {
catchRegions.put(handler, handlerRegion);
}
}
}
}
public IContainer getTryRegion() {
return tryRegion;
}
public List<IContainer> getCatchRegions() {
public Map<ExceptionHandler, IContainer> getCatchRegions() {
return catchRegions;
}
public TryCatchBlock geTryCatchBlock() {
public TryCatchBlock getTryCatchBlock() {
return tryCatchBlock;
}
public void setTryCatchBlock(TryCatchBlock tryCatchBlock) {
this.tryCatchBlock = tryCatchBlock;
this.catchRegions = new ArrayList<IContainer>(tryCatchBlock.getHandlersCount());
for (ExceptionHandler handler : tryCatchBlock.getHandlers()) {
catchRegions.add(handler.getHandlerRegion());
}
public IContainer getFinallyRegion() {
return finallyRegion;
}
public void setFinallyRegion(IContainer finallyRegion) {
this.finallyRegion = finallyRegion;
}
@Override
public List<IContainer> getSubBlocks() {
List<IContainer> all = new ArrayList<IContainer>(1 + catchRegions.size());
List<IContainer> all = new ArrayList<IContainer>(2 + catchRegions.size());
all.add(tryRegion);
all.addAll(catchRegions);
all.addAll(catchRegions.values());
if (finallyRegion != null) {
all.add(finallyRegion);
}
return Collections.unmodifiableList(all);
}
@Override
public List<IContainer> getBranches() {
return getSubBlocks();
}
@Override
public String baseString() {
return tryRegion.baseString();
@@ -56,7 +84,14 @@ public final class TryCatchRegion extends AbstractRegion {
@Override
public String toString() {
return "Try: " + tryRegion
+ " catches: " + Utils.listToString(catchRegions);
StringBuilder sb = new StringBuilder();
sb.append("Try: ").append(tryRegion);
if (!catchRegions.isEmpty()) {
sb.append(" catches: ").append(Utils.listToString(catchRegions.values()));
}
if (finallyRegion != null) {
sb.append(" finally: ").append(finallyRegion);
}
return sb.toString();
}
}
@@ -19,16 +19,12 @@ public final class IfInfo {
this(condition, thenBlock, elseBlock, new HashSet<BlockNode>(), new HashSet<BlockNode>());
}
public IfInfo(IfCondition condition, IfInfo info) {
this(condition, info.getThenBlock(), info.getElseBlock(), info.getMergedBlocks(), info.getSkipBlocks());
}
public IfInfo(IfInfo info, BlockNode thenBlock, BlockNode elseBlock) {
this(info.getCondition(), thenBlock, elseBlock, info.getMergedBlocks(), info.getSkipBlocks());
}
private IfInfo(IfCondition condition, BlockNode thenBlock, BlockNode elseBlock,
Set<BlockNode> mergedBlocks, Set<BlockNode> skipBlocks) {
Set<BlockNode> mergedBlocks, Set<BlockNode> skipBlocks) {
this.condition = condition;
this.thenBlock = thenBlock;
this.elseBlock = elseBlock;
@@ -90,6 +86,6 @@ public final class IfInfo {
@Override
public String toString() {
return "IfInfo: " + condition + ", then: " + thenBlock + ", else: " + elseBlock;
return "IfInfo: then: " + thenBlock + ", else: " + elseBlock;
}
}
@@ -1,6 +1,7 @@
package jadx.core.dex.regions.conditions;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.IBranchRegion;
import jadx.core.dex.nodes.IContainer;
import jadx.core.dex.nodes.IRegion;
import jadx.core.dex.regions.AbstractRegion;
@@ -9,7 +10,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public final class IfRegion extends AbstractRegion {
public final class IfRegion extends AbstractRegion implements IBranchRegion {
private final BlockNode header;
@@ -89,6 +90,14 @@ public final class IfRegion extends AbstractRegion {
return Collections.unmodifiableList(all);
}
@Override
public List<IContainer> getBranches() {
List<IContainer> branches = new ArrayList<IContainer>(2);
branches.add(thenRegion);
branches.add(elseRegion);
return Collections.unmodifiableList(branches);
}
@Override
public boolean replaceSubBlock(IContainer oldBlock, IContainer newBlock) {
if (oldBlock == thenRegion) {
@@ -116,6 +125,6 @@ public final class IfRegion extends AbstractRegion {
@Override
public String toString() {
return "IF(" + condition + ") then " + thenRegion + " else " + elseRegion;
return "IF " + header + " then " + thenRegion + " else " + elseRegion;
}
}
@@ -156,7 +156,7 @@ public final class LoopRegion extends AbstractRegion {
@Override
public String baseString() {
return body.baseString();
return body == null ? "-" : body.baseString();
}
@Override
@@ -28,8 +28,8 @@ public class ExcHandlerAttr implements IAttribute {
@Override
public String toString() {
return "ExcHandler: "
+ (handler.isCatchAll() ? "all" : handler.getCatchType())
+ " " + handler.getArg();
return "ExcHandler: " + (handler.isFinally()
? " FINALLY"
: (handler.isCatchAll() ? "all" : handler.getCatchType()) + " " + handler.getArg());
}
}
@@ -21,6 +21,7 @@ public class ExceptionHandler {
private InsnArg arg;
private TryCatchBlock tryBlock;
private boolean isFinally;
public ExceptionHandler(int addr, ClassInfo type) {
this.handleOffset = addr;
@@ -79,6 +80,14 @@ public class ExceptionHandler {
return tryBlock;
}
public boolean isFinally() {
return isFinally;
}
public void setFinally(boolean isFinally) {
this.isFinally = isFinally;
}
@Override
public int hashCode() {
return (catchType == null ? 0 : 31 * catchType.hashCode()) + handleOffset;
@@ -1,14 +1,11 @@
package jadx.core.dex.trycatch;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.IBlock;
import jadx.core.dex.nodes.IContainer;
import jadx.core.dex.nodes.InsnContainer;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.InstructionRemover;
import jadx.core.utils.Utils;
import java.util.ArrayList;
@@ -19,7 +16,6 @@ import java.util.List;
public class TryCatchBlock {
private final List<ExceptionHandler> handlers;
private IContainer finalRegion;
// references for fast remove/modify
private final List<InsnNode> insns;
@@ -55,6 +51,7 @@ public class TryCatchBlock {
for (Iterator<ExceptionHandler> it = handlers.iterator(); it.hasNext(); ) {
ExceptionHandler h = it.next();
if (h == handler) {
unbindHandler(h);
it.remove();
break;
}
@@ -64,26 +61,34 @@ public class TryCatchBlock {
}
}
private void removeWholeBlock(MethodNode mth) {
if (finalRegion != null) {
// search catch attr
for (BlockNode block : mth.getBasicBlocks()) {
CatchAttr cb = block.get(AType.CATCH_BLOCK);
if (cb == attr) {
for (ExceptionHandler eh : mth.getExceptionHandlers()) {
if (eh.getBlocks().contains(block)) {
TryCatchBlock tb = eh.getTryBlock();
tb.setFinalRegionFromInsns(mth, ((IBlock) finalRegion).getInstructions());
}
}
private void unbindHandler(ExceptionHandler handler) {
for (BlockNode block : handler.getBlocks()) {
block.add(AFlag.SKIP);
ExcHandlerAttr excHandlerAttr = block.get(AType.EXC_HANDLER);
if (excHandlerAttr != null) {
if (excHandlerAttr.getHandler().equals(handler)) {
block.remove(AType.EXC_HANDLER);
}
}
} else {
// self destruction
for (InsnNode insn : insns) {
insn.removeAttr(attr);
SplitterBlockAttr splitter = handler.getHandlerBlock().get(AType.SPLITTER_BLOCK);
if (splitter != null) {
splitter.getBlock().remove(AType.SPLITTER_BLOCK);
}
insns.clear();
}
}
private void removeWholeBlock(MethodNode mth) {
// self destruction
for (Iterator<ExceptionHandler> it = handlers.iterator(); it.hasNext(); ) {
ExceptionHandler h = it.next();
unbindHandler(h);
it.remove();
}
for (InsnNode insn : insns) {
insn.removeAttr(attr);
}
insns.clear();
if (mth.getBasicBlocks() != null) {
for (BlockNode block : mth.getBasicBlocks()) {
block.removeAttr(attr);
}
@@ -95,9 +100,22 @@ public class TryCatchBlock {
insn.addAttr(attr);
}
public void removeInsn(InsnNode insn) {
public void removeInsn(MethodNode mth, InsnNode insn) {
insns.remove(insn);
insn.remove(AType.CATCH_BLOCK);
if (insns.isEmpty()) {
removeWholeBlock(mth);
}
}
public void removeBlock(MethodNode mth, BlockNode block) {
for (InsnNode insn : block.getInstructions()) {
insns.remove(insn);
insn.remove(AType.CATCH_BLOCK);
}
if (insns.isEmpty()) {
removeWholeBlock(mth);
}
}
public Iterable<InsnNode> getInsns() {
@@ -108,36 +126,11 @@ public class TryCatchBlock {
return attr;
}
public IContainer getFinalRegion() {
return finalRegion;
}
public void setFinalRegion(IContainer finalRegion) {
this.finalRegion = finalRegion;
}
public void setFinalRegionFromInsns(MethodNode mth, List<InsnNode> insns) {
List<InsnNode> finalBlockInsns = new ArrayList<InsnNode>(insns);
setFinalRegion(new InsnContainer(finalBlockInsns));
InstructionRemover.unbindInsnList(mth, finalBlockInsns);
// remove these instructions from other handlers
for (ExceptionHandler h : getHandlers()) {
for (BlockNode ehb : h.getBlocks()) {
ehb.getInstructions().removeAll(finalBlockInsns);
}
public boolean merge(MethodNode mth, TryCatchBlock tryBlock) {
if (tryBlock == this) {
return false;
}
// remove from blocks with this catch
for (BlockNode b : mth.getBasicBlocks()) {
CatchAttr ca = b.get(AType.CATCH_BLOCK);
if (attr == ca) {
b.getInstructions().removeAll(finalBlockInsns);
}
}
}
public void merge(MethodNode mth, TryCatchBlock tryBlock) {
for (InsnNode insn : tryBlock.getInsns()) {
this.addInsn(insn);
}
@@ -148,6 +141,7 @@ public class TryCatchBlock {
// clear
tryBlock.handlers.clear();
tryBlock.removeWholeBlock(mth);
return true;
}
@Override

Some files were not shown because too many files have changed in this diff Show More