Compare commits

..

271 Commits

Author SHA1 Message Date
skylot 8c7140d6b8 fix: change not allowed access modifiers for methods (#387) (PR #439)
Fix visibility access modifies for methods (see discussions in #370 and #387):
    * all virtual methods become public
    * direct methods become private (instead constructors and static methods for now)
    * such modifications perform by default and can be disabled by the option in preferences (`--respect-bytecode-access-modifiers` in jadx-cli)
    * if changed to method added comment (`Access modifiers changed, original: private`)
2019-02-11 14:56:03 +03:00
Jan S bf42b97580 build: compile for Java 8; enable G1GC on Java 8 (PR #436) 2019-01-23 17:34:03 +03:00
skylot f8c0449d4e feat(gui): add icons to jadx-gui (#420) (PR #428) 2019-01-23 11:00:24 +03:00
Skylot b28eaa1a94 fix(gui): add synchronization to SimpleIndex class (#435) 2019-01-23 10:06:13 +03:00
Skylot be509c7104 fix(gui): use editor font in search node column 2019-01-23 10:05:00 +03:00
Skylot 2931617202 fix(gui): use editor font in files tree and fix bundled font loading 2019-01-23 09:39:57 +03:00
Skylot 82d0d622a8 fix: refactor, improve performance and fix some issues in resource processing
fix(gui): instead gradle export was executed normal export
fix(gui): content of some resource files was not shown
perf: direct resource files saving without full length buffer in memory
perf(gui): line numbers will be disabled on big files due to performance issue
feat(gui): click on HeapUsageBar will run GC and update memory info
feat(gui): add more file types for syntax highlights
refactor: ResContainer class changed for support more types of data (added link to resource file)
2019-01-22 18:51:09 +03:00
Skylot bcaca781b1 style(gui): reformat code and fix some warnings 2019-01-21 13:47:05 +03:00
Jan S ffedaea505 fix(gui): limit the spare memory to max. 512MiB (#434) 2019-01-21 09:52:00 +03:00
Skylot aec986447e fix: support multi-exception catch blocks (#421) 2019-01-19 16:28:25 +03:00
Skylot b0e3cfedf4 fix: update apksig library to latest version (#431) 2019-01-19 09:49:20 +03:00
Skylot da41efa3db fix: force rename by checks from RenameVisitor (#432) 2019-01-18 16:50:11 +03:00
Jan S 9e0cd2e14e fix(gui): add synchronizations to search index creation (#433)
* fix: unsynchronized search index creation (code usage) results in ArrayIndexOutOfBoundsException and stuck at 99%

* fix: use computeIfAbsent instead of synchronized block
2019-01-18 16:47:44 +03:00
Jan S d1af751226 feat(gui): APK signature check v1/v2 using the apksig library from Google (#431)
* feat: APK signature check v1/v2 using the apksig library from Google
* fix: proposed changes implemented
2019-01-18 12:26:22 +03:00
Skylot 618b014b3d fix: rename method wrapped by synthetic only from same class (#430) 2019-01-16 22:27:50 +03:00
Jan S 7c353a6c6f fix(gui): unsynchronized search index creation results in NullPointerException upon performing search (#429) 2019-01-15 13:05:45 +03:00
Jan S 72b2663949 fix: ArrayIndexOutOfBoundsException in string concatenation visitor (#427)
* fix: ArrayIndexOutOfBoundsException in string concatenation visitor
* fix: typo in comment
* fix: StringBuilder chain processing created wrong code
* test: simple JUnit test cases added for testing StringBuilder chain processing (chains that can be and that can't be simplified)
2019-01-12 21:12:28 +03:00
Skylot 727285e3df chore: update dependencies and gradle 2019-01-12 19:07:37 +03:00
Skylot a932c7c569 build: add java 11 to build on travis 2019-01-12 13:06:41 +03:00
Skylot 1272ae2d4d fix(gui): don't skip indexing code lines starting with '}' (#426) 2019-01-10 23:46:59 +03:00
Skylot ddaf0375dc docs: add pyjadx link in readme (#424) 2019-01-07 11:28:03 +03:00
Jan S f60bb6b121 fix: various UI improvements (#419)
* fixed wait time for background jobs
* enable multi-threaded decompiling
* added preference for excluding certain packages from decompiling and indexing
* show message dialog in case classes are not indexed because of low memory
* added heap usage bar for visualizing Java memory usage
2019-01-06 15:46:54 +03:00
Donlon fd3498add6 fix: show method alias in "method not decompiled" messages (#410) 2019-01-06 14:02:37 +03:00
Jan Peter Stotz 1ac2cdfc41 fix: wait time for background jobs too short 2018-12-26 20:21:16 +03:00
Skylot eadf046b2c chore: show try/catch processing problems in code comments 2018-12-25 17:29:36 +03:00
Skylot e9591efd7e fix: search exception handler splitter block by offset if jump source unknown (#406) 2018-12-25 17:27:42 +03:00
Skylot fbf750f588 build: update jacoco for build with java 11 2018-12-22 15:10:09 +03:00
Skylot 63c528dba9 build: update shadowJar for build with gradle 5.0 2018-12-22 14:57:06 +03:00
Skylot 0f27eba1b1 fix: don't rename constructors and class init methods in deobfuscator (#415) 2018-12-22 13:03:06 +03:00
Skylot a841d0ebe7 fix: use '$' for inner classes also in methods and fields (#415) 2018-12-22 13:02:27 +03:00
Skylot e0624ce986 fix: use '$' as separator for inner classes in .jobf file (#415) 2018-12-21 19:44:25 +03:00
Skylot 7e8435cceb fix(gui): fill background before draw line numbers (#404) 2018-12-06 14:03:09 +03:00
Skylot 6d59f77165 fix: process try/catch without move-exception instruction (#395) 2018-11-26 14:31:49 +03:00
Marcin Kamionowski 3a798cb21c fix: return type lost after type inference (#396) 2018-11-23 20:01:50 +03:00
Skylot 1fc92d2a16 fix: instruction deep equals must check result 2018-11-16 19:04:38 +03:00
Skylot 850bd96976 fix: don't remove synthetic class with inner classes 2018-11-11 21:04:37 +03:00
Skylot 20b03aa755 fix: don't remove synthetic method if args count or name not same (#361) 2018-11-09 19:54:00 +03:00
Jan S 5281eed1a5 fix: loading of i18n resources as UTF-8 (see #363) (PR #386) 2018-11-07 22:57:31 +03:00
Parth Bhatia bedbf94b4a fix: update dx to version 16 (#369) 2018-11-07 20:10:37 +03:00
Skylot 47917fd5c2 fix: resolve some sonar critical issues 2018-10-29 22:27:28 +03:00
Skylot 0abb51c87a fix(gui): on settings reset run upgrade method 2018-10-29 22:27:05 +03:00
Skylot 557667b125 fix(gui): allow partial settings sync to not save command line options 2018-10-29 22:27:05 +03:00
Skylot 1d7bb43dfd fix: correct code line number calculation 2018-10-29 18:43:22 +03:00
Skylot 6b3e8f083c fix(gui): override settings by cmd options 2018-10-29 18:42:17 +03:00
Skylot bc629337d6 fix(gui): add "use imports" option to preferences 2018-10-29 18:35:23 +03:00
Skylot 58993b9799 fix(gui): apply render hints for line numbers 2018-10-28 18:52:43 +03:00
Skylot a3464d7184 fix(gui): make link for full class names (#378) 2018-10-28 18:52:43 +03:00
Jan S a8a31643f1 fix: Fix for #377 (Jadx in Windows open with list) (#379) 2018-10-28 18:51:25 +03:00
Jan S df9ae295db feat: make the import class name clickable (#378) 2018-10-25 16:36:37 +03:00
Skylot 8c348c935c build: disable travis build on oracle jdk 10 2018-10-24 21:45:00 +03:00
Skylot 3815d30fc1 fix: force rename fields and methods with reserved names (#364) 2018-10-24 21:30:36 +03:00
Skylot 778b9bb851 fix: resolve lint errors in resource save methods 2018-10-24 21:30:36 +03:00
Sergey Toshin 57dd9e6146 Removes useless imports which prevented gradle build 2018-10-24 21:21:17 +03:00
sergey-wowwow 8eef4a9075 fix: saves all resources (#375) 2018-10-24 20:58:49 +03:00
sergey-wowwow 87f50ab513 fix: exports resources first (#376) 2018-10-24 20:56:32 +03:00
Skylot 2de86b6db5 fix(gui): make correct size truncate for recent files list 2018-09-08 17:59:38 +03:00
Skylot 9be62fb16e fix: lower regions count limit (#354) 2018-09-08 17:45:04 +03:00
Skylot f6f883b9d1 fix: change resource fields generations in R class (#308) 2018-09-08 14:33:33 +03:00
sergey-wowwow 5de4d0792f fix: generates code of missing R class (#353) 2018-09-08 14:32:59 +03:00
Skylot 8c43e7f7ce style: fix code formating 2018-09-08 10:22:06 +03:00
Skylot 9e24a5abeb fix(gui): show 'copy name' action only for supported nodes 2018-09-08 10:12:40 +03:00
Skylot b587b6d694 fix(gui): use correct font and style for certificate panel 2018-09-08 10:12:40 +03:00
Skylot bc3af8e64d fix: resolve some sonar warnings 2018-09-08 10:12:40 +03:00
Skylot 7bd428cf6d build: fix gitlab config 2018-09-08 10:12:40 +03:00
Skylot 912f3c8467 build: skip gradle assemble before build 2018-09-01 14:27:15 +03:00
javaeryang a8febb2447 feat(gui): add a menu to copy class name (#351) 2018-08-28 11:28:53 +03:00
Skylot 1b0b526822 chore: remove 'v' from version string 2018-08-26 23:15:48 +03:00
Skylot 6250ebdd75 chore: don't use labels for artifacts in github release 2018-08-26 23:14:18 +03:00
Skylot 156e4209f9 feat(build): use semantic-release for automatic release publishing 2018-08-26 22:18:06 +03:00
Skylot 7492889f4e core: prevent endless region processing (#340) 2018-08-23 23:16:36 +03:00
Skylot 0c041120f6 core: show all decompilation errors in code comments (#313) 2018-08-23 23:16:36 +03:00
Skylot ecbb53aaea core: fixed 'this' attribute propagation for move insn (#345) 2018-08-22 21:38:43 +03:00
skylot ffe739b7eb Merge pull request #343 from Donlon/master
Solve unreplaced fields names when deobfuscation is on (#241)
2018-08-22 11:59:04 +03:00
Donlon bd05be6fb6 Delete some changes 2018-08-22 12:14:29 +08:00
skylot ff7df3818f Merge pull request #344 from JaviLukiOfficial/master
New language: Spanish + Small typo corrected on a LOG.error String argument.
2018-08-21 16:49:26 +03:00
JaviLukiOfficial 39899e4edc -New language: Spanish
-Small typo corrected on a LOG.error String argument.
2018-08-21 15:33:22 +02:00
Donlon dc578f98e7 Fix deobfuscation issue 2018-08-21 17:10:20 +08:00
Skylot d7ce41e724 core: don't remove synthetic methods with some logic beside casts (#336) 2018-08-20 23:05:17 +03:00
Skylot eaaeb2c843 core: fix return block split after try/catch (#295) 2018-08-20 21:36:19 +03:00
Skylot 0ae7c1efbf core: rename wrapped synthetic method (#336) 2018-08-19 19:15:31 +03:00
Donlon cb13599739 Upgrade Chinese translation 2018-08-19 15:49:27 +03:00
Skylot a9251de1dd deobf: prevents overlaping of class names and packages (#335) 2018-08-19 13:06:47 +03:00
Skylot 56798e716a gui: min and max deobf lengths must be positive 2018-08-19 12:19:11 +03:00
Donlon 904f0a1197 A subtle bug repairing 2018-08-19 10:24:34 +03:00
Donlon 4d3f2740ce Language switch supported 2018-08-19 10:21:30 +03:00
Skylot f9e7a29c08 core: fix sythetic constructor replacement (#334) 2018-08-16 23:30:53 +03:00
Skylot 6cb14a1c50 core: use flag for mark 'this' register 2018-08-16 22:55:30 +03:00
Skylot ea9f933f9e core: fix register arg hashCode method (#321, #328) 2018-08-15 16:23:30 +03:00
Skylot eb2e5e3da5 cli: set lower java starting heap size 2018-08-15 16:02:29 +03:00
Skylot 9a4e8bdb48 set default deobfuscation min length to 3 (#332) 2018-08-15 15:29:43 +03:00
Sergey Toshin fad0091d87 Prevents generation of NSes second time in wrong place 2018-08-15 15:20:03 +03:00
Skylot b861151f63 core: rollback finally block extraction if some blocks not removed (#327) 2018-08-04 22:32:00 +03:00
Skylot feeafc407a core: exclude inner classes from dependencies (#318) 2018-08-04 15:35:32 +03:00
Skylot ea1c1eb803 core: fix insn move check for field assign (#326) 2018-08-04 14:32:27 +03:00
Skylot b83e20b571 core: improve immutable list implementation 2018-08-01 15:07:05 +03:00
skylot 160ad64e67 Merge pull request #325 from FlXME/patch-3
Making the Classloader threadsafe
2018-07-30 11:41:38 +03:00
skylot 1213ff26b4 Merge pull request #324 from FlXME/patch-1
Performance issue when building strings
2018-07-30 11:34:23 +03:00
skylot 3bf93f1f85 Merge pull request #323 from FlXME/patch-2
Directory Bug
2018-07-30 11:33:59 +03:00
Felix Bergmann a502581640 Making the Classloader threadsafe 2018-07-30 09:31:44 +02:00
Felix Bergmann 1ec041a48f Directory Bug
The correct pattern to make a directory is: `if (!dir.mkdirs() && !dir.isDirectory()) { error }` mkdirs checks for exists so the exists check is redundant.
2018-07-30 00:09:54 +02:00
Felix Bergmann fdaf8492ef Performance issue when building strings
Improve performance by using StringBuilder instead of StringBuffer.
2018-07-30 00:02:14 +02:00
Skylot 2433a7e89c core: fix exception handler jumps (#320) 2018-07-28 22:26:56 +03:00
Skylot 6e358d3eab core: use own immutable list 2018-07-28 22:26:56 +03:00
Skylot 7e462e800f update gradle wrapper and dependencies 2018-07-28 11:21:46 +03:00
Skylot 156e54c77f core: exclude inner classes from class dependencies (#318) 2018-07-22 15:01:38 +03:00
Skylot 9752ec2655 core: fix duplicate regions creation (#314) 2018-07-22 00:50:04 +03:00
asviridenko edc1e5fa84 gui: show the certificate if the certificate file name is not standard (#315)
* show the certificate if the certificate file name is not standard
eg https://play.google.com/store/apps/details?id=com.kms.free
2018-07-22 00:43:59 +03:00
Skylot a959af087b core: fix replace target in if instruction (#317) 2018-07-19 15:27:35 +03:00
Skylot c5994f954a core: fix NPE in signature parser (#313) 2018-07-16 18:16:13 +03:00
asviridenko 03a09debfa gui: show app certificate (#305)
* add node

* add node

* add certificate panel

* add certifcate manager

* работа над проектом

* ресурсы

* включение возможности показа

* небольшие исправления

* start tests

* more tests

* signature test

* fingerprint test

* public key test

* удалено лишний код

* add scroll
2018-07-05 12:40:27 +03:00
Skylot 2cf6a9b691 gui: fixed object reference holding by LogCollector (#302) 2018-07-04 00:15:43 +03:00
Skylot 5b712e8dbc core: fix inline of anonymous obfuscated class (#122) 2018-06-29 22:23:48 +03:00
skylot 5d7f2c706c Merge pull request #301 from skylot/public_xml_no_dups
Prevent adding duplicate ids for resource entries with different entry configs
2018-06-27 20:46:28 +03:00
Sergey Toshin 22f51e1a28 Change display style for resources.arsc 2018-06-27 20:09:08 +03:00
Sergey Toshin 61684ea73d Prevent adding duplicate ids for resource entries with different entry configs 2018-06-27 19:55:24 +03:00
Skylot 45b37dcd10 core: fix class name checker execution after deobfuscator (#286) 2018-06-27 16:25:40 +03:00
skylot c0b2230b0b Merge pull request #299 from skylot/public_xml_impl
Generates and saves public.xml in apktool style
2018-06-27 01:15:46 +03:00
Sergey Toshin 53fa8205f2 Receives canonical path in ZipSecurity.isInSubDirectory(...) 2018-06-26 22:37:16 +03:00
Sergey Toshin ddbcf8bb19 Prevents path traversal attacks thru rc names 2018-06-26 20:26:31 +03:00
Sergey Toshin 6e50ddf5c8 Generates and saves public.xml in apktool style 2018-06-26 20:07:30 +03:00
Skylot dda49f1501 core: fix enum reconstruction (#272) 2018-06-21 16:50:36 +03:00
Skylot 4e2e5aa975 gui: fix colors to match system theme, add editor theme selector (#297) 2018-06-19 23:12:53 +03:00
Skylot 10fd3652d4 core: fix processing same class several times (#274)
Caution: This change can increase memory usage!
However overall decompilation must be faster
2018-06-17 15:26:52 +03:00
Skylot 4d30510706 core: fix line number references 2018-06-17 12:21:22 +03:00
Skylot ee74c4d870 core: ignore debug info with bad variable names 2018-06-16 20:02:29 +03:00
Skylot 45f5e0cb04 core: prevent endless loop in region construction (#267) 2018-06-16 12:31:37 +03:00
Skylot 7d983f2847 core: fix catch block argument if move-exception instruction is missing (#295) 2018-06-16 12:31:37 +03:00
skylot 3b2b5417aa Merge pull request #292 from skylot/xml_bug_fixes
Fixes xml deobfuscation errors
2018-06-05 19:09:58 +03:00
Sergey Toshin 0a0c4eac88 Fixes errors 2018-06-05 17:06:55 +03:00
Skylot d20cd43a99 core: fix loop handling 2018-06-02 21:16:58 +03:00
Skylot 7b4321ecee gui: fix build for java 10 and update dependencies (#291) 2018-06-02 19:47:18 +03:00
Skylot 188bfd1a7e core: fix endless loop processing (#275) 2018-06-01 23:15:46 +03:00
Skylot 7b6825d85c core: move same instructions from predecessors for loops 2018-06-01 23:14:36 +03:00
Skylot a27cb9c34e core: bind blocks for target instructions at early stage 2018-06-01 21:35:19 +03:00
Skylot 8445ebf107 fix sonar badge 2018-06-01 21:32:15 +03:00
Skylot 6df315017c gui: add Hack font 2018-06-01 21:32:15 +03:00
skylot 1931e78367 Merge pull request #290 from skylot/xml_deobf_2
Xml deobf 2.0
2018-06-01 11:12:23 -07:00
Sergey Toshin 90692d89c5 Xml deobf 2.0 2018-06-01 17:49:29 +03:00
Skylot 4f02864e12 core: fix variable declaration in else-if chain (#273) 2018-05-26 20:41:54 +03:00
Skylot 7562ec9e1a tests: add base test class for simplified apk debugging 2018-05-26 20:41:54 +03:00
Skylot 6d984c0407 gui: update default settings 2018-05-26 20:41:54 +03:00
Skylot 3556e591b0 gui: hide deobf options if not enabled (#281) 2018-05-26 20:41:54 +03:00
skylot 8fdb473d78 Merge pull request #280 from skylot/xml_tag_attr_name_validator
res: Copy XMLChar class from Apache Xerces library. Replaces all invalid (obfuscated) XML tag and attribute names to random ones
2018-05-17 23:33:53 +03:00
Sergey Toshin 398cd15dcf Copy XMLChar class from Apache Xerces library. Replaces all invalid (obfuscated) XML tag and attribute names to random ones 2018-05-17 22:49:40 +03:00
Skylot 5006b3e837 gui: fix cell renderer in search dialog (#271) 2018-05-07 22:08:16 +03:00
Skylot 7216635d84 gui: run text search in background thread (#269) 2018-05-05 22:44:49 +03:00
Skylot 98ef7c39b7 core: fix synthetic constructor remove (#265) 2018-05-02 16:05:06 +03:00
Skylot e039a5a9af core: don't process debug info if offset is incorrect (#259) 2018-05-01 19:32:55 +03:00
Skylot 412a185fa1 core: fix null pointer error in try/catch processing 2018-05-01 17:31:44 +03:00
Skylot 20bfe83849 core: fix null pointer in code annotations getter 2018-05-01 17:18:14 +03:00
Skylot 39093130a3 core: fix processing overriden methods in deobfuscator (#207) 2018-05-01 17:16:52 +03:00
Skylot 9e9270a8b7 core: fix type inference StackOverflowError 2018-05-01 16:29:50 +03:00
Skylot 2c904c56f4 core: reformat imports, fix some sonar issues 2018-04-22 21:55:18 +03:00
Skylot a3b961e72f core: fix method deobfuscation (#241) 2018-04-22 20:18:33 +03:00
Skylot 0e4c8df418 cli: print default value for number options 2018-04-22 20:06:21 +03:00
skylot 3b2d595a06 Merge pull request #255 from skylot/xml_unreadable_chars_escapes
Adds more escape for unreadable characters so parser won't throw exceptions during parse
2018-04-14 15:23:02 +03:00
Skylot b29223c5b6 core: fix enum processing order, remove synchronization (#257) 2018-04-14 13:22:53 +03:00
Sergey Toshin d805ec15b4 Adds more escape for unreadable characters so parser won't throw exceptions during parse 2018-04-09 19:23:31 +03:00
Sergey Toshin d5cfdfb50d Prevents command injections when opening links 2018-04-08 21:23:01 +03:00
Sergey Toshin 2e5d73a7e4 Fixes bug with NS declaration duplicates 2018-04-08 21:21:45 +03:00
Sergey Toshin 1c352cc81b Fixes bugs 2018-04-08 21:21:45 +03:00
Sergey Toshin 9c6c18780f Adds define of unknown NSes 2018-04-08 21:21:45 +03:00
Skylot cb23b65797 core: fix variable names propagation (#219) 2018-04-08 19:24:39 +03:00
Skylot 7a16814808 core: fix class file loading (#249) 2018-04-07 16:58:03 +03:00
Skylot 23553c9944 cli: fix missing spaces in help 2018-04-01 14:01:55 +03:00
Skylot 54fbe8a7c0 remove -d64 option as java 10 not support it anymore 2018-04-01 13:59:18 +03:00
Skylot ea01102f1d gui: fix decompilation task on search (#235) 2018-03-29 21:04:05 +03:00
Skylot 15e1e1dfab build jadx-gui.exe 2018-03-27 19:48:29 +03:00
Skylot 37ed9cd25b core: make decompilation results more deterministic 2018-03-27 11:25:17 +03:00
Sergey Toshin 882af04027 Encodes XML attrs 2018-03-25 13:16:23 +03:00
Skylot aa8a298ec7 remove sourceforge from downloads list 2018-03-25 13:14:24 +03:00
Skylot 05ab8fd868 bump version 2018-03-25 12:33:54 +03:00
Skylot 1a736aadf5 v0.7.1 2018-03-25 12:30:39 +03:00
Skylot 2cea653249 fix bintray upload 2018-03-18 16:38:55 +03:00
Skylot 1085238031 res: don't use system locale for number formating (#238) 2018-03-18 15:25:57 +03:00
Skylot 1356d91423 res: don't add colon for empty namespace (#231) 2018-03-18 15:25:57 +03:00
sergey-wowwow fd2dc14ede Update ZipSecurity.java
Changes max diff size to prevent valid resources filtration
2018-03-13 19:20:52 +03:00
Skylot f91c5d3647 res: skip padding on file end (#225) 2018-03-11 19:19:06 +03:00
Skylot 1f3aebf584 res: close tag before cdata (#231) 2018-03-11 17:09:17 +03:00
Skylot b39d79a0f9 remove coverity and downloads badges 2018-03-11 10:25:28 +03:00
Skylot c410914208 core: skip finally extract visitor on error 2018-03-04 19:18:53 +03:00
Skylot a046f1caec core: ignore dex loading errors (#233) 2018-02-28 21:55:19 +03:00
Skylot c25f918cc5 gui: fix some sonar warnings 2018-02-28 21:50:14 +03:00
Skylot 6fb1c8d3b9 gui: don't decode resources on file open 2018-02-17 23:23:03 +03:00
Skylot 6047a27c89 build: disable codecov pull request report 2018-02-13 16:07:16 +03:00
Skylot 8446d016e4 cli: update jcommander lib 2018-02-13 15:31:49 +03:00
Skylot ab040d36d5 update all dependencies (#229) 2018-02-13 14:58:27 +03:00
skylot a2781b5bd3 Merge pull request #221 from rgoggins/master
Move contribution section to separate file
2018-02-12 21:45:41 +03:00
skylot a7903f31ac Merge pull request #228 from jpstotz/master
Search dialog improvements
2018-02-12 21:42:33 +03:00
Jan Peter Stotz c134837ca6 pagination for search results. 2018-02-12 16:20:12 +01:00
Jan Peter Stotz f0a57e6714 case insensitive option for searches 2018-02-12 13:17:40 +01:00
Skylot d9b0365c9f core: fix binary xml parser (#211) 2018-02-11 23:18:21 +03:00
Skylot 948f9456f5 core: change jadx args api for easier processing and validation 2018-02-10 21:32:51 +03:00
Skylot 32f94b463f core: add code lines for while loop 2018-02-07 21:21:53 +03:00
Skylot 035506496e core: add endless loop test 2018-02-04 17:08:22 +03:00
Ryan Goggins 477222a395 Update README.md 2018-02-02 03:59:10 -05:00
Ryan Goggins 4bdfdfcb36 Create CONTRIBUTING.md 2018-02-02 03:58:41 -05:00
Skylot 3167cd0817 fix bintray upload 2018-01-28 00:12:55 +03:00
Skylot b52f35259b core: support 'not-int' and 'not-long' instructions 2018-01-28 00:04:27 +03:00
Skylot 20bf85b14d core: ignore bogus opcode decode (#214) 2018-01-28 00:03:59 +03:00
Skylot f02b99a1d0 fix some sonar warnings 2018-01-26 17:36:30 +03:00
Skylot 9132ef57f1 core: use late deletion for NOP instruction (#215) 2018-01-26 16:08:13 +03:00
skylot d42bf2d43c Merge pull request #212 from KpChuck/master
Don't throw a DecodeException if dex files aren't found but --no-src used
2018-01-21 11:30:42 +03:00
KpChuck 89042fbf4a Don't throw a DecodeException if dex files aren't found but --no-src is enabled 2018-01-20 21:12:04 +00:00
Skylot fc4dcd2db5 core: prevent some null crash on resource decoding 2018-01-18 23:21:36 +03:00
Skylot 4e07d80ebc cli: fix errors list sorting 2018-01-18 23:16:46 +03:00
Skylot c4a462d601 core: update dx to version 14, allow to decompile java 8 classes (new instructions not implemented yet) 2018-01-18 23:15:20 +03:00
Skylot 7fe46fb6f3 gui: highlight words on double click (#210) 2018-01-18 22:43:20 +03:00
Skylot 2cb94856fd build: setup bintray unstable upload 2018-01-17 11:02:31 +03:00
Skylot f53fc03c6c core: use dynamic check for filesystem case-sensitivity (#158) 2018-01-16 21:09:09 +03:00
Skylot 9278c51035 build: use commits count in gitlab ci version 2018-01-16 21:09:08 +03:00
sergey-wowwow ca9dc5f944 Update README.md 2018-01-16 21:05:59 +03:00
unknown f30cfb6166 Merge branch 'master' of https://github.com/skylot/jadx into issue_204 2018-01-16 20:58:22 +03:00
unknown 9614929f77 Fixes build issues 2018-01-16 20:54:52 +03:00
skylot 8e418d4414 Merge pull request #206 from mtdcr/master
Decode attributes which may contain either enums or values
2018-01-16 15:58:19 +03:00
Andreas Oberritter 5e81bd833b Decode attributes which may contain either enums or values, e.g. layout_width
android:layout_width="UNKNOWN_DATA_0x6401" becomes android:layout_width="100dp".
2018-01-16 12:56:21 +01:00
unknown cc2ae80e7b Issue #204 2018-01-15 19:35:30 +03:00
sergey-wowwow b921f6097d Update JadxCLIArgs.java 2018-01-14 21:47:37 +03:00
sergey-wowwow 9679ef893b Update README.md 2018-01-14 21:40:08 +03:00
sergey-wowwow e4fc3cebfd Update JadxArgs.java
Changes flags to true
2018-01-14 21:34:17 +03:00
sergey-wowwow 75135819cf Delete .DS_Store 2018-01-14 21:31:01 +03:00
sergey-wowwow 072b6cce36 Update README.md 2018-01-14 21:08:49 +03:00
Sergey Toshin 5d60f2cdf2 PR for issue #191 2018-01-14 21:04:28 +03:00
Sergey Toshin c476593925 Changes MAX_SIZE_DIFF in ZipSecurity, and adds extra logging 2018-01-14 20:14:11 +03:00
skylot 089467a419 Merge pull request #203 from dgorshkov/patch-1
Readme - GUI screenshot fix
2018-01-14 19:50:22 +03:00
dgorshkov ee68e04f84 GUI screenshot fix 2018-01-14 17:35:26 +01:00
binjia.zhou 9cd46e74be fix some xml generate issues 2018-01-14 12:15:00 +03:00
sergey-wowwow 5781220415 Delete .DS_Store 2018-01-13 20:14:11 +03:00
Sergey Toshin 965fd66e0f Adds more checks for file write ops 2018-01-03 19:50:16 +01:00
Sergey Toshin 7d3caa2875 Adds checks for resources, and logs detected attacks 2018-01-03 18:20:21 +01:00
Sergey Toshin 418546a659 Merge with master 2018-01-03 17:55:55 +01:00
Skylot d586c84b56 reformat code and fix sonar warnings 2018-01-02 21:26:43 +03:00
Skylot 7b9e5fe99f build: add sonarqube 2018-01-02 19:33:12 +03:00
Skylot 648f0edc79 build: add gitlab-ci 2018-01-02 18:54:41 +03:00
Skylot 4d9d0884c3 remove old version of dx.jar 2018-01-02 17:05:25 +03:00
Skylot 19c0bbb94c Merge jiqimaogou/jadx 2018-01-02 16:27:56 +03:00
Skylot c6995c2283 remove .DS_Store file 2018-01-02 15:59:45 +03:00
Skylot 49a263454c update gradle wrapper to version 4.4.1 2018-01-02 15:28:10 +03:00
Skylot 454519220f ui: don't run full decompilation on start 2018-01-02 15:24:20 +03:00
Skylot 118fa98ca9 gui: fix incorrect settings loading 2018-01-02 15:24:13 +03:00
Skylot 001fa639be core: fix some concurrency issues 2018-01-02 15:23:36 +03:00
Skylot 009749cf8b core: ignore errors in debug info parser (fix #176) 2018-01-02 15:22:49 +03:00
Skylot da94e7b1be core: update dx to 1.13 for support java 8, build for jdk 8 2018-01-02 15:22:20 +03:00
skylot ea346145f6 Merge pull request #194 from wuyongzheng/master
fixed a few bugs resulting program hang
2018-01-02 15:16:09 +03:00
skylot a01c379c95 Merge pull request #171 from daramos/deobfuscation_work
deobfuscation fixes
2018-01-02 15:15:34 +03:00
skylot c9b781d5e1 Merge pull request #119 from ITMonkeys/master
support for Android InstantRun Apk
2018-01-02 15:14:40 +03:00
skylot 0b49abf3f5 Merge pull request #115 from bigfool/master
add some code to enable jadx can decompile apk by file's type, not only by file's extension
2018-01-02 15:12:10 +03:00
skylot e5fe4b0a99 Merge pull request #169 from appetizerio/master
Increase heap limit to avoid most OOMs in Gradle
2018-01-02 15:08:57 +03:00
skylot 7474d305fb Merge pull request #114 from allight/master
Replace jadx-core/lib/dx-1.10.jar with recent AOSP dx.jar
2018-01-02 15:08:21 +03:00
skylot 4716929158 Merge pull request #108 from Tneciv/master
add translation of Chinese
2018-01-02 15:07:45 +03:00
Sergey Toshin 528ca09e8e Fixes for ZIP and XML processors 2017-12-31 01:51:25 +03:00
Wu Yongzheng 233054219f fix issue #184
please refer to https://github.com/skylot/jadx/issues/184
2017-12-21 16:04:12 +08:00
Wu Yongzheng 0e2c4d4af1 fix thread-safe bug
wrap WeakHashMap with Collections.synchronizedMap, because it can be used by many worker threads.
2017-12-21 16:00:11 +08:00
Wu Yongzheng f101e9a775 fix thread-safe bug
Change HashMap to ConcurrentHashMap, because it can be used by many worker threads.
2017-12-21 15:58:37 +08:00
Daniel Ramos bf3863d1bf Fixed issue where deobfuscated classes,methods,and fields were not being commented with their original name. 2017-04-30 21:23:19 -04:00
Daniel Ramos 94e9291c40 Fixed issue where postProcess may overwrite Deobfuscated method names.
The function will now check if the method name was aliased by Deobfuscation and if so it will use that name for all the overriden methods. Note: If the deobfuscation file contains two names for the same overriden method in two related Classes then the name that "wins" is non-deterministic.
2017-04-30 21:22:59 -04:00
Daniel Ramos 459d133b5d Fixed issue where renamed classes/methods referenced in a different dex file would not be renamed properly. Deobfuscation only modified the Class info for the InfoStorage of the Dex file the class belonged to. If a class in another Dex file referenced it, it would not know of the rename.
This commit moves InfoStorage to the RootNode. This allows all classes to know of each other regardless of the Dex file. A dexId field was added to the DexNode class to allow the the MethodInfo.fromDex function to continue to use method index to locate methods. The getMethod and putMethod functions in InfoStorage was modified to take a DexNode. The DexNode id is used to create a unique key used for the lookup into the methods HashMap.
2017-04-30 13:34:21 -04:00
Daniel Ramos 773fad66bb Fix issue where name generation on variables of deobfuscated objects were sometimes based off of the original class name, and not the alias. 2017-04-29 15:26:07 -04:00
Daniel Ramos e250c73109 Fix issue with inner classes and empty package name. 2017-04-29 15:18:41 -04:00
Mingyuan Xia 6870c05ffa increase cli heap usage as well 2017-04-10 13:13:04 +08:00
Mingyuan Xia 199581bf74 tune start script heap size for real-world apks 2017-04-10 12:56:05 +08:00
Skylot a9ae971602 build: remove gradle plugins, update gradle to 2.14.1 (#145) 2016-12-22 11:54:56 +03:00
chenzhong.cz fe03c85b97 ensure a zip file by file content. 2016-11-08 15:45:35 +08:00
chenzhong.cz c338652045 no limit to the package id 2016-10-31 11:24:33 +08:00
chenzhong.cz 1f5cdeb01b support arsc raw file view 2016-07-09 12:38:00 +08:00
袭建帅 e53a72c5f5 support for Android InstantRun Apk
we should consider the input file could contain only one single dex, multi-dex, or instantRun support dex for Android .apk files
2016-05-12 18:40:00 +08:00
Alex Light fc2690888e Replace the java 1.8 only dx.jar with one supporting java 1.7
Also removed the travis test for java 1.6.

Java 1.7 version of dx.jar compiled with plaform/libcore at commit
c3e562a and platform/dalvik at commit db9197b with
https://android-review.googlesource.com/#/c/221127/1 cherry-picked on
top of it.
2016-05-02 09:50:39 -07:00
齐振芳 b4472fd7d4 delete comments 2016-05-02 09:03:25 +08:00
Alex Light 796d02506a Replace jadx-core/lib/dx-1.10.jar with recent AOSP dx.jar
Recently support has been added to AOSP for generating and running
version 037 dex files. In order to load these we update the dx.jar
with a recent version built from AOSP.
2016-04-28 16:54:55 -07:00
齐振芳 467f729f06 add file type detect, jadx file by file's header, not only file's extension 2016-04-28 16:25:14 +08:00
Tneciv 050ec8b988 Create Messages_zh_CN.properties
add translation of Chinese
2016-04-05 18:35:52 +08:00
577 changed files with 14191 additions and 6293 deletions
+14
View File
@@ -0,0 +1,14 @@
coverage:
precision: 2
round: down
range: "50...100"
status:
project:
default: on
patch:
default: on
changes:
default: off
comment: false
+6 -2
View File
@@ -9,14 +9,19 @@ out/
*.iml
*.ipr
*.iws
.attach_pid*
**/.DS_Store
bin/
target/
build/
classes/
idea/
.gradle/
gradle.properties
node_modules/
jadx-output/
*-tmp/
*.dex
@@ -24,4 +29,3 @@ gradle.properties
*.dump
*.log
*.cfg
+25
View File
@@ -0,0 +1,25 @@
image: java:8
variables:
GRADLE_OPTS: "-Dorg.gradle.daemon=false"
TERM: "dumb"
before_script:
- chmod +x gradlew
stages:
- build
build:
stage: build
before_script:
- export JADX_LAST_TAG="$(git describe --abbrev=0 --tags)"
- export JADX_VERSION="${JADX_LAST_TAG:1}-$(git rev-parse --short HEAD)"
script:
- ./gradlew -g /cache/.gradle clean build jacocoTestReport
- ./gradlew -g /cache/.gradle clean sonarqube -Dsonar.host.url=$SONAR_HOST -Dsonar.organization=$SONAR_ORG -Dsonar.login=$SONAR_TOKEN
- ./gradlew -g /cache/.gradle clean dist
artifacts:
paths:
- build/jadx*.zip
- build/jadx*.exe
+14
View File
@@ -0,0 +1,14 @@
branch: release
verifyConditions:
- '@semantic-release/github'
prepare:
- path: '@semantic-release/exec'
cmd: "JADX_VERSION=${nextRelease.version} ./gradlew clean dist"
publish:
- path: '@semantic-release/exec'
cmd: "JADX_VERSION=${nextRelease.version} BINTRAY_PACKAGE=releases bash scripts/bintray-upload.sh"
- path: '@semantic-release/github'
assets:
- path: 'build/*.zip'
- path: 'build/*.exe'
+45 -19
View File
@@ -1,28 +1,54 @@
language: java
jdk:
- oraclejdk8
- oraclejdk7
- openjdk6
sudo: false
dist: trusty
# don't build on tag push
if: tag IS blank
git:
depth: false
before_install:
- chmod +x gradlew
- chmod +x gradlew
# override install to skip 'gradle assemble'
install:
- true
env:
global:
- TERM=dumb
- JADX_LAST_TAG=$(git describe --abbrev=0 --tags)
- JADX_VERSION="${JADX_LAST_TAG:1}-b$TRAVIS_BUILD_NUMBER-$(git rev-parse --short HEAD)"
matrix:
include:
- env: JDK=oracle-8
jdk: oraclejdk8
- env: JDK=openjdk11
jdk: openjdk11
script:
- TERM=dumb ./gradlew clean build dist
- java -version
- ./gradlew clean build
after_success:
- TERM=dumb ./gradlew jacocoTestReport coveralls
deploy:
- provider: script
skip_cleanup: true
on:
branch: master
tags: false
condition: $JDK = oracle-8
script: bash scripts/travis-master.sh
sudo: false
cache:
directories:
- $HOME/.gradle/caches/
- $HOME/.gradle/wrapper/
before_cache:
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
- provider: script
skip_cleanup: true
on:
branch: release
tags: false
condition: $JDK = oracle-8
script: bash scripts/travis-release.sh
notifications:
email:
- skylot@gmail.com
email:
- skylot@gmail.com
+7
View File
@@ -0,0 +1,7 @@
# Contribution
To support this project you can:
- Post thoughts about new features/optimizations that important to you
- Submit bug using one of following patterns:
* Java code examples which decompiles incorrectly
* Error log and link to _public available_ apk file or app page on Google play
+46 -42
View File
@@ -1,24 +1,38 @@
## JADX
[![Build Status](https://travis-ci.org/skylot/jadx.png?branch=master)](https://travis-ci.org/skylot/jadx)
[![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)
[![Code Coverage](https://codecov.io/gh/skylot/jadx/branch/master/graph/badge.svg)](https://codecov.io/gh/skylot/jadx)
[![SonarQube Bugs](https://sonarcloud.io/api/project_badges/measure?project=jadx&metric=bugs)](https://sonarcloud.io/dashboard?id=jadx)
[![License](http://img.shields.io/:license-apache-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0.html)
[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)
**jadx** - Dex to Java decompiler
Command line and GUI tools for produce Java source code from Android Dex and Apk files
![jadx-gui screenshot](http://skylot.github.io/jadx/jadx-gui.png)
![jadx-gui screenshot](https://i.imgur.com/h917IBZ.png)
### Downloads
- [unstable](https://drone.io/github.com/skylot/jadx/files)
- from [github](https://github.com/skylot/jadx/releases)
- from [sourceforge](http://sourceforge.net/projects/jadx/files/)
- latest [unstable build: ![Download](https://api.bintray.com/packages/skylot/jadx/unstable/images/download.svg) ](https://bintray.com/skylot/jadx/unstable/_latestVersion#files)
- release from [github: ![Latest release](https://img.shields.io/github/release/skylot/jadx.svg)](https://github.com/skylot/jadx/releases/latest)
- release from [bintray: ![Download](https://api.bintray.com/packages/skylot/jadx/releases/images/download.svg) ](https://bintray.com/skylot/jadx/releases/_latestVersion#files)
After download unpack zip file go to `bin` directory and run:
- `jadx` - command line version
- `jadx-gui` - graphical version
On Windows run `.bat` files with double-click\
**Note:** ensure you have installed Java 8 64-bit version
### Building from source
### Related projects:
- [PyJadx](https://github.com/romainthomas/pyjadx) - python binding for jadx by [@romainthomas](https://github.com/romainthomas)
### Building jadx from source
JDK 8 or higher must be installed:
git clone https://github.com/skylot/jadx.git
cd jadx
./gradlew dist
@@ -34,7 +48,7 @@ Run **jadx** on itself:
cd build/jadx/
bin/jadx -d out lib/jadx-core-*.jar
#or
# or
bin/jadx-gui lib/jadx-core-*.jar
@@ -42,27 +56,31 @@ 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
-r, --no-res - do not decode resources
-s, --no-src - do not decompile source code
-e, --export-gradle - save as android gradle project
--show-bad-code - show inconsistent code (incorrectly decompiled)
--no-replace-consts - don't replace constant value with matching constant field
--escape-unicode - escape non latin characters in strings (with \u)
--deobf - activate deobfuscation
--deobf-min - min length of name
--deobf-max - max length of name
--deobf-rewrite-cfg - force to save deobfuscation map
--deobf-use-sourcename - use source file name as class name alias
--cfg - save methods control flow graph to dot file
--raw-cfg - save methods control flow graph (use raw instructions)
-f, --fallback - make simple dump (using goto instead of 'if', 'for', etc)
-v, --verbose - verbose output
-h, --help - print this help
-d, --output-dir - output directory
-ds, --output-dir-src - output directory for sources
-dr, --output-dir-res - output directory for resources
-j, --threads-count - processing threads count
-r, --no-res - do not decode resources
-s, --no-src - do not decompile source code
-e, --export-gradle - save as android gradle project
--show-bad-code - show inconsistent code (incorrectly decompiled)
--no-imports - disable use of imports, always write entire package name
--no-replace-consts - don't replace constant value with matching constant field
--escape-unicode - escape non latin characters in strings (with \u)
--deobf - activate deobfuscation
--deobf-min - min length of name
--deobf-max - max length of name
--deobf-rewrite-cfg - force to save deobfuscation map
--deobf-use-sourcename - use source file name as class name alias
--cfg - save methods control flow graph to dot file
--raw-cfg - save methods control flow graph (use raw instructions)
-f, --fallback - make simple dump (using goto instead of 'if', 'for', etc)
-v, --verbose - verbose output
-h, --help - print this help
Example:
jadx -d out classes.dex
```
These options also worked on jadx-gui running from command line and override options from preferences dialog
### Troubleshooting
##### Out of memory error:
@@ -73,21 +91,7 @@ Example:
* edit 'jadx' script (jadx.bat on Windows) and setup bigger heap size:
`DEFAULT_JVM_OPTS="-Xmx2500M"`
### Contribution
To support this project you can:
- Post thoughts about new features/optimizations that important to you
- Submit bug using one of following patterns:
* Java code examples which decompiles incorrectly
* Error log and link to _public available_ apk file or app page on Google play
And any other comments will be very helpfull,
because at current stage of development it is very time consuming
to **find** new bugs, design and implement new features.
Also I need to **prioritize** these task for complete most important at first.
---------------------------------------
*Licensed under the Apache 2.0 License*
*Copyright 2015 by Skylot*
*Copyright 2018 by Skylot*
+55 -30
View File
@@ -1,36 +1,32 @@
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"
id 'org.sonarqube' version '2.7'
id 'com.github.ben-manes.versions' version '0.20.0'
}
ext.jadxVersion = file('version').readLines().get(0)
ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev"
version = jadxVersion
println("jadx version: ${jadxVersion}")
subprojects {
allprojects {
apply plugin: 'java'
apply plugin: 'groovy'
apply plugin: 'jacoco'
apply plugin: 'com.github.kt3k.coveralls'
version = jadxVersion
tasks.withType(JavaCompile) {
sourceCompatibility = JavaVersion.VERSION_1_6
targetCompatibility = JavaVersion.VERSION_1_6
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
if (!"$it".contains(':jadx-samples:')) {
options.compilerArgs << '-Xlint' << '-Xlint:unchecked' << '-Xlint:deprecation'
}
}
compileJava {
options.encoding = "UTF-8"
}
jar {
version = jadxVersion
manifest {
@@ -39,30 +35,53 @@ subprojects {
}
dependencies {
compile 'org.slf4j:slf4j-api:1.7.10'
compile 'org.slf4j:slf4j-api:1.7.25'
testCompile 'ch.qos.logback:logback-classic:1.1.2'
testCompile 'ch.qos.logback:logback-classic:1.2.3'
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'
testCompile 'org.hamcrest:hamcrest-library:2.1'
testCompile 'org.mockito:mockito-core:2.23.4'
testCompile 'org.spockframework:spock-core:1.1-groovy-2.4'
}
repositories {
mavenCentral()
mavenLocal()
mavenCentral()
jcenter()
google()
}
jacoco {
toolVersion = "0.8.2"
}
jacocoTestReport {
reports {
xml.enabled = true // coveralls plugin depends on xml format report
xml.enabled = true
html.enabled = true
}
}
}
sonarqube {
properties {
property 'sonar.exclusions', '**/jadx/samples/**/*,**/test-app/**/*'
property 'sonar.coverage.exclusions', '**/jadx/gui/**/*'
}
}
dependencyUpdates.resolutionStrategy = {
componentSelection { rules ->
rules.all { ComponentSelection selection ->
boolean rejected = ['alpha', 'beta', 'rc', 'cr', 'm', 'atlassian'].any { qualifier ->
selection.candidate.version ==~ /(?i).*[.-]${qualifier}[.\d-]*/
}
if (rejected) {
selection.reject('Release candidate')
}
}
}
}
task copyArtifacts(type: Sync, dependsOn: ['jadx-cli:installDist', 'jadx-gui:installDist']) {
destinationDir file("$buildDir/jadx")
['jadx-cli', 'jadx-gui'].each {
@@ -76,20 +95,29 @@ task pack(type: Zip, dependsOn: copyArtifacts) {
from copyArtifacts.destinationDir
}
task dist(dependsOn: pack) {
task copyExe(type: Copy, dependsOn: 'jadx-gui:createExe') {
group 'jadx'
description = 'Copy exe to build dir'
destinationDir buildDir
from tasks.getByPath('jadx-gui:createExe').outputs
include '*.exe'
}
task dist(dependsOn: [pack, copyExe]) {
group 'jadx'
description = 'Build jadx distribution zip'
}
task samples(dependsOn: 'jadx-samples:samples') {
group 'jadx'
}
task testAppCheck(dependsOn: 'jadx-test-app:testAppCheck') {
}
task pitest(overwrite: true, dependsOn: 'jadx-core:pitest') {
group 'jadx'
}
task cleanBuildDir(type: Delete) {
group 'jadx'
delete buildDir
}
@@ -97,6 +125,3 @@ build.dependsOn(dist, samples)
clean.dependsOn(cleanBuildDir)
task wrapper(type: Wrapper) {
gradleVersion = '2.7'
}
+1
View File
@@ -0,0 +1 @@
org.gradle.daemon=false
Binary file not shown.
+1 -1
View File
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.7-bin.zip
Vendored
+59 -51
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env bash
#!/usr/bin/env sh
##############################################################################
##
@@ -6,47 +6,6 @@
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# For Cygwin, ensure paths are in UNIX format before anything is touched.
if $cygwin ; then
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
fi
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
@@ -61,9 +20,49 @@ while [ -h "$PRG" ] ; do
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >&-
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >&-
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
@@ -90,7 +89,7 @@ location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
@@ -114,6 +113,7 @@ fi
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
@@ -154,11 +154,19 @@ if $cygwin ; then
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
APP_ARGS=$(save "$@")
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"
Vendored
+4 -10
View File
@@ -8,14 +8,14 @@
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
@@ -46,10 +46,9 @@ echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
@@ -60,11 +59,6 @@ set _SKIP=2
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
+5 -2
View File
@@ -5,8 +5,8 @@ applicationName = 'jadx'
dependencies {
compile(project(':jadx-core'))
compile 'com.beust:jcommander:1.47'
compile 'ch.qos.logback:logback-classic:1.1.2'
compile 'com.beust:jcommander:1.74'
compile 'ch.qos.logback:logback-classic:1.2.3'
}
applicationDistribution.with {
@@ -18,3 +18,6 @@ applicationDistribution.with {
}
}
startScripts {
defaultJvmOpts = ['-Xms128M', '-Xmx4g']
}
@@ -0,0 +1,105 @@
package jadx.cli;
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 com.beust.jcommander.JCommander;
import com.beust.jcommander.ParameterDescription;
import com.beust.jcommander.ParameterException;
import com.beust.jcommander.Parameterized;
import jadx.api.JadxDecompiler;
public class JCommanderWrapper<T> {
private final JCommander jc;
public JCommanderWrapper(T obj) {
this.jc = JCommander.newBuilder().addObject(obj).build();
}
public boolean parse(String[] args) {
try {
jc.parse(args);
return true;
} catch (ParameterException e) {
System.err.println("Arguments parse error: " + e.getMessage());
printUsage();
return false;
}
}
public void overrideProvided(T obj) {
List<ParameterDescription> fieldsParams = jc.getParameters();
List<ParameterDescription> parameters = new ArrayList<>(1 + fieldsParams.size());
parameters.add(jc.getMainParameterValue());
parameters.addAll(fieldsParams);
for (ParameterDescription parameter : parameters) {
if (parameter.isAssigned()) {
// copy assigned field value to obj
Parameterized parameterized = parameter.getParameterized();
Object val = parameterized.get(parameter.getObject());
parameterized.set(obj, val);
}
}
}
public void printUsage() {
// print usage in not sorted fields order (by default its sorted by description)
PrintStream out = System.out;
out.println();
out.println("jadx - dex to java decompiler, version: " + JadxDecompiler.getVersion());
out.println();
out.println("usage: jadx [options] " + jc.getMainParameterDescription());
out.println("options:");
List<ParameterDescription> params = jc.getParameters();
Map<String, ParameterDescription> paramsMap = new LinkedHashMap<>(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;
}
}
JadxCLIArgs args = new JadxCLIArgs();
Field[] fields = args.getClass().getDeclaredFields();
for (Field f : fields) {
String name = f.getName();
ParameterDescription p = paramsMap.get(name);
if (p == null) {
continue;
}
StringBuilder opt = new StringBuilder();
opt.append(" ").append(p.getNames());
addSpaces(opt, maxNamesLen - opt.length() + 3);
opt.append("- ").append(p.getDescription());
addDefaultValue(args, f, opt);
out.println(opt);
}
out.println("Example:");
out.println(" jadx -d out classes.dex");
}
private void addDefaultValue(JadxCLIArgs args, Field f, StringBuilder opt) {
Class<?> fieldType = f.getType();
if (fieldType == int.class) {
try {
int val = f.getInt(args);
opt.append(" (default: ").append(val).append(")");
} catch (Exception e) {
// ignore
}
}
}
private static void addSpaces(StringBuilder str, int count) {
for (int i = 0; i < count; i++) {
str.append(' ');
}
}
}
+22 -45
View File
@@ -1,68 +1,45 @@
package jadx.cli;
import jadx.api.JadxDecompiler;
import jadx.core.utils.exceptions.JadxException;
import java.io.File;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.JadxDecompiler;
import jadx.core.utils.exceptions.JadxArgsValidateException;
public class JadxCLI {
private static final Logger LOG = LoggerFactory.getLogger(JadxCLI.class);
public static void main(String[] args) throws JadxException {
public static void main(String[] args) {
int result = 0;
try {
JadxCLIArgs jadxArgs = new JadxCLIArgs();
if (processArgs(jadxArgs, args)) {
processAndSave(jadxArgs);
if (jadxArgs.processArgs(args)) {
result = processAndSave(jadxArgs);
}
} catch (Throwable e) {
} catch (Exception e) {
LOG.error("jadx error: {}", e.getMessage(), e);
System.exit(1);
result = 1;
} finally {
System.exit(result);
}
}
static void processAndSave(JadxCLIArgs jadxArgs) throws JadxException {
JadxDecompiler jadx = new JadxDecompiler(jadxArgs);
jadx.setOutputDir(jadxArgs.getOutDir());
jadx.loadFiles(jadxArgs.getInput());
static int processAndSave(JadxCLIArgs inputArgs) {
JadxDecompiler jadx = new JadxDecompiler(inputArgs.toJadxArgs());
try {
jadx.load();
} catch (JadxArgsValidateException e) {
LOG.error("Incorrect arguments: {}", e.getMessage());
return 1;
}
jadx.save();
if (jadx.getErrorsCount() != 0) {
int errorsCount = jadx.getErrorsCount();
if (errorsCount != 0) {
jadx.printErrorsReport();
LOG.error("finished with errors");
} else {
LOG.info("done");
}
}
static boolean processArgs(JadxCLIArgs jadxArgs, String[] args) throws JadxException {
if (!jadxArgs.processArgs(args)) {
return false;
}
if (jadxArgs.getInput().isEmpty()) {
LOG.error("Please specify input file");
jadxArgs.printUsage();
return false;
}
File outputDir = jadxArgs.getOutDir();
if (outputDir == null) {
String outDirName;
File file = jadxArgs.getInput().get(0);
String name = file.getName();
int pos = name.lastIndexOf('.');
if (pos != -1) {
outDirName = name.substring(0, pos);
} else {
outDirName = name + "-jadx-out";
}
LOG.info("output directory: {}", outDirName);
outputDir = new File(outDirName);
jadxArgs.setOutputDir(outputDir);
}
if (outputDir.exists() && !outputDir.isDirectory()) {
throw new JadxException("Output directory exists as file " + outputDir);
}
return true;
return errorsCount;
}
}
+107 -146
View File
@@ -1,38 +1,33 @@
package jadx.cli;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
import jadx.api.IJadxArgs;
import jadx.api.JadxDecompiler;
import jadx.core.utils.exceptions.JadxException;
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 com.beust.jcommander.Parameter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.beust.jcommander.IStringConverter;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.ParameterDescription;
import com.beust.jcommander.ParameterException;
import jadx.api.JadxArgs;
import jadx.api.JadxDecompiler;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.files.FileUtils;
public class JadxCLIArgs implements IJadxArgs {
public class JadxCLIArgs {
@Parameter(description = "<input file> (.dex, .apk, .jar or .class)")
protected List<String> files;
@Parameter(description = "<input file> (.apk, .dex, .jar or .class)")
protected List<String> files = new ArrayList<>(1);
@Parameter(names = {"-d", "--output-dir"}, description = "output directory")
protected String outDirName;
protected String outDir;
@Parameter(names = {"-j", "--threads-count"}, description = "processing threads count")
protected int threadsCount = Math.max(1, Runtime.getRuntime().availableProcessors() / 2);
@Parameter(names = {"-ds", "--output-dir-src"}, description = "output directory for sources")
protected String outDirSrc;
@Parameter(names = {"-dr", "--output-dir-res"}, description = "output directory for resources")
protected String outDirRes;
@Parameter(names = {"-r", "--no-res"}, description = "do not decode resources")
protected boolean skipResources = false;
@@ -43,30 +38,38 @@ public class JadxCLIArgs implements IJadxArgs {
@Parameter(names = {"-e", "--export-gradle"}, description = "save as android gradle project")
protected boolean exportAsGradleProject = false;
@Parameter(names = {"-j", "--threads-count"}, description = "processing threads count")
protected int threadsCount = JadxArgs.DEFAULT_THREADS_COUNT;
@Parameter(names = {"--show-bad-code"}, description = "show inconsistent code (incorrectly decompiled)")
protected boolean showInconsistentCode = false;
@Parameter(names = "--no-replace-consts", converter = InvertedBooleanConverter.class,
description = "don't replace constant value with matching constant field")
@Parameter(names = {"--no-imports"}, description = "disable use of imports, always write entire package name")
protected boolean useImports = true;
@Parameter(names = "--no-replace-consts", description = "don't replace constant value with matching constant field")
protected boolean replaceConsts = true;
@Parameter(names = {"--escape-unicode"}, description = "escape non latin characters in strings (with \\u)")
protected boolean escapeUnicode = false;
@Parameter(names = {"--respect-bytecode-access-modifiers"}, description = "don't change original access modifiers")
protected boolean respectBytecodeAccessModifiers = 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-min"}, description = "min length of name, renamed if shorter")
protected int deobfuscationMinLength = 3;
@Parameter(names = {"--deobf-max"}, description = "max length of name")
@Parameter(names = {"--deobf-max"}, description = "max length of name, renamed if longer")
protected int deobfuscationMaxLength = 64;
@Parameter(names = {"--deobf-rewrite-cfg"}, description = "force to save deobfuscation map")
protected boolean deobfuscationForceSave = false;
@Parameter(names = {"--deobf-use-sourcename"}, description = "use source file name as class name alias")
protected boolean deobfuscationUseSourceNameAsAlias = false;
protected boolean deobfuscationUseSourceNameAsAlias = true;
@Parameter(names = {"--cfg"}, description = "save methods control flow graph to dot file")
protected boolean cfgOutput = false;
@@ -80,53 +83,44 @@ public class JadxCLIArgs implements IJadxArgs {
@Parameter(names = {"-v", "--verbose"}, description = "verbose output")
protected boolean verbose = false;
@Parameter(names = {"--version"}, description = "print jadx version")
protected boolean printVersion = false;
@Parameter(names = {"-h", "--help"}, description = "print this help", help = true)
protected boolean printHelp = false;
private final List<File> input = new ArrayList<File>(1);
private File outputDir;
public boolean processArgs(String[] args) {
return parse(args) && process();
JCommanderWrapper<JadxCLIArgs> jcw = new JCommanderWrapper<>(this);
return jcw.parse(args) && process(jcw);
}
private boolean parse(String[] args) {
try {
new JCommander(this, args);
return true;
} catch (ParameterException e) {
System.err.println("Arguments parse error: " + e.getMessage());
printUsage();
/**
* Set values only for options provided in cmd.
* Used to merge saved options and options passed in command line.
*/
public boolean overrideProvided(String[] args) {
JCommanderWrapper<JadxCLIArgs> jcw = new JCommanderWrapper<>(new JadxCLIArgs());
if (!jcw.parse(args)) {
return false;
}
jcw.overrideProvided(this);
return process(jcw);
}
private boolean process() {
if (isPrintHelp()) {
printUsage();
private boolean process(JCommanderWrapper jcw) {
if (printHelp) {
jcw.printUsage();
return false;
}
if (printVersion) {
System.out.println(JadxDecompiler.getVersion());
return false;
}
try {
if (threadsCount <= 0) {
throw new JadxException("Threads count must be positive");
throw new JadxException("Threads count must be positive, got: " + threadsCount);
}
if (files != null) {
for (String fileName : files) {
File file = new File(fileName);
if (file.exists()) {
input.add(file);
} else {
throw new JadxException("File not found: " + file);
}
}
}
if (input.size() > 1) {
throw new JadxException("Only one input file is supported");
}
if (outDirName != null) {
outputDir = new File(outDirName);
}
if (isVerbose()) {
if (verbose) {
ch.qos.logback.classic.Logger rootLogger =
(ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
// remove INFO ThresholdFilter
@@ -137,155 +131,122 @@ public class JadxCLIArgs implements IJadxArgs {
}
} catch (JadxException e) {
System.err.println("ERROR: " + e.getMessage());
printUsage();
jcw.printUsage();
return false;
}
return true;
}
public void printUsage() {
JCommander jc = new JCommander(this);
// print usage in not sorted fields order (by default its sorted by description)
PrintStream out = System.out;
out.println();
out.println("jadx - dex to java decompiler, version: " + JadxDecompiler.getVersion());
out.println();
out.println("usage: jadx [options] " + jc.getMainParameterDescription());
out.println("options:");
List<ParameterDescription> params = jc.getParameters();
Map<String, ParameterDescription> paramsMap = new LinkedHashMap<String, ParameterDescription>(params.size());
int maxNamesLen = 0;
for (ParameterDescription p : params) {
paramsMap.put(p.getParameterized().getName(), p);
int len = p.getNames().length();
if (len > maxNamesLen) {
maxNamesLen = len;
}
}
Field[] fields = JadxCLIArgs.class.getDeclaredFields();
for (Field f : fields) {
String name = f.getName();
ParameterDescription p = paramsMap.get(name);
if (p == null) {
continue;
}
StringBuilder opt = new StringBuilder();
opt.append(' ').append(p.getNames());
addSpaces(opt, maxNamesLen - opt.length() + 2);
opt.append("- ").append(p.getDescription());
out.println(opt);
}
out.println("Example:");
out.println(" jadx -d out classes.dex");
public JadxArgs toJadxArgs() {
JadxArgs args = new JadxArgs();
args.setInputFiles(files.stream().map(FileUtils::toFile).collect(Collectors.toList()));
args.setOutDir(FileUtils.toFile(outDir));
args.setOutDirSrc(FileUtils.toFile(outDirSrc));
args.setOutDirRes(FileUtils.toFile(outDirRes));
args.setThreadsCount(threadsCount);
args.setSkipSources(skipSources);
args.setSkipResources(skipResources);
args.setFallbackMode(fallbackMode);
args.setShowInconsistentCode(showInconsistentCode);
args.setCfgOutput(cfgOutput);
args.setRawCFGOutput(rawCfgOutput);
args.setReplaceConsts(replaceConsts);
args.setDeobfuscationOn(deobfuscationOn);
args.setDeobfuscationForceSave(deobfuscationForceSave);
args.setDeobfuscationMinLength(deobfuscationMinLength);
args.setDeobfuscationMaxLength(deobfuscationMaxLength);
args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias);
args.setEscapeUnicode(escapeUnicode);
args.setRespectBytecodeAccModifiers(respectBytecodeAccessModifiers);
args.setExportAsGradleProject(exportAsGradleProject);
args.setUseImports(useImports);
return args;
}
private static void addSpaces(StringBuilder str, int count) {
for (int i = 0; i < count; i++) {
str.append(' ');
}
public List<String> getFiles() {
return files;
}
public static class InvertedBooleanConverter implements IStringConverter<Boolean> {
@Override
public Boolean convert(String value) {
return "false".equals(value);
}
public String getOutDir() {
return outDir;
}
public List<File> getInput() {
return input;
public String getOutDirSrc() {
return outDirSrc;
}
@Override
public File getOutDir() {
return outputDir;
public String getOutDirRes() {
return outDirRes;
}
public void setOutputDir(File outputDir) {
this.outputDir = outputDir;
}
public boolean isPrintHelp() {
return printHelp;
}
@Override
public boolean isSkipResources() {
return skipResources;
}
@Override
public boolean isSkipSources() {
return skipSources;
}
@Override
public int getThreadsCount() {
return threadsCount;
}
@Override
public boolean isCFGOutput() {
return cfgOutput;
}
@Override
public boolean isRawCFGOutput() {
return rawCfgOutput;
}
@Override
public boolean isFallbackMode() {
return fallbackMode;
}
@Override
public boolean isShowInconsistentCode() {
return showInconsistentCode;
}
@Override
public boolean isVerbose() {
return verbose;
public boolean isUseImports() {
return useImports;
}
@Override
public boolean isDeobfuscationOn() {
return deobfuscationOn;
}
@Override
public int getDeobfuscationMinLength() {
return deobfuscationMinLength;
}
@Override
public int getDeobfuscationMaxLength() {
return deobfuscationMaxLength;
}
@Override
public boolean isDeobfuscationForceSave() {
return deobfuscationForceSave;
}
@Override
public boolean useSourceNameAsClassAlias() {
public boolean isDeobfuscationUseSourceNameAsAlias() {
return deobfuscationUseSourceNameAsAlias;
}
@Override
public boolean escapeUnicode() {
return escapeUnicode;
}
@Override
public boolean isEscapeUnicode() {
return escapeUnicode;
}
public boolean isCfgOutput() {
return cfgOutput;
}
public boolean isRawCfgOutput() {
return rawCfgOutput;
}
public boolean isReplaceConsts() {
return replaceConsts;
}
@Override
public boolean isRespectBytecodeAccessModifiers() {
return respectBytecodeAccessModifiers;
}
public boolean isExportAsGradleProject() {
return exportAsGradleProject;
}
+1 -1
View File
@@ -5,7 +5,7 @@
<level>INFO</level>
</filter>
<encoder>
<pattern>%d{HH:mm:ss} %-5level - %msg%n</pattern>
<pattern>%-5level - %msg%n</pattern>
</encoder>
</appender>
@@ -1,22 +1,64 @@
package jadx.cli;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
public class JadxCLIArgsTest {
private static final Logger LOG = LoggerFactory.getLogger(JadxCLIArgsTest.class);
@Test
public void testInvertedBooleanOption() throws Exception {
public void testInvertedBooleanOption() {
assertThat(parse("--no-replace-consts").isReplaceConsts(), is(false));
assertThat(parse("").isReplaceConsts(), is(true));
}
@Test
public void testEscapeUnicodeOption() {
assertThat(parse("--escape-unicode").isEscapeUnicode(), is(true));
assertThat(parse("").isEscapeUnicode(), is(false));
}
@Test
public void testSrcOption() {
assertThat(parse("--no-src").isSkipSources(), is(true));
assertThat(parse("-s").isSkipSources(), is(true));
assertThat(parse("").isSkipSources(), is(false));
}
@Test
public void testOptionsOverride() {
assertThat(override(new JadxCLIArgs(), "--no-imports").isUseImports(), is(false));
assertThat(override(new JadxCLIArgs(), "").isUseImports(), is(true));
JadxCLIArgs args = new JadxCLIArgs();
args.useImports = false;
assertThat(override(args, "--no-imports").isUseImports(), is(false));
args = new JadxCLIArgs();
args.useImports = false;
assertThat(override(args, "").isUseImports(), is(false));
}
private JadxCLIArgs parse(String... args) {
JadxCLIArgs jadxArgs = new JadxCLIArgs();
return parse(new JadxCLIArgs(), args);
}
private JadxCLIArgs parse(JadxCLIArgs jadxArgs, String... args) {
boolean res = jadxArgs.processArgs(args);
assertThat(res, is(true));
LOG.info("Jadx args: {}", jadxArgs.toJadxArgs());
return jadxArgs;
}
private JadxCLIArgs override(JadxCLIArgs jadxArgs, String... args) {
boolean res = jadxArgs.overrideProvided(args);
assertThat(res, is(true));
LOG.info("Jadx args: {}", jadxArgs.toJadxArgs());
return jadxArgs;
}
}
+9 -20
View File
@@ -1,28 +1,17 @@
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'
compile 'uk.com.robust-it:cloning:1.9.2'
compile files('lib/dx-1.16.jar')
compile 'commons-io:commons-io:2.6'
compile 'org.ow2.asm:asm:7.0'
compile 'org.jetbrains:annotations:16.0.3'
compile 'uk.com.robust-it:cloning:1.9.11'
testCompile 'org.smali:smali:2.0.3'
testCompile 'org.smali:smali:2.2.5'
testCompile 'org.smali:baksmali:2.2.5'
testCompile 'org.apache.commons:commons-lang3:3.8.1'
}
task packTests(type: Jar) {
classifier = 'tests'
from sourceSets.test.output
}
pitest {
excludedMethods = ['toString']
threads = 4
enableDefaultIncrementalAnalysis = true
outputFormats = ['XML', 'HTML']
jvmArgs = ['-Xmx12G']
}
Binary file not shown.
Binary file not shown.
@@ -1,45 +0,0 @@
package jadx.api;
import java.io.File;
public interface IJadxArgs {
File getOutDir();
int getThreadsCount();
boolean isCFGOutput();
boolean isRawCFGOutput();
boolean isFallbackMode();
boolean isShowInconsistentCode();
boolean isVerbose();
boolean isSkipResources();
boolean isSkipSources();
boolean isDeobfuscationOn();
int getDeobfuscationMinLength();
int getDeobfuscationMaxLength();
boolean isDeobfuscationForceSave();
boolean useSourceNameAsClassAlias();
boolean escapeUnicode();
/**
* Replace constant values with static final fields with same value
*/
boolean isReplaceConsts();
/**
* Save as gradle project
*/
boolean isExportAsGradleProject();
}
+97 -28
View File
@@ -1,19 +1,33 @@
package jadx.api;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class JadxArgs implements IJadxArgs {
public class JadxArgs {
private File outDir = new File("jadx-output");
private int threadsCount = Math.max(1, Runtime.getRuntime().availableProcessors() - 1);
public static final int DEFAULT_THREADS_COUNT = Math.max(1, Runtime.getRuntime().availableProcessors() / 2);
public static final String DEFAULT_OUT_DIR = "jadx-output";
public static final String DEFAULT_SRC_DIR = "sources";
public static final String DEFAULT_RES_DIR = "resources";
private List<File> inputFiles = new ArrayList<>(1);
private File outDir;
private File outDirSrc;
private File outDirRes;
private int threadsCount = DEFAULT_THREADS_COUNT;
private boolean cfgOutput = false;
private boolean rawCFGOutput = false;
private boolean isVerbose = false;
private boolean fallbackMode = false;
private boolean showInconsistentCode = false;
private boolean useImports = true;
private boolean isSkipResources = false;
private boolean isSkipSources = false;
@@ -26,9 +40,27 @@ public class JadxArgs implements IJadxArgs {
private boolean escapeUnicode = false;
private boolean replaceConsts = true;
private boolean respectBytecodeAccModifiers = false;
private boolean exportAsGradleProject = false;
@Override
public JadxArgs() {
// use default options
}
public void setRootDir(File rootDir) {
setOutDir(rootDir);
setOutDirSrc(new File(rootDir, DEFAULT_SRC_DIR));
setOutDirRes(new File(rootDir, DEFAULT_RES_DIR));
}
public List<File> getInputFiles() {
return inputFiles;
}
public void setInputFiles(List<File> inputFiles) {
this.inputFiles = inputFiles;
}
public File getOutDir() {
return outDir;
}
@@ -37,7 +69,22 @@ public class JadxArgs implements IJadxArgs {
this.outDir = outDir;
}
@Override
public File getOutDirSrc() {
return outDirSrc;
}
public void setOutDirSrc(File outDirSrc) {
this.outDirSrc = outDirSrc;
}
public File getOutDirRes() {
return outDirRes;
}
public void setOutDirRes(File outDirRes) {
this.outDirRes = outDirRes;
}
public int getThreadsCount() {
return threadsCount;
}
@@ -46,8 +93,7 @@ public class JadxArgs implements IJadxArgs {
this.threadsCount = threadsCount;
}
@Override
public boolean isCFGOutput() {
public boolean isCfgOutput() {
return cfgOutput;
}
@@ -55,7 +101,6 @@ public class JadxArgs implements IJadxArgs {
this.cfgOutput = cfgOutput;
}
@Override
public boolean isRawCFGOutput() {
return rawCFGOutput;
}
@@ -64,7 +109,6 @@ public class JadxArgs implements IJadxArgs {
this.rawCFGOutput = rawCFGOutput;
}
@Override
public boolean isFallbackMode() {
return fallbackMode;
}
@@ -73,7 +117,6 @@ public class JadxArgs implements IJadxArgs {
this.fallbackMode = fallbackMode;
}
@Override
public boolean isShowInconsistentCode() {
return showInconsistentCode;
}
@@ -82,16 +125,14 @@ public class JadxArgs implements IJadxArgs {
this.showInconsistentCode = showInconsistentCode;
}
@Override
public boolean isVerbose() {
return isVerbose;
public boolean isUseImports() {
return useImports;
}
public void setVerbose(boolean verbose) {
isVerbose = verbose;
public void setUseImports(boolean useImports) {
this.useImports = useImports;
}
@Override
public boolean isSkipResources() {
return isSkipResources;
}
@@ -100,7 +141,6 @@ public class JadxArgs implements IJadxArgs {
isSkipResources = skipResources;
}
@Override
public boolean isSkipSources() {
return isSkipSources;
}
@@ -109,7 +149,6 @@ public class JadxArgs implements IJadxArgs {
isSkipSources = skipSources;
}
@Override
public boolean isDeobfuscationOn() {
return isDeobfuscationOn;
}
@@ -118,7 +157,6 @@ public class JadxArgs implements IJadxArgs {
isDeobfuscationOn = deobfuscationOn;
}
@Override
public boolean isDeobfuscationForceSave() {
return isDeobfuscationForceSave;
}
@@ -127,8 +165,7 @@ public class JadxArgs implements IJadxArgs {
isDeobfuscationForceSave = deobfuscationForceSave;
}
@Override
public boolean useSourceNameAsClassAlias() {
public boolean isUseSourceNameAsClassAlias() {
return useSourceNameAsClassAlias;
}
@@ -136,7 +173,6 @@ public class JadxArgs implements IJadxArgs {
this.useSourceNameAsClassAlias = useSourceNameAsClassAlias;
}
@Override
public int getDeobfuscationMinLength() {
return deobfuscationMinLength;
}
@@ -145,7 +181,6 @@ public class JadxArgs implements IJadxArgs {
this.deobfuscationMinLength = deobfuscationMinLength;
}
@Override
public int getDeobfuscationMaxLength() {
return deobfuscationMaxLength;
}
@@ -154,8 +189,7 @@ public class JadxArgs implements IJadxArgs {
this.deobfuscationMaxLength = deobfuscationMaxLength;
}
@Override
public boolean escapeUnicode() {
public boolean isEscapeUnicode() {
return escapeUnicode;
}
@@ -163,7 +197,6 @@ public class JadxArgs implements IJadxArgs {
this.escapeUnicode = escapeUnicode;
}
@Override
public boolean isReplaceConsts() {
return replaceConsts;
}
@@ -172,7 +205,14 @@ public class JadxArgs implements IJadxArgs {
this.replaceConsts = replaceConsts;
}
@Override
public boolean isRespectBytecodeAccModifiers() {
return respectBytecodeAccModifiers;
}
public void setRespectBytecodeAccModifiers(boolean respectBytecodeAccModifiers) {
this.respectBytecodeAccModifiers = respectBytecodeAccModifiers;
}
public boolean isExportAsGradleProject() {
return exportAsGradleProject;
}
@@ -180,4 +220,33 @@ public class JadxArgs implements IJadxArgs {
public void setExportAsGradleProject(boolean exportAsGradleProject) {
this.exportAsGradleProject = exportAsGradleProject;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("JadxArgs{");
sb.append("inputFiles=").append(inputFiles);
sb.append(", outDir=").append(outDir);
sb.append(", outDirSrc=").append(outDirSrc);
sb.append(", outDirRes=").append(outDirRes);
sb.append(", threadsCount=").append(threadsCount);
sb.append(", cfgOutput=").append(cfgOutput);
sb.append(", rawCFGOutput=").append(rawCFGOutput);
sb.append(", fallbackMode=").append(fallbackMode);
sb.append(", showInconsistentCode=").append(showInconsistentCode);
sb.append(", useImports=").append(useImports);
sb.append(", isSkipResources=").append(isSkipResources);
sb.append(", isSkipSources=").append(isSkipSources);
sb.append(", isDeobfuscationOn=").append(isDeobfuscationOn);
sb.append(", isDeobfuscationForceSave=").append(isDeobfuscationForceSave);
sb.append(", useSourceNameAsClassAlias=").append(useSourceNameAsClassAlias);
sb.append(", deobfuscationMinLength=").append(deobfuscationMinLength);
sb.append(", deobfuscationMaxLength=").append(deobfuscationMaxLength);
sb.append(", escapeUnicode=").append(escapeUnicode);
sb.append(", replaceConsts=").append(replaceConsts);
sb.append(", respectBytecodeAccModifiers=").append(respectBytecodeAccModifiers);
sb.append(", exportAsGradleProject=").append(exportAsGradleProject);
sb.append('}');
return sb.toString();
}
}
@@ -0,0 +1,108 @@
package jadx.api;
import java.io.File;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.utils.exceptions.JadxArgsValidateException;
public class JadxArgsValidator {
private static final Logger LOG = LoggerFactory.getLogger(JadxArgsValidator.class);
public static void validate(JadxArgs args) {
checkInputFiles(args);
validateOutDirs(args);
if (LOG.isDebugEnabled()) {
LOG.debug("Effective jadx args: {}", args);
}
}
private static void checkInputFiles(JadxArgs args) {
List<File> inputFiles = args.getInputFiles();
if (inputFiles.isEmpty()) {
throw new JadxArgsValidateException("Please specify input file");
}
if (inputFiles.size() > 1) {
for (File inputFile : inputFiles) {
String fileName = inputFile.getName();
if (fileName.startsWith("--")) {
throw new JadxArgsValidateException("Unknown argument: " + fileName);
}
}
throw new JadxArgsValidateException("Only one input file supported");
}
for (File file : inputFiles) {
checkFile(file);
}
}
private static void validateOutDirs(JadxArgs args) {
File outDir = args.getOutDir();
File srcDir = args.getOutDirSrc();
File resDir = args.getOutDirRes();
if (outDir == null) {
if (srcDir != null) {
outDir = srcDir;
} else if (resDir != null) {
outDir = resDir;
} else {
outDir = makeDirFromInput(args);
}
}
args.setOutDir(outDir);
setFromOut(args);
checkDir(args.getOutDir());
checkDir(args.getOutDirSrc());
checkDir(args.getOutDirRes());
}
@NotNull
private static File makeDirFromInput(JadxArgs args) {
File outDir;
String outDirName;
File file = args.getInputFiles().get(0);
String name = file.getName();
int pos = name.lastIndexOf('.');
if (pos != -1) {
outDirName = name.substring(0, pos);
} else {
outDirName = name + "-" + JadxArgs.DEFAULT_OUT_DIR;
}
LOG.info("output directory: {}", outDirName);
outDir = new File(outDirName);
return outDir;
}
private static void setFromOut(JadxArgs args) {
if (args.getOutDirSrc() == null) {
args.setOutDirSrc(new File(args.getOutDir(), JadxArgs.DEFAULT_SRC_DIR));
}
if (args.getOutDirRes() == null) {
args.setOutDirRes(new File(args.getOutDir(), JadxArgs.DEFAULT_RES_DIR));
}
}
private static void checkFile(File file) {
if (!file.exists()) {
throw new JadxArgsValidateException("File not found " + file.getAbsolutePath());
}
if (file.isDirectory()) {
throw new JadxArgsValidateException("Expected file but found directory instead: " + file.getAbsolutePath());
}
}
private static void checkDir(File dir) {
if (dir != null && dir.exists() && !dir.isDirectory()) {
throw new JadxArgsValidateException("Output directory exists as file " + dir);
}
}
private JadxArgsValidator() {
}
}
@@ -1,5 +1,20 @@
package jadx.api;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.Jadx;
import jadx.core.ProcessClass;
import jadx.core.codegen.CodeGen;
@@ -11,38 +26,24 @@ import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.dex.visitors.SaveCode;
import jadx.core.export.ExportGradleProject;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.InputFile;
import jadx.core.xmlgen.BinaryXMLParser;
import jadx.core.xmlgen.ResourcesSaver;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Jadx API usage example:
* <pre><code>
* JadxDecompiler jadx = new JadxDecompiler();
* jadx.loadFile(new File("classes.dex"));
* jadx.setOutputDir(new File("out"));
* jadx.save();
* JadxArgs args = new JadxArgs();
* args.getInputFiles().add(new File("test.apk"));
* args.setOutDir(new File("jadx-test-output"));
*
* JadxDecompiler jadx = new JadxDecompiler(args);
* jadx.load();
* jadx.save();
* </code></pre>
* <p/>
* Instead of 'save()' you can get list of decompiled classes:
* <p>
* Instead of 'save()' you can iterate over decompiled classes:
* <pre><code>
* for(JavaClass cls : jadx.getClasses()) {
* System.out.println(cls.getCode());
@@ -52,10 +53,9 @@ import org.slf4j.LoggerFactory;
public final class JadxDecompiler {
private static final Logger LOG = LoggerFactory.getLogger(JadxDecompiler.class);
private final IJadxArgs args;
private final List<InputFile> inputFiles = new ArrayList<InputFile>();
private JadxArgs args;
private File outDir;
private final List<InputFile> inputFiles = new ArrayList<>();
private RootNode root;
private List<IDexTreeVisitor> passes;
@@ -66,32 +66,38 @@ public final class JadxDecompiler {
private BinaryXMLParser xmlParser;
private Map<ClassNode, JavaClass> classesMap = new HashMap<ClassNode, JavaClass>();
private Map<MethodNode, JavaMethod> methodsMap = new HashMap<MethodNode, JavaMethod>();
private Map<FieldNode, JavaField> fieldsMap = new HashMap<FieldNode, JavaField>();
private Map<ClassNode, JavaClass> classesMap = new ConcurrentHashMap<>();
private Map<MethodNode, JavaMethod> methodsMap = new ConcurrentHashMap<>();
private Map<FieldNode, JavaField> fieldsMap = new ConcurrentHashMap<>();
public JadxDecompiler() {
this(new JadxArgs());
}
public JadxDecompiler(IJadxArgs jadxArgs) {
this.args = jadxArgs;
this.outDir = jadxArgs.getOutDir();
reset();
init();
public JadxDecompiler(JadxArgs args) {
this.args = args;
}
public void setOutputDir(File outDir) {
this.outDir = outDir;
public void load() {
reset();
JadxArgsValidator.validate(args);
init();
LOG.info("loading ...");
loadFiles(args.getInputFiles());
root = new RootNode(args);
root.load(inputFiles);
root.initClassPath();
root.loadResources(getResources());
initVisitors();
}
void init() {
if (outDir == null) {
outDir = new JadxArgs().getOutDir();
}
this.passes = Jadx.getPassesList(args, outDir);
this.codeGen = new CodeGen(args);
this.passes = Jadx.getPassesList(args);
this.codeGen = new CodeGen();
}
void reset() {
@@ -107,23 +113,18 @@ public final class JadxDecompiler {
return Jadx.getVersion();
}
public void loadFile(File file) throws JadxException {
loadFiles(Collections.singletonList(file));
}
public void loadFiles(List<File> files) throws JadxException {
private void loadFiles(List<File> files) {
if (files.isEmpty()) {
throw new JadxException("Empty file list");
throw new JadxRuntimeException("Empty file list");
}
inputFiles.clear();
for (File file : files) {
try {
InputFile.addFilesFrom(file, inputFiles);
} catch (IOException e) {
throw new JadxException("Error load file: " + file, e);
InputFile.addFilesFrom(file, inputFiles, args.isSkipSources());
} catch (Exception e) {
throw new JadxRuntimeException("Error load file: " + file, e);
}
}
parse();
}
public void save() {
@@ -139,12 +140,13 @@ public final class JadxDecompiler {
}
private void save(boolean saveSources, boolean saveResources) {
ExecutorService ex = getSaveExecutor(saveSources, saveResources);
ex.shutdown();
try {
ExecutorService ex = getSaveExecutor(saveSources, saveResources);
ex.shutdown();
ex.awaitTermination(1, TimeUnit.DAYS);
} catch (InterruptedException e) {
throw new JadxRuntimeException("Save interrupted", e);
LOG.error("Save interrupted", e);
Thread.currentThread().interrupt();
}
}
@@ -165,20 +167,20 @@ public final class JadxDecompiler {
File sourcesOutDir;
File resOutDir;
if (args.isExportAsGradleProject()) {
ExportGradleProject export = new ExportGradleProject(root, outDir);
ExportGradleProject export = new ExportGradleProject(root, args.getOutDir());
export.init();
sourcesOutDir = export.getSrcOutDir();
resOutDir = export.getResOutDir();
} else {
sourcesOutDir = outDir;
resOutDir = outDir;
}
if (saveSources) {
appendSourcesSave(executor, sourcesOutDir);
sourcesOutDir = args.getOutDirSrc();
resOutDir = args.getOutDirRes();
}
if (saveResources) {
appendResourcesSave(executor, resOutDir);
}
if (saveSources) {
appendSourcesSave(executor, sourcesOutDir);
}
return executor;
}
@@ -188,16 +190,17 @@ public final class JadxDecompiler {
}
}
private void appendSourcesSave(ExecutorService executor, final File outDir) {
for (final JavaClass cls : getClasses()) {
private void appendSourcesSave(ExecutorService executor, File outDir) {
for (JavaClass cls : getClasses()) {
if (cls.getClassNode().contains(AFlag.DONT_GENERATE)) {
continue;
}
executor.execute(new Runnable() {
@Override
public void run() {
executor.execute(() -> {
try {
cls.decompile();
SaveCode.save(outDir, args, cls.getClassNode());
} catch (Exception e) {
LOG.error("Error saving class: {}", cls.getFullName(), e);
}
});
}
@@ -209,7 +212,7 @@ public final class JadxDecompiler {
}
if (classes == null) {
List<ClassNode> classNodeList = root.getClasses(false);
List<JavaClass> clsList = new ArrayList<JavaClass>(classNodeList.size());
List<JavaClass> clsList = new ArrayList<>(classNodeList.size());
classesMap.clear();
for (ClassNode classNode : classNodeList) {
JavaClass javaClass = new JavaClass(classNode, this);
@@ -236,28 +239,19 @@ public final class JadxDecompiler {
if (classList.isEmpty()) {
return Collections.emptyList();
}
Map<String, List<JavaClass>> map = new HashMap<String, List<JavaClass>>();
Map<String, List<JavaClass>> map = new HashMap<>();
for (JavaClass javaClass : classList) {
String pkg = javaClass.getPackage();
List<JavaClass> clsList = map.get(pkg);
if (clsList == null) {
clsList = new ArrayList<JavaClass>();
map.put(pkg, clsList);
}
List<JavaClass> clsList = map.computeIfAbsent(pkg, k -> new ArrayList<>());
clsList.add(javaClass);
}
List<JavaPackage> packages = new ArrayList<JavaPackage>(map.size());
List<JavaPackage> packages = new ArrayList<>(map.size());
for (Map.Entry<String, List<JavaClass>> entry : map.entrySet()) {
packages.add(new JavaPackage(entry.getKey(), entry.getValue()));
}
Collections.sort(packages);
for (JavaPackage pkg : packages) {
Collections.sort(pkg.getClasses(), new Comparator<JavaClass>() {
@Override
public int compare(JavaClass o1, JavaClass o2) {
return o1.getName().compareTo(o2.getName());
}
});
pkg.getClasses().sort(Comparator.comparing(JavaClass::getName));
}
return Collections.unmodifiableList(packages);
}
@@ -269,6 +263,13 @@ public final class JadxDecompiler {
return root.getErrorsCounter().getErrorCount();
}
public int getWarnsCount() {
if (root == null) {
return 0;
}
return root.getErrorsCounter().getWarnsCount();
}
public void printErrorsReport() {
if (root == null) {
return;
@@ -277,21 +278,6 @@ public final class JadxDecompiler {
root.getErrorsCounter().printReport();
}
void parse() throws DecodeException {
reset();
init();
root = new RootNode(args);
LOG.info("loading ...");
root.load(inputFiles);
root.initClassPath();
root.loadResources(getResources());
root.initAppResClass();
initVisitors();
}
private void initVisitors() {
for (IDexTreeVisitor pass : passes) {
try {
@@ -325,11 +311,39 @@ public final class JadxDecompiler {
return methodsMap;
}
JavaMethod getJavaMethodByNode(MethodNode mth) {
JavaMethod javaMethod = methodsMap.get(mth);
if (javaMethod != null) {
return javaMethod;
}
// parent class not loaded yet
JavaClass javaClass = classesMap.get(mth.getParentClass());
if (javaClass != null) {
javaClass.decompile();
return methodsMap.get(mth);
}
return null;
}
Map<FieldNode, JavaField> getFieldsMap() {
return fieldsMap;
}
public IJadxArgs getArgs() {
JavaField getJavaFieldByNode(FieldNode fld) {
JavaField javaField = fieldsMap.get(fld);
if (javaField != null) {
return javaField;
}
// parent class not loaded yet
JavaClass javaClass = classesMap.get(fld.getParentClass());
if (javaClass != null) {
javaClass.decompile();
return fieldsMap.get(fld);
}
return null;
}
public JadxArgs getArgs() {
return args;
}
@@ -337,5 +351,4 @@ public final class JadxDecompiler {
public String toString() {
return "jadx decompiler " + getVersion();
}
}
+25 -31
View File
@@ -1,13 +1,5 @@
package jadx.api;
import jadx.core.codegen.CodeWriter;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@@ -17,6 +9,14 @@ import java.util.Map;
import org.jetbrains.annotations.Nullable;
import jadx.core.codegen.CodeWriter;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
public final class JavaClass implements JavaNode {
private final JadxDecompiler decompiler;
@@ -64,6 +64,10 @@ public final class JavaClass implements JavaNode {
}
}
public synchronized void unload() {
cls.unload();
}
ClassNode getClassNode() {
return cls;
}
@@ -72,7 +76,7 @@ public final class JavaClass implements JavaNode {
JadxDecompiler rootDecompiler = getRootDecompiler();
int inClsCount = cls.getInnerClasses().size();
if (inClsCount != 0) {
List<JavaClass> list = new ArrayList<JavaClass>(inClsCount);
List<JavaClass> list = new ArrayList<>(inClsCount);
for (ClassNode inner : cls.getInnerClasses()) {
if (!inner.contains(AFlag.DONT_GENERATE)) {
JavaClass javaClass = new JavaClass(inner, this);
@@ -86,7 +90,7 @@ public final class JavaClass implements JavaNode {
int fieldsCount = cls.getFields().size();
if (fieldsCount != 0) {
List<JavaField> flds = new ArrayList<JavaField>(fieldsCount);
List<JavaField> flds = new ArrayList<>(fieldsCount);
for (FieldNode f : cls.getFields()) {
if (!f.contains(AFlag.DONT_GENERATE)) {
JavaField javaField = new JavaField(f, this);
@@ -99,7 +103,7 @@ public final class JavaClass implements JavaNode {
int methodsCount = cls.getMethods().size();
if (methodsCount != 0) {
List<JavaMethod> mths = new ArrayList<JavaMethod>(methodsCount);
List<JavaMethod> mths = new ArrayList<>(methodsCount);
for (MethodNode m : cls.getMethods()) {
if (!m.contains(AFlag.DONT_GENERATE)) {
JavaMethod javaMethod = new JavaMethod(this, m);
@@ -107,12 +111,7 @@ public final class JavaClass implements JavaNode {
rootDecompiler.getMethodsMap().put(m, javaMethod);
}
}
Collections.sort(mths, new Comparator<JavaMethod>() {
@Override
public int compare(JavaMethod o1, JavaMethod o2) {
return o1.getName().compareTo(o2.getName());
}
});
mths.sort(Comparator.comparing(JavaMethod::getName));
this.methods = Collections.unmodifiableList(mths);
}
}
@@ -126,7 +125,11 @@ public final class JavaClass implements JavaNode {
private Map<CodePosition, Object> getCodeAnnotations() {
decompile();
return cls.getCode().getAnnotations();
CodeWriter code = cls.getCode();
if (code == null) {
return Collections.emptyMap();
}
return code.getAnnotations();
}
public Map<CodePosition, JavaNode> getUsageMap() {
@@ -134,7 +137,7 @@ public final class JavaClass implements JavaNode {
if (map.isEmpty() || decompiler == null) {
return Collections.emptyMap();
}
Map<CodePosition, JavaNode> resultMap = new HashMap<CodePosition, JavaNode>(map.size());
Map<CodePosition, JavaNode> resultMap = new HashMap<>(map.size());
for (Map.Entry<CodePosition, Object> entry : map.entrySet()) {
CodePosition codePosition = entry.getKey();
Object obj = entry.getValue();
@@ -157,10 +160,10 @@ public final class JavaClass implements JavaNode {
return getRootDecompiler().getClassesMap().get(obj);
}
if (obj instanceof MethodNode) {
return getRootDecompiler().getMethodsMap().get(obj);
return getRootDecompiler().getJavaMethodByNode(((MethodNode) obj));
}
if (obj instanceof FieldNode) {
return getRootDecompiler().getFieldsMap().get(obj);
return getRootDecompiler().getJavaFieldByNode((FieldNode) obj);
}
return null;
}
@@ -178,15 +181,6 @@ public final class JavaClass implements JavaNode {
return convertNode(obj);
}
@Nullable
public CodePosition getDefinitionPosition(int line, int offset) {
JavaNode javaNode = getJavaNodeAtPosition(line, offset);
if (javaNode == null) {
return null;
}
return getDefinitionPosition(javaNode);
}
@Nullable
public CodePosition getDefinitionPosition(JavaNode javaNode) {
JavaClass jCls = javaNode.getTopParentClass();
@@ -262,6 +256,6 @@ public final class JavaClass implements JavaNode {
@Override
public String toString() {
return cls.getFullName() + "[ " + getFullName() + " ]";
return getFullName();
}
}
@@ -1,11 +1,11 @@
package jadx.api;
import java.util.List;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.MethodNode;
import java.util.List;
public final class JavaMethod implements JavaNode {
private final MethodNode mth;
private final JavaClass parent;
@@ -1,9 +1,10 @@
package jadx.api;
import jadx.core.xmlgen.ResContainer;
import java.io.File;
import jadx.core.utils.files.ZipSecurity;
import jadx.core.xmlgen.ResContainer;
public class ResourceFile {
public static final class ZipRef {
@@ -34,7 +35,14 @@ public class ResourceFile {
private final ResourceType type;
private ZipRef zipRef;
ResourceFile(JadxDecompiler decompiler, String name, ResourceType type) {
public static ResourceFile createResourceFile(JadxDecompiler decompiler, String name, ResourceType type) {
if (!ZipSecurity.isValidZipEntryName(name)) {
return null;
}
return new ResourceFile(decompiler, name, type);
}
protected ResourceFile(JadxDecompiler decompiler, String name, ResourceType type) {
this.decompiler = decompiler;
this.name = name;
this.type = type;
@@ -56,7 +64,7 @@ public class ResourceFile {
this.zipRef = zipRef;
}
ZipRef getZipRef() {
public ZipRef getZipRef() {
return zipRef;
}
@@ -4,7 +4,6 @@ import jadx.core.codegen.CodeWriter;
import jadx.core.xmlgen.ResContainer;
public class ResourceFileContent extends ResourceFile {
private final CodeWriter content;
public ResourceFileContent(String name, ResourceType type, CodeWriter content) {
@@ -14,6 +13,6 @@ public class ResourceFileContent extends ResourceFile {
@Override
public ResContainer loadContent() {
return ResContainer.singleFile(getName(), content);
return ResContainer.textResource(getName(), content);
}
}
@@ -30,21 +30,4 @@ public enum ResourceType {
}
return UNKNOWN;
}
public static boolean isSupportedForUnpack(ResourceType type) {
switch (type) {
case CODE:
case LIB:
case FONT:
case UNKNOWN:
return false;
case MANIFEST:
case XML:
case ARSC:
case IMG:
return true;
}
return false;
}
}
@@ -1,16 +1,9 @@
package jadx.api;
import jadx.api.ResourceFile.ZipRef;
import jadx.core.codegen.CodeWriter;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.files.InputFile;
import jadx.core.xmlgen.ResContainer;
import jadx.core.xmlgen.ResTableParser;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
@@ -22,16 +15,23 @@ import java.util.zip.ZipFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.ResourceFile.ZipRef;
import jadx.core.codegen.CodeWriter;
import jadx.core.utils.Utils;
import jadx.core.utils.android.Res9patchStreamDecoder;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.files.InputFile;
import jadx.core.utils.files.ZipSecurity;
import jadx.core.xmlgen.ResContainer;
import jadx.core.xmlgen.ResTableParser;
import static jadx.core.utils.files.FileUtils.READ_BUFFER_SIZE;
import static jadx.core.utils.files.FileUtils.close;
import static jadx.core.utils.files.FileUtils.copyStream;
// TODO: move to core package
public final class ResourcesLoader {
private static final Logger LOG = LoggerFactory.getLogger(ResourcesLoader.class);
private static final int LOAD_SIZE_LIMIT = 10 * 1024 * 1024;
private final JadxDecompiler jadxRef;
ResourcesLoader(JadxDecompiler jadxRef) {
@@ -39,108 +39,114 @@ public final class ResourcesLoader {
}
List<ResourceFile> load(List<InputFile> inputFiles) {
List<ResourceFile> list = new ArrayList<ResourceFile>(inputFiles.size());
List<ResourceFile> list = new ArrayList<>(inputFiles.size());
for (InputFile file : inputFiles) {
loadFile(list, file.getFile());
}
return list;
}
public interface ResourceDecoder {
ResContainer decode(long size, InputStream is) throws IOException;
public interface ResourceDecoder<T> {
T decode(long size, InputStream is) throws IOException;
}
public static ResContainer decodeStream(ResourceFile rf, ResourceDecoder decoder) throws JadxException {
ZipRef zipRef = rf.getZipRef();
if (zipRef == null) {
return null;
}
ZipFile zipFile = null;
InputStream inputStream = null;
ResContainer result = null;
public static <T> T decodeStream(ResourceFile rf, ResourceDecoder<T> decoder) throws JadxException {
try {
zipFile = new ZipFile(zipRef.getZipFile());
ZipEntry entry = zipFile.getEntry(zipRef.getEntryName());
if (entry == null) {
throw new IOException("Zip entry not found: " + zipRef);
ZipRef zipRef = rf.getZipRef();
if (zipRef == null) {
File file = new File(rf.getName());
try (InputStream inputStream = new BufferedInputStream(new FileInputStream(file))) {
return decoder.decode(file.length(), inputStream);
}
} else {
try (ZipFile zipFile = new ZipFile(zipRef.getZipFile())) {
ZipEntry entry = zipFile.getEntry(zipRef.getEntryName());
if (entry == null) {
throw new IOException("Zip entry not found: " + zipRef);
}
if (!ZipSecurity.isValidZipEntry(entry)) {
return null;
}
try (InputStream inputStream = new BufferedInputStream(zipFile.getInputStream(entry))) {
return decoder.decode(entry.getSize(), inputStream);
}
}
}
inputStream = new BufferedInputStream(zipFile.getInputStream(entry));
result = decoder.decode(entry.getSize(), inputStream);
} catch (Exception e) {
throw new JadxException("Error decode: " + zipRef.getEntryName(), e);
} finally {
try {
if (zipFile != null) {
zipFile.close();
}
} catch (Exception e) {
LOG.error("Error close zip file: {}", zipRef, e);
}
close(inputStream);
throw new JadxException("Error decode: " + rf.getName(), e);
}
return result;
}
static ResContainer loadContent(final JadxDecompiler jadxRef, final ResourceFile rf) {
static ResContainer loadContent(JadxDecompiler jadxRef, ResourceFile rf) {
try {
return decodeStream(rf, new ResourceDecoder() {
@Override
public ResContainer decode(long size, InputStream is) throws IOException {
return loadContent(jadxRef, rf, is, size);
}
});
return decodeStream(rf, (size, is) -> loadContent(jadxRef, rf, 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 ResContainer.singleFile(rf.getName(), cw);
return ResContainer.textResource(rf.getName(), cw);
}
}
private static ResContainer loadContent(JadxDecompiler jadxRef, ResourceFile rf,
InputStream inputStream, long size) throws IOException {
InputStream inputStream) throws IOException {
switch (rf.getType()) {
case MANIFEST:
case XML:
return ResContainer.singleFile(rf.getName(),
jadxRef.getXmlParser().parse(inputStream));
CodeWriter content = jadxRef.getXmlParser().parse(inputStream);
return ResContainer.textResource(rf.getName(), content);
case ARSC:
return new ResTableParser().decodeFiles(inputStream);
case IMG:
return ResContainer.singleImageFile(rf.getName(), inputStream);
return decodeImage(rf, inputStream);
default:
return ResContainer.resourceFileLink(rf);
}
if (size > LOAD_SIZE_LIMIT) {
return ResContainer.singleFile(rf.getName(),
new CodeWriter().add("File too big, size: " + String.format("%.2f KB", size / 1024.)));
}
private static ResContainer decodeImage(ResourceFile rf, InputStream inputStream) {
String name = rf.getName();
if (name.endsWith(".9.png")) {
Res9patchStreamDecoder decoder = new Res9patchStreamDecoder();
ByteArrayOutputStream os = new ByteArrayOutputStream();
try {
decoder.decode(inputStream, os);
return ResContainer.decodedData(rf.getName(), os.toByteArray());
} catch (Exception e) {
LOG.error("Failed to decode 9-patch png image, path: {}", name, e);
}
}
return ResContainer.singleFile(rf.getName(), loadToCodeWriter(inputStream));
return ResContainer.resourceFileLink(rf);
}
private void loadFile(List<ResourceFile> list, File file) {
if (file == null) {
return;
}
ZipFile zip = null;
try {
zip = new ZipFile(file);
try (ZipFile 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);
if (ZipSecurity.isValidZipEntry(entry)) {
addEntry(list, file, entry);
}
}
} catch (Exception e) {
LOG.debug("Not a zip file: {}", file.getAbsolutePath());
addResourceFile(list, file);
}
}
private void addResourceFile(List<ResourceFile> list, File file) {
String name = file.getAbsolutePath();
ResourceType type = ResourceType.getFileType(name);
ResourceFile rf = ResourceFile.createResourceFile(jadxRef, name, type);
if (rf != null) {
list.add(rf);
}
}
@@ -150,9 +156,11 @@ public final class ResourcesLoader {
}
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);
ResourceFile rf = ResourceFile.createResourceFile(jadxRef, name, type);
if (rf != null) {
rf.setZipRef(new ZipRef(zipFile, name));
list.add(rf);
}
}
public static CodeWriter loadToCodeWriter(InputStream is) throws IOException {
@@ -21,4 +21,7 @@ public class Consts {
public static final String ANONYMOUS_CLASS_PREFIX = "AnonymousClass";
public static final String MTH_TOSTRING_SIGNATURE = "toString()Ljava/lang/String;";
private Consts() {
}
}
+24 -20
View File
@@ -1,6 +1,15 @@
package jadx.core;
import jadx.api.IJadxArgs;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.Manifest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs;
import jadx.core.dex.visitors.ClassModifier;
import jadx.core.dex.visitors.CodeShrinker;
import jadx.core.dex.visitors.ConstInlineVisitor;
@@ -10,6 +19,7 @@ import jadx.core.dex.visitors.DotGraphVisitor;
import jadx.core.dex.visitors.EnumVisitor;
import jadx.core.dex.visitors.ExtractFieldInit;
import jadx.core.dex.visitors.FallbackModeVisitor;
import jadx.core.dex.visitors.FixAccessModifiers;
import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.dex.visitors.MethodInlineVisitor;
import jadx.core.dex.visitors.ModVisitor;
@@ -33,27 +43,20 @@ import jadx.core.dex.visitors.ssa.SSATransform;
import jadx.core.dex.visitors.typeinference.FinishTypeInference;
import jadx.core.dex.visitors.typeinference.TypeInference;
import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.Manifest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Jadx {
private static final Logger LOG = LoggerFactory.getLogger(Jadx.class);
private Jadx() {
}
static {
if (Consts.DEBUG) {
LOG.info("debug enabled");
}
}
public static List<IDexTreeVisitor> getPassesList(IJadxArgs args, File outDir) {
List<IDexTreeVisitor> passes = new ArrayList<IDexTreeVisitor>();
public static List<IDexTreeVisitor> getPassesList(JadxArgs args) {
List<IDexTreeVisitor> passes = new ArrayList<>();
if (args.isFallbackMode()) {
passes.add(new FallbackModeVisitor());
} else {
@@ -68,7 +71,7 @@ public class Jadx {
passes.add(new TypeInference());
if (args.isRawCFGOutput()) {
passes.add(DotGraphVisitor.dumpRaw(outDir));
passes.add(DotGraphVisitor.dumpRaw());
}
passes.add(new ConstInlineVisitor());
@@ -80,8 +83,8 @@ public class Jadx {
passes.add(new CodeShrinker());
passes.add(new ReSugarCode());
if (args.isCFGOutput()) {
passes.add(DotGraphVisitor.dump(outDir));
if (args.isCfgOutput()) {
passes.add(DotGraphVisitor.dump());
}
passes.add(new RegionMakerVisitor());
@@ -92,18 +95,19 @@ public class Jadx {
passes.add(new SimplifyVisitor());
passes.add(new CheckRegions());
if (args.isCFGOutput()) {
passes.add(DotGraphVisitor.dumpRegions(outDir));
}
passes.add(new MethodInlineVisitor());
passes.add(new ExtractFieldInit());
passes.add(new FixAccessModifiers());
passes.add(new ClassModifier());
passes.add(new EnumVisitor());
passes.add(new PrepareForCodeGen());
passes.add(new LoopRegionVisitor());
passes.add(new ProcessVariables());
if (args.isCfgOutput()) {
passes.add(DotGraphVisitor.dumpRegions());
}
passes.add(new DependencyCollector());
passes.add(new RenameVisitor());
@@ -1,25 +1,20 @@
package jadx.core;
import java.util.List;
import org.jetbrains.annotations.Nullable;
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() {
}
@@ -28,7 +23,7 @@ public final class ProcessClass {
if (codeGen == null && cls.getState() == PROCESSED) {
return;
}
synchronized (cls) {
synchronized (getSyncObj(cls)) {
try {
if (cls.getState() == NOT_LOADED) {
cls.load();
@@ -41,22 +36,18 @@ public final class ProcessClass {
if (cls.getState() == PROCESSED && codeGen != null) {
processDependencies(cls, passes);
codeGen.visit(cls);
cls.setState(GENERATED);
}
} catch (Exception e) {
ErrorsCounter.classError(cls, e.getClass().getSimpleName(), e);
} finally {
if (cls.getState() == GENERATED) {
cls.unload();
cls.setState(UNLOADED);
}
}
}
}
static void processDependencies(ClassNode cls, List<IDexTreeVisitor> passes) {
for (ClassNode depCls : cls.getDependencies()) {
process(depCls, passes, null);
}
public static Object getSyncObj(ClassNode cls) {
return cls.getClassInfo();
}
private static void processDependencies(ClassNode cls, List<IDexTreeVisitor> passes) {
cls.getDependencies().forEach(depCls -> process(depCls, passes, null));
}
}
@@ -1,12 +1,5 @@
package jadx.core.clsp;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.FileUtils;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
@@ -27,6 +20,14 @@ import java.util.zip.ZipOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.FileUtils;
import jadx.core.utils.files.ZipSecurity;
import static jadx.core.utils.files.FileUtils.close;
/**
@@ -48,7 +49,7 @@ public class ClsSet {
public void load(RootNode root) {
List<ClassNode> list = root.getClasses(true);
Map<String, NClass> names = new HashMap<String, NClass>(list.size());
Map<String, NClass> names = new HashMap<>(list.size());
int k = 0;
for (ClassNode cls : list) {
String clsRawName = cls.getRawName();
@@ -78,7 +79,7 @@ public class ClsSet {
}
public static NClass[] makeParentsArray(ClassNode cls, Map<String, NClass> names) {
List<NClass> parents = new ArrayList<NClass>(1 + cls.getInterfaces().size());
List<NClass> parents = new ArrayList<>(1 + cls.getInterfaces().size());
ArgType superClass = cls.getSuperClass();
if (superClass != null) {
NClass c = getCls(superClass.getObject(), names);
@@ -105,9 +106,7 @@ public class ClsSet {
void save(File output) throws IOException {
FileUtils.makeDirsForFile(output);
BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(output));
try {
try (BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(output))) {
String outputName = output.getName();
if (outputName.endsWith(CLST_EXTENSION)) {
save(outputStream);
@@ -122,14 +121,11 @@ public class ClsSet {
} else {
throw new JadxRuntimeException("Unknown file format: " + outputName);
}
} finally {
close(outputStream);
}
}
public void save(OutputStream output) throws IOException {
DataOutputStream out = new DataOutputStream(output);
try {
try (DataOutputStream out = new DataOutputStream(output)) {
out.writeBytes(JADX_CLS_SET_HEADER);
out.writeByte(VERSION);
@@ -145,53 +141,41 @@ public class ClsSet {
out.writeInt(parent.getId());
}
}
} finally {
close(out);
}
}
public void load() throws IOException, DecodeException {
InputStream input = getClass().getResourceAsStream(CLST_FILENAME);
if (input == null) {
throw new JadxRuntimeException("Can't load classpath file: " + CLST_FILENAME);
}
try {
try (InputStream input = getClass().getResourceAsStream(CLST_FILENAME)) {
if (input == null) {
throw new JadxRuntimeException("Can't load classpath file: " + CLST_FILENAME);
}
load(input);
} finally {
close(input);
}
}
public void load(File input) throws IOException, DecodeException {
String name = input.getName();
InputStream inputStream = new FileInputStream(input);
try {
try (InputStream inputStream = new FileInputStream(input)) {
if (name.endsWith(CLST_EXTENSION)) {
load(inputStream);
} else if (name.endsWith(".jar")) {
ZipInputStream in = new ZipInputStream(inputStream);
try {
try (ZipInputStream in = new ZipInputStream(inputStream)) {
ZipEntry entry = in.getNextEntry();
while (entry != null) {
if (entry.getName().endsWith(CLST_EXTENSION)) {
if (entry.getName().endsWith(CLST_EXTENSION) && ZipSecurity.isValidZipEntry(entry)) {
load(in);
}
entry = in.getNextEntry();
}
} finally {
close(in);
}
} else {
throw new JadxRuntimeException("Unknown file format: " + name);
}
} finally {
close(inputStream);
}
}
public void load(InputStream input) throws IOException, DecodeException {
DataInputStream in = new DataInputStream(input);
try {
try (DataInputStream in = new DataInputStream(input)) {
byte[] header = new byte[JADX_CLS_SET_HEADER.length()];
int readHeaderLength = in.read(header);
int version = in.readByte();
@@ -214,8 +198,6 @@ public class ClsSet {
}
classes[i].setParents(parents);
}
} finally {
close(in);
}
}
@@ -1,9 +1,5 @@
package jadx.core.clsp;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
@@ -17,16 +13,20 @@ import java.util.WeakHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxRuntimeException;
/**
* Classes hierarchy graph
*/
public class ClspGraph {
private static final Logger LOG = LoggerFactory.getLogger(ClspGraph.class);
private final Map<String, Set<String>> ancestorCache = new WeakHashMap<String, Set<String>>();
private final Map<String, Set<String>> ancestorCache = Collections.synchronizedMap(new WeakHashMap<String, Set<String>>());
private Map<String, NClass> nameMap;
private final Set<String> missingClasses = new HashSet<String>();
private final Set<String> missingClasses = new HashSet<>();
public void load() throws IOException, DecodeException {
ClsSet set = new ClsSet();
@@ -36,7 +36,7 @@ public class ClspGraph {
public void addClasspath(ClsSet set) {
if (nameMap == null) {
nameMap = new HashMap<String, NClass>(set.getClassesCount());
nameMap = new HashMap<>(set.getClassesCount());
set.addToMap(nameMap);
} else {
throw new JadxRuntimeException("Classpath already loaded");
@@ -110,7 +110,7 @@ public class ClspGraph {
missingClasses.add(clsName);
return Collections.emptySet();
}
result = new HashSet<String>();
result = new HashSet<>();
addAncestorsNames(cls, result);
if (result.isEmpty()) {
result = Collections.emptySet();
@@ -120,9 +120,11 @@ public class ClspGraph {
}
private void addAncestorsNames(NClass cls, Set<String> result) {
result.add(cls.getName());
for (NClass p : cls.getParents()) {
addAncestorsNames(p, result);
boolean isNew = result.add(cls.getName());
if (isNew) {
for (NClass p : cls.getParents()) {
addAncestorsNames(p, result);
}
}
}
@@ -133,7 +135,7 @@ public class ClspGraph {
}
LOG.warn("Found {} references to unknown classes", count);
if (LOG.isDebugEnabled()) {
List<String> clsNames = new ArrayList<String>(missingClasses);
List<String> clsNames = new ArrayList<>(missingClasses);
Collections.sort(clsNames);
for (String cls : clsNames) {
LOG.debug(" {}", cls);
@@ -1,10 +1,5 @@
package jadx.core.clsp;
import jadx.api.JadxArgs;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.files.InputFile;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
@@ -13,6 +8,11 @@ import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.files.InputFile;
/**
* Utility class for convert dex or jar to jadx classes set (.jcst)
*/
@@ -30,7 +30,7 @@ public class ConvertToClsSet {
}
File output = new File(args[0]);
List<InputFile> inputFiles = new ArrayList<InputFile>(args.length - 1);
List<InputFile> inputFiles = new ArrayList<>(args.length - 1);
for (int i = 1; i < args.length; i++) {
File f = new File(args[i]);
if (f.isDirectory()) {
@@ -1,5 +1,10 @@
package jadx.core.codegen;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import jadx.core.Consts;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttributeNode;
@@ -14,11 +19,6 @@ import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.StringUtils;
import jadx.core.utils.exceptions.JadxRuntimeException;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
public class AnnotationGen {
private final ClassNode cls;
@@ -1,11 +1,25 @@
package jadx.core.codegen;
import jadx.api.IJadxArgs;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import com.android.dx.rop.code.AccessFlags;
import jadx.api.JadxArgs;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.AttrNode;
import jadx.core.dex.attributes.nodes.EnumClassAttr;
import jadx.core.dex.attributes.nodes.EnumClassAttr.EnumField;
import jadx.core.dex.attributes.nodes.JadxError;
import jadx.core.dex.attributes.nodes.JadxWarn;
import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.dex.attributes.nodes.SourceFileAttr;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.ClassInfo;
@@ -19,56 +33,36 @@ import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.parser.FieldInitAttr;
import jadx.core.dex.nodes.parser.FieldInitAttr.InitType;
import jadx.core.utils.CodegenUtils;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.CodegenException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.android.dx.rop.code.AccessFlags;
public class ClassGen {
private static final Logger LOG = LoggerFactory.getLogger(ClassGen.class);
public static final Comparator<MethodNode> METHOD_LINE_COMPARATOR = new Comparator<MethodNode>() {
@Override
public int compare(MethodNode a, MethodNode b) {
return Utils.compare(a.getSourceLine(), b.getSourceLine());
}
};
private final ClassNode cls;
private final ClassGen parentGen;
private final AnnotationGen annotationGen;
private final boolean fallback;
private final boolean useImports;
private final boolean showInconsistentCode;
private final Set<ClassInfo> imports = new HashSet<ClassInfo>();
private final Set<ClassInfo> imports = new HashSet<>();
private int clsDeclLine;
public ClassGen(ClassNode cls, IJadxArgs jadxArgs) {
this(cls, null, jadxArgs.isFallbackMode(), jadxArgs.isShowInconsistentCode());
public ClassGen(ClassNode cls, JadxArgs jadxArgs) {
this(cls, null, jadxArgs.isUseImports(), jadxArgs.isFallbackMode(), jadxArgs.isShowInconsistentCode());
}
public ClassGen(ClassNode cls, ClassGen parentClsGen) {
this(cls, parentClsGen, parentClsGen.fallback, parentClsGen.showInconsistentCode);
this(cls, parentClsGen, parentClsGen.useImports, parentClsGen.fallback, parentClsGen.showInconsistentCode);
}
public ClassGen(ClassNode cls, ClassGen parentClsGen, boolean fallback, boolean showBadCode) {
public ClassGen(ClassNode cls, ClassGen parentClsGen, boolean useImports, boolean fallback, boolean showBadCode) {
this.cls = cls;
this.parentGen = parentClsGen;
this.fallback = fallback;
this.useImports = useImports;
this.showInconsistentCode = showBadCode;
this.annotationGen = new AnnotationGen(cls, this);
@@ -89,18 +83,18 @@ public class ClassGen {
}
int importsCount = imports.size();
if (importsCount != 0) {
List<String> sortImports = new ArrayList<String>(importsCount);
for (ClassInfo ic : imports) {
sortImports.add(ic.getAlias().getFullName());
}
Collections.sort(sortImports);
for (String imp : sortImports) {
clsCode.startLine("import ").add(imp).add(';');
}
List<ClassInfo> sortedImports = new ArrayList<>(imports);
sortedImports.sort(Comparator.comparing(classInfo -> classInfo.getAlias().getFullName()));
sortedImports.forEach(classInfo -> {
clsCode.startLine("import ");
ClassNode classNode = cls.root().resolveClass(classInfo);
if (classNode != null) {
clsCode.attachAnnotation(classNode);
}
clsCode.add(classInfo.getAlias().getFullName());
clsCode.add(';');
});
clsCode.newLine();
sortImports.clear();
imports.clear();
}
clsCode.add(clsBody);
@@ -111,9 +105,8 @@ public class ClassGen {
if (cls.contains(AFlag.DONT_GENERATE)) {
return;
}
if (cls.contains(AFlag.INCONSISTENT_CODE)) {
code.startLine("// jadx: inconsistent code");
}
CodegenUtils.addComments(code, cls);
insertDecompilationProblems(code, cls);
addClassDeclaration(code);
addClassBody(code);
}
@@ -261,6 +254,7 @@ public class ClassGen {
if (code.getLine() != clsDeclLine) {
code.newLine();
}
int savedIndent = code.getIndent();
try {
addMethod(code, mth);
} catch (Exception e) {
@@ -268,13 +262,14 @@ public class ClassGen {
code.newLine().add(ErrorsCounter.methodError(mth, "Method generation error", e));
code.newLine().add(Utils.getStackTrace(e));
code.newLine().add("*/");
code.setIndent(savedIndent);
}
}
}
private static List<MethodNode> sortMethodsByLine(List<MethodNode> methods) {
List<MethodNode> out = new ArrayList<MethodNode>(methods);
Collections.sort(out, METHOD_LINE_COMPARATOR);
List<MethodNode> out = new ArrayList<>(methods);
out.sort(Comparator.comparingInt(LineAttrNode::getSourceLine));
return out;
}
@@ -300,15 +295,13 @@ public class ClassGen {
}
code.add(';');
} else {
CodegenUtils.addComments(code, mth);
insertDecompilationProblems(code, mth);
boolean badCode = mth.contains(AFlag.INCONSISTENT_CODE);
if (badCode) {
code.startLine("/* JADX WARNING: inconsistent code. */");
if (badCode && showInconsistentCode) {
code.startLine("/* Code decompiled incorrectly, please refer to instructions dump. */");
ErrorsCounter.methodError(mth, "Inconsistent code");
if (showInconsistentCode) {
mth.remove(AFlag.INCONSISTENT_CODE);
badCode = false;
}
mth.remove(AFlag.INCONSISTENT_CODE);
badCode = false;
}
MethodGen mthGen;
if (badCode || mth.contains(AType.JADX_ERROR) || fallback) {
@@ -332,13 +325,38 @@ public class ClassGen {
}
}
private void insertDecompilationProblems(CodeWriter code, AttrNode node) {
List<JadxError> errors = node.getAll(AType.JADX_ERROR);
List<JadxWarn> warns = node.getAll(AType.JADX_WARN);
if (!errors.isEmpty()) {
errors.forEach(err -> {
code.startLine("/* JADX ERROR: ").add(err.getError());
Throwable cause = err.getCause();
if (cause != null) {
code.incIndent();
Utils.appendStackTrace(code, cause);
code.decIndent();
}
code.add("*/");
});
}
if (!warns.isEmpty()) {
warns.forEach(warn -> code.startLine("/* JADX WARNING: ").addMultiLine(warn.getWarn()).add(" */"));
}
}
private void addFields(CodeWriter code) throws CodegenException {
addEnumFields(code);
for (FieldNode f : cls.getFields()) {
if (f.contains(AFlag.DONT_GENERATE)) {
continue;
}
CodegenUtils.addComments(code, f);
annotationGen.addForField(code, f);
if (f.getFieldInfo().isRenamed()) {
code.startLine("/* renamed from: ").add(f.getName()).add(" */");
}
code.startLine(f.getAccessFlags().makeString());
useType(code, f.getType());
code.add(' ');
@@ -439,7 +457,7 @@ public class ClassGen {
}
public void useClass(CodeWriter code, ArgType type) {
useClass(code, ClassInfo.extCls(cls.dex(), type));
useClass(code, ClassInfo.extCls(cls.root(), type));
ArgType[] generics = type.getGenericTypes();
if (generics != null) {
code.add('<');
@@ -476,7 +494,7 @@ public class ClassGen {
private String useClassInternal(ClassInfo useCls, ClassInfo extClsInfo) {
String fullName = extClsInfo.getFullName();
if (fallback) {
if (fallback || !useImports) {
return fullName;
}
String shortName = extClsInfo.getShortName();
@@ -586,9 +604,8 @@ public class ClassGen {
private void insertRenameInfo(CodeWriter code, ClassNode cls) {
ClassInfo classInfo = cls.getClassInfo();
if (classInfo.isRenamed()
&& !cls.getShortName().equals(cls.getAlias().getShortName())) {
code.startLine("/* renamed from: ").add(classInfo.getFullName()).add(" */");
if (classInfo.isRenamed()) {
code.startLine("/* renamed from: ").add(classInfo.getType().getObject()).add(" */");
}
}
@@ -1,25 +1,15 @@
package jadx.core.codegen;
import jadx.api.IJadxArgs;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.visitors.AbstractVisitor;
import jadx.core.utils.exceptions.CodegenException;
public class CodeGen extends AbstractVisitor {
public class CodeGen {
private final IJadxArgs args;
public CodeGen(IJadxArgs args) {
this.args = args;
}
@Override
public boolean visit(ClassNode cls) throws CodegenException {
ClassGen clsGen = new ClassGen(cls, args);
ClassGen clsGen = new ClassGen(cls, cls.root().getArgs());
CodeWriter clsCode = clsGen.makeClass();
clsCode.finish();
cls.setCode(clsCode);
return false;
}
}
@@ -1,9 +1,5 @@
package jadx.core.codegen;
import jadx.api.CodePosition;
import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.utils.files.FileUtils;
import java.io.File;
import java.io.PrintWriter;
import java.util.Collections;
@@ -16,23 +12,27 @@ import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static jadx.core.utils.files.FileUtils.close;
import jadx.api.CodePosition;
import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.utils.StringUtils;
import jadx.core.utils.files.FileUtils;
import jadx.core.utils.files.ZipSecurity;
public class CodeWriter {
private static final Logger LOG = LoggerFactory.getLogger(CodeWriter.class);
public static final String NL = System.getProperty("line.separator");
public static final String INDENT = " ";
public static final String INDENT_STR = " ";
private static final boolean ADD_LINE_NUMBERS = false;
private static final String[] INDENT_CACHE = {
"",
INDENT,
INDENT + INDENT,
INDENT + INDENT + INDENT,
INDENT + INDENT + INDENT + INDENT,
INDENT + INDENT + INDENT + INDENT + INDENT,
INDENT_STR,
INDENT_STR + INDENT_STR,
INDENT_STR + INDENT_STR + INDENT_STR,
INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR,
INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR,
};
private StringBuilder buf = new StringBuilder();
@@ -94,6 +94,15 @@ public class CodeWriter {
return this;
}
public CodeWriter addMultiLine(String str) {
buf.append(str);
if (str.contains(NL)) {
line += StringUtils.countMatches(str, NL);
offset = 0;
}
return this;
}
public CodeWriter add(String str) {
buf.append(str);
offset += str.length();
@@ -127,7 +136,7 @@ public class CodeWriter {
}
public CodeWriter addIndent() {
add(INDENT);
add(INDENT_STR);
return this;
}
@@ -148,9 +157,9 @@ public class CodeWriter {
if (curIndent < INDENT_CACHE.length) {
this.indentStr = INDENT_CACHE[curIndent];
} else {
StringBuilder s = new StringBuilder(curIndent * INDENT.length());
StringBuilder s = new StringBuilder(curIndent * INDENT_STR.length());
for (int i = 0; i < curIndent; i++) {
s.append(INDENT);
s.append(INDENT_STR);
}
this.indentStr = s.toString();
}
@@ -182,6 +191,11 @@ public class CodeWriter {
return indent;
}
public void setIndent(int indent) {
this.indent = indent;
updateIndent();
}
public int getLine() {
return line;
}
@@ -209,7 +223,7 @@ public class CodeWriter {
private Object attachAnnotation(Object obj, CodePosition pos) {
if (annotations.isEmpty()) {
annotations = new HashMap<CodePosition, Object>();
annotations = new HashMap<>();
}
return annotations.put(pos, obj);
}
@@ -227,7 +241,7 @@ public class CodeWriter {
private void attachSourceLine(int decompiledLine, int sourceLine) {
if (lineMap.isEmpty()) {
lineMap = new TreeMap<Integer, Integer>();
lineMap = new TreeMap<>();
}
lineMap.put(decompiledLine, sourceLine);
}
@@ -255,8 +269,9 @@ public class CodeWriter {
}
private void removeFirstEmptyLine() {
if (buf.indexOf(NL) == 0) {
buf.delete(0, NL.length());
int len = NL.length();
if (buf.substring(0, len).equals(NL)) {
buf.delete(0, len);
}
}
@@ -274,10 +289,16 @@ public class CodeWriter {
}
public void save(File dir, String subDir, String fileName) {
if (!ZipSecurity.isValidZipEntryName(subDir) || !ZipSecurity.isValidZipEntryName(fileName)) {
return;
}
save(dir, new File(subDir, fileName).getPath());
}
public void save(File dir, String fileName) {
if (!ZipSecurity.isValidZipEntryName(fileName)) {
return;
}
save(new File(dir, fileName));
}
@@ -286,15 +307,10 @@ public class CodeWriter {
finish();
}
File outFile = FileUtils.prepareFile(file);
PrintWriter out = null;
try {
out = new PrintWriter(outFile, "UTF-8");
try (PrintWriter out = new PrintWriter(outFile, "UTF-8")) {
out.println(code);
} catch (Exception e) {
LOG.error("Save file error", e);
} finally {
close(out);
}
}
}
@@ -1,5 +1,9 @@
package jadx.core.codegen;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Queue;
import jadx.core.dex.instructions.ArithNode;
import jadx.core.dex.instructions.IfOp;
import jadx.core.dex.instructions.InsnType;
@@ -15,18 +19,10 @@ import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Queue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ConditionGen extends InsnGen {
private static final Logger LOG = LoggerFactory.getLogger(ConditionGen.class);
private static class CondStack {
private final Queue<IfCondition> stack = new LinkedList<IfCondition>();
private final Queue<IfCondition> stack = new LinkedList<>();
public Queue<IfCondition> getStack() {
return stack;
@@ -126,7 +122,7 @@ public class ConditionGen extends InsnGen {
wrap(code, firstArg);
return;
}
LOG.warn(ErrorsCounter.formatErrorMsg(mth, "Unsupported boolean condition " + op.getSymbol()));
ErrorsCounter.methodWarn(mth, "Unsupported boolean condition " + op.getSymbol());
}
addArg(code, firstArg, isArgWrapNeeded(firstArg));
@@ -179,6 +175,9 @@ public class ConditionGen extends InsnGen {
case DIV:
case REM:
return false;
default:
return true;
}
} else {
switch (insnType) {
@@ -189,10 +188,10 @@ public class ConditionGen extends InsnGen {
case CONST:
case ARRAY_LENGTH:
return false;
default:
return true;
}
}
return true;
}
}
@@ -1,5 +1,16 @@
package jadx.core.codegen;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
@@ -41,17 +52,6 @@ import jadx.core.utils.RegionUtils;
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.Iterator;
import java.util.List;
import java.util.Set;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static jadx.core.utils.android.AndroidResourcesUtils.handleAppResField;
public class InsnGen {
@@ -137,13 +137,7 @@ public class InsnGen {
private void instanceField(CodeWriter code, FieldInfo field, InsnArg arg) throws CodegenException {
ClassNode pCls = mth.getParentClass();
FieldNode fieldNode = pCls.searchField(field);
while (fieldNode == null
&& pCls.getParentClass() != pCls
&& pCls.getParentClass() != null) {
pCls = pCls.getParentClass();
fieldNode = pCls.searchField(field);
}
FieldNode fieldNode = pCls.dex().root().deepResolveField(field);
if (fieldNode != null) {
FieldReplaceAttr replace = fieldNode.get(AType.FIELD_REPLACE);
if (replace != null) {
@@ -163,7 +157,11 @@ public class InsnGen {
if (fieldNode != null) {
code.attachAnnotation(fieldNode);
}
code.add(field.getAlias());
if (fieldNode == null) {
code.add(field.getAlias());
} else {
code.add(fieldNode.getAlias());
}
}
public static void makeStaticFieldAccess(CodeWriter code, FieldInfo field, ClassGen clsGen) {
@@ -176,11 +174,15 @@ public class InsnGen {
}
code.add('.');
}
FieldNode fieldNode = clsGen.getClassNode().dex().resolveField(field);
FieldNode fieldNode = clsGen.getClassNode().dex().root().deepResolveField(field);
if (fieldNode != null) {
code.attachAnnotation(fieldNode);
}
code.add(field.getAlias());
if (fieldNode == null) {
code.add(field.getAlias());
} else {
code.add(fieldNode.getAlias());
}
}
protected void staticField(CodeWriter code, FieldInfo field) {
@@ -222,7 +224,7 @@ public class InsnGen {
code.add(';');
}
}
} catch (Throwable th) {
} catch (Exception th) {
throw new CodegenException(mth, "Error generate insn: " + insn, th);
}
return true;
@@ -270,18 +272,13 @@ public class InsnGen {
makeArith((ArithNode) insn, code, state);
break;
case NEG: {
boolean wrap = state.contains(Flags.BODY_ONLY);
if (wrap) {
code.add('(');
}
code.add('-');
addArg(code, insn.getArg(0));
if (wrap) {
code.add(')');
}
case NEG:
oneArgInsn(code, insn, state, '-');
break;
case NOT:
oneArgInsn(code, insn, state, '~');
break;
}
case RETURN:
if (insn.getArgsCount() != 0) {
@@ -525,6 +522,18 @@ public class InsnGen {
}
}
private void oneArgInsn(CodeWriter code, InsnNode insn, Set<Flags> state, char op) throws CodegenException {
boolean wrap = state.contains(Flags.BODY_ONLY);
if (wrap) {
code.add('(');
}
code.add(op);
addArg(code, insn.getArg(0));
if (wrap) {
code.add(')');
}
}
private void fallbackOnlyInsn(InsnNode insn) throws CodegenException {
if (!fallback) {
throw new CodegenException(insn.getType() + " can be used only in fallback mode");
@@ -571,7 +580,7 @@ public class InsnGen {
// anonymous class construction
if (cls.contains(AFlag.DONT_GENERATE)) {
code.add("/* anonymous class already generated */");
ErrorsCounter.methodError(mth, "Anonymous class already generated: " + cls);
ErrorsCounter.methodWarn(mth, "Anonymous class already generated: " + cls);
return;
}
ArgType parent;
@@ -605,7 +614,7 @@ public class InsnGen {
MethodInfo callMth = insn.getCallMth();
// inline method
MethodNode callMthNode = mth.dex().deepResolveMethod(callMth);
MethodNode callMthNode = mth.root().deepResolveMethod(callMth);
if (callMthNode != null) {
if (inlineMethod(callMthNode, insn, code)) {
return;
@@ -650,7 +659,7 @@ public class InsnGen {
}
void generateMethodArguments(CodeWriter code, InsnNode insn, int startArgNum,
@Nullable MethodNode callMth) throws CodegenException {
@Nullable MethodNode callMth) throws CodegenException {
int k = startArgNum;
if (callMth != null && callMth.contains(AFlag.SKIP_FIRST_ARG)) {
k++;
@@ -752,7 +761,7 @@ public class InsnGen {
}
// replace args
InsnNode inlCopy = inl.copy();
List<RegisterArg> inlArgs = new ArrayList<RegisterArg>();
List<RegisterArg> inlArgs = new ArrayList<>();
inlCopy.getRegisterArgs(inlArgs);
for (RegisterArg r : inlArgs) {
int regNum = r.getRegNum();
@@ -1,9 +1,17 @@
package jadx.core.codegen;
import java.util.Iterator;
import java.util.List;
import com.android.dx.rop.code.AccessFlags;
import jadx.core.dex.info.ClassInfo;
import jadx.core.utils.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.annotations.MethodParameters;
import jadx.core.dex.attributes.nodes.JadxErrorAttr;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.args.ArgType;
@@ -16,18 +24,9 @@ import jadx.core.dex.visitors.DepthTraversal;
import jadx.core.dex.visitors.FallbackModeVisitor;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.DecodeException;
import java.util.Iterator;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.android.dx.rop.code.AccessFlags;
public class MethodGen {
private static final Logger LOG = LoggerFactory.getLogger(MethodGen.class);
@@ -80,13 +79,17 @@ public class MethodGen {
if (clsAccFlags.isAnnotation()) {
ai = ai.remove(AccessFlags.ACC_PUBLIC);
}
if (mth.getMethodInfo().isRenamed() && !ai.isConstructor()) {
code.startLine("/* renamed from: ").add(mth.getName()).add(" */");
}
code.startLineWithNum(mth.getSourceLine());
code.add(ai.makeString());
if (classGen.addGenericMap(code, mth.getGenericMap())) {
code.add(' ');
}
if (mth.getAccessFlags().isConstructor()) {
if (ai.isConstructor()) {
code.attachDefinition(mth);
code.add(classGen.getClassNode().getShortName()); // constructor
} else {
@@ -105,7 +108,7 @@ public class MethodGen {
} else if (args.size() > 2) {
args = args.subList(2, args.size());
} else {
LOG.warn(ErrorsCounter.formatErrorMsg(mth,
LOG.warn(ErrorsCounter.formatMsg(mth,
"Incorrect number of args for enum constructor: " + args.size()
+ " (expected >= 2)"
));
@@ -129,7 +132,7 @@ public class MethodGen {
annotationGen.addForParameter(argsCode, paramsAnnotation, i);
}
SSAVar argSVar = arg.getSVar();
if (argSVar!= null && argSVar.contains(AFlag.FINAL)) {
if (argSVar != null && argSVar.contains(AFlag.FINAL)) {
argsCode.add("final ");
}
if (!it.hasNext() && mth.getAccessFlags().isVarArgs()) {
@@ -140,7 +143,7 @@ public class MethodGen {
classGen.useType(argsCode, elType);
argsCode.add("...");
} else {
LOG.warn(ErrorsCounter.formatErrorMsg(mth, "Last argument in varargs method not array"));
LOG.warn(ErrorsCounter.formatMsg(mth, "Last argument in varargs method not array"));
classGen.useType(argsCode, arg.getType());
}
} else {
@@ -160,23 +163,20 @@ public class MethodGen {
if (mth.contains(AType.JADX_ERROR)
|| mth.contains(AFlag.INCONSISTENT_CODE)
|| mth.getRegion() == null) {
JadxErrorAttr err = mth.get(AType.JADX_ERROR);
if (err != null) {
code.startLine("/* JADX: method processing error */");
Throwable cause = err.getCause();
if (cause != null) {
code.newLine();
code.add("/*");
code.newLine().add("Error: ").add(Utils.getStackTrace(cause));
code.add("*/");
}
}
code.startLine("/*");
addFallbackMethodCode(code);
code.startLine("*/");
ClassInfo clsAlias = mth.getParentClass().getAlias();
code.startLine("throw new UnsupportedOperationException(\"Method not decompiled: ")
.add(mth.toString())
.add(clsAlias.makeFullClsName(clsAlias.getShortName(), true))
.add(".")
.add(mth.getAlias())
.add("(")
.add(Utils.listToString(mth.getMethodInfo().getArgumentsTypes()))
.add("):")
.add(mth.getMethodInfo().getReturnType().toString())
.add("\");");
} else {
RegionGen regionGen = new RegionGen(this);
@@ -186,19 +186,14 @@ public class MethodGen {
public void addFallbackMethodCode(CodeWriter code) {
if (mth.getInstructions() == null) {
JadxErrorAttr errorAttr = mth.get(AType.JADX_ERROR);
if (errorAttr == null
|| errorAttr.getCause() == null
|| !errorAttr.getCause().getClass().equals(DecodeException.class)) {
// load original instructions
try {
mth.load();
DepthTraversal.visit(new FallbackModeVisitor(), mth);
} catch (DecodeException e) {
LOG.error("Error reload instructions in fallback mode:", e);
code.startLine("// Can't load method instructions: " + e.getMessage());
return;
}
// 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();
@@ -241,12 +236,11 @@ public class MethodGen {
* Return fallback variant of method codegen
*/
public static MethodGen getFallbackMethodGen(MethodNode mth) {
ClassGen clsGen = new ClassGen(mth.getParentClass(), null, true, true);
ClassGen clsGen = new ClassGen(mth.getParentClass(), null, true, true, true);
return new MethodGen(clsGen, mth);
}
public static String getLabelName(int offset) {
return "L_" + InsnUtils.formatOffset(offset);
}
}
@@ -1,5 +1,9 @@
package jadx.core.codegen;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import jadx.core.Consts;
import jadx.core.deobf.NameMapper;
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
@@ -16,35 +20,32 @@ import jadx.core.dex.instructions.mods.ConstructorInsn;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.StringUtils;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import jadx.core.utils.Utils;
public class NameGen {
private static final Map<String, String> OBJ_ALIAS;
private final Set<String> varNames = new HashSet<String>();
private final Set<String> varNames = new LinkedHashSet<>();
private final MethodNode mth;
private final boolean fallback;
static {
OBJ_ALIAS = new HashMap<String, String>();
OBJ_ALIAS.put(Consts.CLASS_STRING, "str");
OBJ_ALIAS.put(Consts.CLASS_CLASS, "cls");
OBJ_ALIAS.put(Consts.CLASS_THROWABLE, "th");
OBJ_ALIAS.put(Consts.CLASS_OBJECT, "obj");
OBJ_ALIAS.put("java.util.Iterator", "it");
OBJ_ALIAS.put("java.lang.Boolean", "bool");
OBJ_ALIAS.put("java.lang.Short", "sh");
OBJ_ALIAS.put("java.lang.Integer", "num");
OBJ_ALIAS.put("java.lang.Character", "ch");
OBJ_ALIAS.put("java.lang.Byte", "b");
OBJ_ALIAS.put("java.lang.Float", "f");
OBJ_ALIAS.put("java.lang.Long", "l");
OBJ_ALIAS.put("java.lang.Double", "d");
OBJ_ALIAS = Utils.newConstStringMap(
Consts.CLASS_STRING, "str",
Consts.CLASS_CLASS, "cls",
Consts.CLASS_THROWABLE, "th",
Consts.CLASS_OBJECT, "obj",
"java.util.Iterator", "it",
"java.lang.Boolean", "bool",
"java.lang.Short", "sh",
"java.lang.Integer", "num",
"java.lang.Character", "ch",
"java.lang.Byte", "b",
"java.lang.Float", "f",
"java.lang.Long", "l",
"java.lang.Double", "d"
);
}
public NameGen(MethodNode mth, boolean fallback) {
@@ -102,16 +103,11 @@ public class NameGen {
if (fallback) {
return getFallbackName(arg);
}
String name = arg.getName();
String varName;
if (name != null) {
if ("this".equals(name)) {
return name;
}
varName = name;
} else {
varName = guessName(arg);
if (arg.isThis()) {
return RegisterArg.THIS_ARG_NAME;
}
String name = arg.getName();
String varName = name != null ? name : guessName(arg);
if (NameMapper.isReserved(varName)) {
return varName + "R";
}
@@ -141,11 +137,11 @@ public class NameGen {
private String makeNameForType(ArgType type) {
if (type.isPrimitive()) {
return makeNameForPrimitive(type);
} else if (type.isArray()) {
return makeNameForType(type.getArrayRootElement()) + "Arr";
} else {
return makeNameForObject(type);
}
if (type.isArray()) {
return makeNameForType(type.getArrayRootElement()) + "Arr";
}
return makeNameForObject(type);
}
private static String makeNameForPrimitive(ArgType type) {
@@ -158,12 +154,15 @@ public class NameGen {
if (alias != null) {
return alias;
}
ClassInfo extClsInfo = ClassInfo.extCls(mth.dex(), type);
ClassInfo extClsInfo = ClassInfo.extCls(mth.root(), type);
String shortName = extClsInfo.getShortName();
String vName = fromName(shortName);
if (vName != null) {
return vName;
}
if (shortName != null) {
return StringUtils.escape(shortName.toLowerCase());
}
}
return StringUtils.escape(type.toString());
}
@@ -1,10 +1,18 @@
package jadx.core.codegen;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
import jadx.core.dex.attributes.nodes.ForceReturnAttr;
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.instructions.SwitchNode;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.NamedArg;
@@ -32,12 +40,6 @@ import jadx.core.utils.RegionUtils;
import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RegionGen extends InsnGen {
private static final Logger LOG = LoggerFactory.getLogger(RegionGen.class);
@@ -134,17 +136,16 @@ public class RegionGen extends InsnGen {
* Connect if-else-if block
*/
private boolean connectElseIf(CodeWriter code, IContainer els) throws CodegenException {
if (!els.contains(AFlag.ELSE_IF_CHAIN)) {
return false;
}
if (!(els instanceof Region)) {
return false;
}
List<IContainer> subBlocks = ((Region) els).getSubBlocks();
if (subBlocks.size() == 1
&& subBlocks.get(0) instanceof IfRegion) {
makeIf((IfRegion) subBlocks.get(0), code, false);
return true;
if (els.contains(AFlag.ELSE_IF_CHAIN) && els instanceof Region) {
List<IContainer> subBlocks = ((Region) els).getSubBlocks();
if (subBlocks.size() == 1) {
IContainer elseBlock = subBlocks.get(0);
if (elseBlock instanceof IfRegion) {
declareVars(code, elseBlock);
makeIf((IfRegion) elseBlock, code, false);
return true;
}
}
}
return false;
}
@@ -154,7 +155,7 @@ public class RegionGen extends InsnGen {
if (header != null) {
List<InsnNode> headerInsns = header.getInstructions();
if (headerInsns.size() > 1) {
ErrorsCounter.methodError(mth, "Found not inlined instructions from loop header");
ErrorsCounter.methodWarn(mth, "Found not inlined instructions from loop header");
int last = headerInsns.size() - 1;
for (int i = 0; i < last; i++) {
InsnNode insn = headerInsns.get(i);
@@ -207,11 +208,13 @@ public class RegionGen extends InsnGen {
if (region.isConditionAtEnd()) {
code.startLine("do {");
makeRegionIndent(code, region.getBody());
code.startLine("} while (");
code.startLineWithNum(region.getConditionSourceLine());
code.add("} while (");
conditionGen.add(code, condition);
code.add(");");
} else {
code.startLine("while (");
code.startLineWithNum(region.getConditionSourceLine());
code.add("while (");
conditionGen.add(code, condition);
code.add(") {");
makeRegionIndent(code, region.getBody());
@@ -305,16 +308,23 @@ public class RegionGen extends InsnGen {
return;
}
code.startLine("} catch (");
if (handler.isCatchAll()) {
code.add("Throwable");
} else {
Iterator<ClassInfo> it = handler.getCatchTypes().iterator();
if (it.hasNext()) {
useClass(code, it.next());
}
while (it.hasNext()) {
code.add(" | ");
useClass(code, it.next());
}
}
code.add(' ');
InsnArg arg = handler.getArg();
if (arg instanceof RegisterArg) {
declareVar(code, (RegisterArg) arg);
code.add(mgen.getNameGen().assignArg((RegisterArg) arg));
} else if (arg instanceof NamedArg) {
if (handler.isCatchAll()) {
code.add("Throwable");
} else {
useClass(code, handler.getCatchType());
}
code.add(' ');
code.add(mgen.getNameGen().assignNamedArg((NamedArg) arg));
}
code.add(") {");
@@ -1,6 +1,10 @@
package jadx.core.codegen;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs;
import jadx.core.deobf.NameMapper;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.PrimitiveType;
import jadx.core.dex.nodes.IDexNode;
@@ -8,9 +12,6 @@ import jadx.core.utils.StringUtils;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TypeGen {
private static final Logger LOG = LoggerFactory.getLogger(TypeGen.class);
@@ -57,7 +58,11 @@ public class TypeGen {
case BOOLEAN:
return lit == 0 ? "false" : "true";
case CHAR:
return stringUtils.unescapeChar((char) lit);
char ch = (char) lit;
if (!NameMapper.isPrintableChar(ch)) {
return Integer.toString(ch);
}
return stringUtils.unescapeChar(ch);
case BYTE:
return formatByte((byte) lit);
case SHORT:
@@ -171,5 +176,4 @@ public class TypeGen {
}
return Float.toString(f) + "f";
}
}
@@ -1,9 +1,5 @@
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;
@@ -16,6 +12,10 @@ import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
class DeobfPresets {
private static final Logger LOG = LoggerFactory.getLogger(DeobfPresets.class);
@@ -24,9 +24,9 @@ class DeobfPresets {
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>();
private final Map<String, String> clsPresetMap = new HashMap<>();
private final Map<String, String> fldPresetMap = new HashMap<>();
private final Map<String, String> mthPresetMap = new HashMap<>();
public DeobfPresets(Deobfuscator deobfuscator, File deobfMapFile) {
this.deobfuscator = deobfuscator;
@@ -98,7 +98,7 @@ class DeobfPresets {
* Saves DefaultDeobfuscator presets
*/
private void dumpMapping() throws IOException {
List<String> list = new ArrayList<String>();
List<String> list = new ArrayList<>();
// packages
for (PackageNode p : deobfuscator.getRootPackage().getInnerPackages()) {
for (PackageNode pp : p.getInnerPackages()) {
@@ -112,18 +112,20 @@ class DeobfPresets {
for (DeobfClsInfo deobfClsInfo : deobfuscator.getClsMap().values()) {
if (deobfClsInfo.getAlias() != null) {
list.add(String.format("c %s = %s",
deobfClsInfo.getCls().getClassInfo().getFullName(), deobfClsInfo.getAlias()));
deobfClsInfo.getCls().getClassInfo().makeRawFullName(), deobfClsInfo.getAlias()));
}
}
for (FieldInfo fld : deobfuscator.getFldMap().keySet()) {
list.add(String.format("f %s = %s", fld.getFullId(), fld.getAlias()));
list.add(String.format("f %s = %s", fld.getRawFullId(), fld.getAlias()));
}
for (MethodInfo mth : deobfuscator.getMthMap().keySet()) {
list.add(String.format("m %s = %s", mth.getFullId(), mth.getAlias()));
list.add(String.format("m %s = %s", mth.getRawFullId(), mth.getAlias()));
}
Collections.sort(list);
FileUtils.writeLines(deobfMapFile, MAP_FILE_CHARSET, list);
list.clear();
if (LOG.isDebugEnabled()) {
LOG.debug("Deobfuscation map file saved as: {}", deobfMapFile);
}
}
private static void dfsPackageName(List<String> list, String prefix, PackageNode node) {
@@ -136,15 +138,15 @@ class DeobfPresets {
}
public String getForCls(ClassInfo cls) {
return clsPresetMap.get(cls.getFullName());
return clsPresetMap.get(cls.makeRawFullName());
}
public String getForFld(FieldInfo fld) {
return fldPresetMap.get(fld.getFullId());
return fldPresetMap.get(fld.getRawFullId());
}
public String getForMth(MethodInfo mth) {
return mthPresetMap.get(mth.getFullId());
return mthPresetMap.get(mth.getRawFullId());
}
public void clear() {
@@ -1,6 +1,22 @@
package jadx.core.deobf;
import jadx.api.IJadxArgs;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
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;
import jadx.api.JadxArgs;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.SourceFileAttr;
import jadx.core.dex.info.ClassInfo;
@@ -12,21 +28,6 @@ import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Deobfuscator {
private static final Logger LOG = LoggerFactory.getLogger(Deobfuscator.class);
@@ -35,20 +36,21 @@ public class Deobfuscator {
public static final String CLASS_NAME_SEPARATOR = ".";
public static final String INNER_CLASS_SEPARATOR = "$";
private final IJadxArgs args;
private final JadxArgs args;
@NotNull
private final List<DexNode> dexNodes;
private final DeobfPresets deobfPresets;
private final Map<ClassInfo, DeobfClsInfo> clsMap = new HashMap<ClassInfo, DeobfClsInfo>();
private final Map<FieldInfo, String> fldMap = new HashMap<FieldInfo, String>();
private final Map<MethodInfo, String> mthMap = new HashMap<MethodInfo, String>();
private final Map<ClassInfo, DeobfClsInfo> clsMap = new HashMap<>();
private final Map<FieldInfo, String> fldMap = new HashMap<>();
private final Map<MethodInfo, String> mthMap = new HashMap<>();
private final Map<MethodInfo, OverridedMethodsNode> ovrdMap = new HashMap<MethodInfo, OverridedMethodsNode>();
private final List<OverridedMethodsNode> ovrd = new ArrayList<OverridedMethodsNode>();
private final Map<MethodInfo, OverridedMethodsNode> ovrdMap = new HashMap<>();
private final List<OverridedMethodsNode> ovrd = new ArrayList<>();
private final PackageNode rootPackage = new PackageNode("");
private final Set<String> pkgSet = new TreeSet<String>();
private final Set<String> pkgSet = new TreeSet<>();
private final Set<String> reservedClsNames = new HashSet<>();
private final int maxLength;
private final int minLength;
@@ -59,13 +61,13 @@ public class Deobfuscator {
private int fldIndex = 0;
private int mthIndex = 0;
public Deobfuscator(IJadxArgs args, @NotNull List<DexNode> dexNodes, File deobfMapFile) {
public Deobfuscator(JadxArgs args, @NotNull List<DexNode> dexNodes, File deobfMapFile) {
this.args = args;
this.dexNodes = dexNodes;
this.minLength = args.getDeobfuscationMinLength();
this.maxLength = args.getDeobfuscationMaxLength();
this.useSourceNameAsAlias = args.useSourceNameAsClassAlias();
this.useSourceNameAsAlias = args.isUseSourceNameAsClassAlias();
this.deobfPresets = new DeobfPresets(this, deobfMapFile);
}
@@ -90,7 +92,12 @@ public class Deobfuscator {
private void preProcess() {
for (DexNode dexNode : dexNodes) {
for (ClassNode cls : dexNode.getClasses()) {
doClass(cls);
Collections.addAll(reservedClsNames, cls.getPackage().split("\\."));
}
}
for (DexNode dexNode : dexNodes) {
for (ClassNode cls : dexNode.getClasses()) {
preProcessClass(cls);
}
}
}
@@ -102,7 +109,7 @@ public class Deobfuscator {
}
for (DexNode dexNode : dexNodes) {
for (ClassNode cls : dexNode.getClasses()) {
processClass(dexNode, cls);
processClass(cls);
}
}
postProcess();
@@ -111,24 +118,24 @@ public class Deobfuscator {
private void postProcess() {
int id = 1;
for (OverridedMethodsNode o : ovrd) {
Iterator<MethodInfo> it = o.getMethods().iterator();
if (it.hasNext()) {
MethodInfo mth = it.next();
if (mth.isRenamed() && !mth.isAliasFromPreset()) {
mth.setAlias(String.format("mo%d%s", id, makeName(mth.getName())));
}
String firstMethodAlias = mth.getAlias();
while (it.hasNext()) {
mth = it.next();
if (!mth.getAlias().equals(firstMethodAlias)) {
mth.setAlias(firstMethodAlias);
}
boolean aliasFromPreset = false;
String aliasToUse = null;
for (MethodInfo mth : o.getMethods()) {
if (mth.isAliasFromPreset()) {
aliasToUse = mth.getAlias();
aliasFromPreset = true;
}
}
for (MethodInfo mth : o.getMethods()) {
if (aliasToUse == null) {
if (mth.isRenamed() && !mth.isAliasFromPreset()) {
mth.setAlias(String.format("mo%d%s", id, prepareNamePart(mth.getName())));
}
aliasToUse = mth.getAlias();
}
mth.setAlias(aliasToUse);
mth.setAliasFromPreset(aliasFromPreset);
}
id++;
}
}
@@ -143,118 +150,117 @@ public class Deobfuscator {
ovrdMap.clear();
}
@Nullable
private static ClassNode resolveOverridingInternal(DexNode dex, ClassNode cls, String signature,
Set<MethodInfo> overrideSet, ClassNode rootClass) {
ClassNode result = null;
private void resolveOverriding(MethodNode mth) {
Set<ClassNode> clsParents = new LinkedHashSet<>();
collectClassHierarchy(mth.getParentClass(), clsParents);
for (MethodNode m : cls.getMethods()) {
if (m.getMethodInfo().getShortId().startsWith(signature)) {
result = cls;
if (!overrideSet.contains(m.getMethodInfo())) {
overrideSet.add(m.getMethodInfo());
}
break;
String mthSignature = mth.getMethodInfo().makeSignature(false);
Set<MethodInfo> overrideSet = new LinkedHashSet<>();
for (ClassNode classNode : clsParents) {
MethodInfo methodInfo = getMthOverride(classNode.getMethods(), mthSignature);
if (methodInfo != null) {
overrideSet.add(methodInfo);
}
}
ArgType superClass = cls.getSuperClass();
if (superClass != null) {
ClassNode superNode = dex.resolveClass(superClass);
if (superNode != null) {
ClassNode clsWithMth = resolveOverridingInternal(dex, superNode, signature, overrideSet, rootClass);
if (clsWithMth != null) {
if ((result != null) && (result != cls)) {
if (clsWithMth != result) {
LOG.warn(String.format("Multiple overriding '%s' from '%s' and '%s' in '%s'",
signature,
result.getFullName(), clsWithMth.getFullName(),
rootClass.getFullName()));
}
} else {
result = clsWithMth;
}
}
}
if (overrideSet.isEmpty()) {
return;
}
for (ArgType iFaceType : cls.getInterfaces()) {
ClassNode iFaceNode = dex.resolveClass(iFaceType);
if (iFaceNode != null) {
ClassNode clsWithMth = resolveOverridingInternal(dex, iFaceNode, signature, overrideSet, rootClass);
if (clsWithMth != null) {
if ((result != null) && (result != cls)) {
if (clsWithMth != result) {
LOG.warn(String.format("Multiple overriding '%s' from '%s' and '%s' in '%s'",
signature,
result.getFullName(), clsWithMth.getFullName(),
rootClass.getFullName()));
}
} else {
result = clsWithMth;
}
}
}
OverridedMethodsNode overrideNode = getOverrideMethodsNode(overrideSet);
if (overrideNode == null) {
overrideNode = new OverridedMethodsNode(overrideSet);
ovrd.add(overrideNode);
}
return result;
}
private void resolveOverriding(DexNode dex, ClassNode cls, MethodNode mth) {
Set<MethodInfo> overrideSet = new HashSet<MethodInfo>();
resolveOverridingInternal(dex, cls, mth.getMethodInfo().makeSignature(false), overrideSet, cls);
if (overrideSet.size() > 1) {
OverridedMethodsNode overrideNode = null;
for (MethodInfo _mth : overrideSet) {
if (ovrdMap.containsKey(_mth)) {
overrideNode = ovrdMap.get(_mth);
break;
}
for (MethodInfo overrideMth : overrideSet) {
if (!ovrdMap.containsKey(overrideMth)) {
ovrdMap.put(overrideMth, overrideNode);
overrideNode.add(overrideMth);
}
if (overrideNode == null) {
overrideNode = new OverridedMethodsNode(overrideSet);
ovrd.add(overrideNode);
}
for (MethodInfo _mth : overrideSet) {
if (!ovrdMap.containsKey(_mth)) {
ovrdMap.put(_mth, overrideNode);
if (!overrideNode.contains(_mth)) {
overrideNode.add(_mth);
}
}
}
} else {
overrideSet.clear();
overrideSet = null;
}
}
private void processClass(DexNode dex, ClassNode cls) {
private OverridedMethodsNode getOverrideMethodsNode(Set<MethodInfo> overrideSet) {
for (MethodInfo overrideMth : overrideSet) {
OverridedMethodsNode node = ovrdMap.get(overrideMth);
if (node != null) {
return node;
}
}
return null;
}
private MethodInfo getMthOverride(List<MethodNode> methods, String mthSignature) {
for (MethodNode m : methods) {
MethodInfo mthInfo = m.getMethodInfo();
if (mthInfo.getShortId().startsWith(mthSignature)) {
return mthInfo;
}
}
return null;
}
private void collectClassHierarchy(ClassNode cls, Set<ClassNode> collected) {
boolean added = collected.add(cls);
if (added) {
ArgType superClass = cls.getSuperClass();
if (superClass != null) {
ClassNode superNode = cls.dex().resolveClass(superClass);
if (superNode != null) {
collectClassHierarchy(superNode, collected);
}
}
for (ArgType argType : cls.getInterfaces()) {
ClassNode interfaceNode = cls.dex().resolveClass(argType);
if (interfaceNode != null) {
collectClassHierarchy(interfaceNode, collected);
}
}
}
}
private void processClass(ClassNode cls) {
ClassInfo clsInfo = cls.getClassInfo();
String fullName = getClassFullName(clsInfo);
if (!fullName.equals(clsInfo.getFullName())) {
clsInfo.rename(dex, fullName);
clsInfo.rename(cls.dex().root(), fullName);
}
for (FieldNode field : cls.getFields()) {
FieldInfo fieldInfo = field.getFieldInfo();
String alias = getFieldAlias(field);
if (alias != null) {
fieldInfo.setAlias(alias);
}
renameField(field);
}
for (MethodNode mth : cls.getMethods()) {
MethodInfo methodInfo = mth.getMethodInfo();
String alias = getMethodAlias(mth);
if (alias != null) {
methodInfo.setAlias(alias);
}
renameMethod(mth);
}
for (ClassNode innerCls : cls.getInnerClasses()) {
processClass(innerCls);
}
}
if (mth.isVirtual()) {
resolveOverriding(dex, cls, mth);
}
public void renameField(FieldNode field) {
FieldInfo fieldInfo = field.getFieldInfo();
String alias = getFieldAlias(field);
if (alias != null) {
fieldInfo.setAlias(alias);
}
}
public void forceRenameField(FieldNode field) {
field.getFieldInfo().setAlias(makeFieldAlias(field));
}
public void renameMethod(MethodNode mth) {
String alias = getMethodAlias(mth);
if (alias != null) {
mth.getMethodInfo().setAlias(alias);
}
if (mth.isVirtual()) {
resolveOverriding(mth);
}
}
public void forceRenameMethod(MethodNode mth) {
mth.getMethodInfo().setAlias(makeMethodAlias(mth));
if (mth.isVirtual()) {
resolveOverriding(mth);
}
}
@@ -315,7 +321,7 @@ public class Deobfuscator {
return prefix + clsInfo.getShortName();
}
private void doClass(ClassNode cls) {
private void preProcessClass(ClassNode cls) {
ClassInfo classInfo = cls.getClassInfo();
String pkgFullName = classInfo.getPackage();
PackageNode pkg = getPackageNode(pkgFullName, true);
@@ -324,13 +330,16 @@ public class Deobfuscator {
String alias = deobfPresets.getForCls(classInfo);
if (alias != null) {
clsMap.put(classInfo, new DeobfClsInfo(this, cls, pkg, alias));
return;
} else {
if (!clsMap.containsKey(classInfo)) {
String clsShortName = classInfo.getShortName();
if (shouldRename(clsShortName) || reservedClsNames.contains(clsShortName)) {
makeClsAlias(cls);
}
}
}
if (clsMap.containsKey(classInfo)) {
return;
}
if (shouldRename(classInfo.getShortName())) {
makeClsAlias(cls);
for (ClassNode innerCls : cls.getInnerClasses()) {
preProcessClass(innerCls);
}
}
@@ -352,7 +361,7 @@ public class Deobfuscator {
if (alias == null) {
String clsName = classInfo.getShortName();
alias = String.format("C%04d%s", clsIndex++, makeName(clsName));
alias = String.format("C%04d%s", clsIndex++, prepareNamePart(clsName));
}
PackageNode pkg = getPackageNode(classInfo.getPackage(), true);
clsMap.put(classInfo, new DeobfClsInfo(this, cls, pkg, alias));
@@ -365,21 +374,33 @@ public class Deobfuscator {
if (sourceFileAttr == null) {
return null;
}
if (cls.getClassInfo().isInner()) {
return null;
}
String name = sourceFileAttr.getFileName();
if (name.endsWith(".java")) {
name = name.substring(0, name.length() - ".java".length());
} else if (name.endsWith(".kt")) {
name = name.substring(0, name.length() - ".kt".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;
if (!NameMapper.isValidIdentifier(name) || NameMapper.isReserved(name)) {
return null;
}
return null;
for (DeobfClsInfo deobfClsInfo : clsMap.values()) {
if (deobfClsInfo.getAlias().equals(name)) {
return null;
}
}
ClassNode otherCls = cls.dex().root().searchClassByName(cls.getPackage() + "." + name);
if (otherCls != null) {
return null;
}
cls.remove(AType.SOURCE_FILE);
return name;
}
@Nullable
public String getFieldAlias(FieldNode field) {
private String getFieldAlias(FieldNode field) {
FieldInfo fieldInfo = field.getFieldInfo();
String alias = fldMap.get(fieldInfo);
if (alias != null) {
@@ -397,8 +418,11 @@ public class Deobfuscator {
}
@Nullable
public String getMethodAlias(MethodNode mth) {
private String getMethodAlias(MethodNode mth) {
MethodInfo methodInfo = mth.getMethodInfo();
if (methodInfo.isClassInit() || methodInfo.isConstructor()) {
return null;
}
String alias = mthMap.get(methodInfo);
if (alias != null) {
return alias;
@@ -416,13 +440,13 @@ public class Deobfuscator {
}
public String makeFieldAlias(FieldNode field) {
String alias = String.format("f%d%s", fldIndex++, makeName(field.getName()));
String alias = String.format("f%d%s", fldIndex++, prepareNamePart(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()));
String alias = String.format("m%d%s", mthIndex++, prepareNamePart(mth.getName()));
mthMap.put(mth.getMethodInfo(), alias);
return alias;
}
@@ -442,27 +466,23 @@ public class Deobfuscator {
parentPkg = parentPkg.getParentPackage();
}
final String pkgName = pkg.getName();
String pkgName = pkg.getName();
if (!pkg.hasAlias() && shouldRename(pkgName)) {
final String pkgAlias = String.format("p%03d%s", pkgIndex++, makeName(pkgName));
String pkgAlias = String.format("p%03d%s", pkgIndex++, prepareNamePart(pkgName));
pkg.setAlias(pkgAlias);
}
}
private boolean shouldRename(String s) {
return s.length() > maxLength
|| s.length() < minLength
|| NameMapper.isReserved(s)
|| !NameMapper.isAllCharsPrintable(s);
int len = s.length();
return len < minLength || len > maxLength
|| !NameMapper.isValidIdentifier(s);
}
private String makeName(String name) {
private String prepareNamePart(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);
}
@@ -501,7 +521,7 @@ public class Deobfuscator {
}
private String getPackageName(String packageName) {
final PackageNode pkg = getPackageNode(packageName, false);
PackageNode pkg = getPackageNode(packageName, false);
if (pkg != null) {
return pkg.getFullAlias();
}
@@ -509,7 +529,7 @@ public class Deobfuscator {
}
private String getClassName(ClassInfo clsInfo) {
final DeobfClsInfo deobfClsInfo = clsMap.get(clsInfo);
DeobfClsInfo deobfClsInfo = clsMap.get(clsInfo);
if (deobfClsInfo != null) {
return deobfClsInfo.makeNameWithoutPkg();
}
@@ -521,7 +541,7 @@ public class Deobfuscator {
}
private String getClassFullName(ClassInfo clsInfo) {
final DeobfClsInfo deobfClsInfo = clsMap.get(clsInfo);
DeobfClsInfo deobfClsInfo = clsMap.get(clsInfo);
if (deobfClsInfo != null) {
return deobfClsInfo.getFullName();
}
@@ -5,6 +5,8 @@ import java.util.HashSet;
import java.util.Set;
import java.util.regex.Pattern;
import static jadx.core.utils.StringUtils.notEmpty;
public class NameMapper {
private static final Pattern VALID_JAVA_IDENTIFIER = Pattern.compile(
@@ -13,8 +15,8 @@ public class NameMapper {
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[]{
private static final Set<String> RESERVED_NAMES = new HashSet<>(
Arrays.asList(
"abstract",
"assert",
"boolean",
@@ -67,8 +69,8 @@ public class NameMapper {
"try",
"void",
"volatile",
"while",
})
"while"
)
);
public static boolean isReserved(String str) {
@@ -76,11 +78,17 @@ public class NameMapper {
}
public static boolean isValidIdentifier(String str) {
return VALID_JAVA_IDENTIFIER.matcher(str).matches() && isAllCharsPrintable(str);
return notEmpty(str)
&& !isReserved(str)
&& VALID_JAVA_IDENTIFIER.matcher(str).matches()
&& isAllCharsPrintable(str);
}
public static boolean isValidFullIdentifier(String str) {
return VALID_JAVA_FULL_IDENTIFIER.matcher(str).matches() && isAllCharsPrintable(str);
return notEmpty(str)
&& !isReserved(str)
&& VALID_JAVA_FULL_IDENTIFIER.matcher(str).matches()
&& isAllCharsPrintable(str);
}
public static boolean isPrintableChar(int c) {
@@ -96,4 +104,7 @@ public class NameMapper {
}
return true;
}
private NameMapper() {
}
}
@@ -1,10 +1,10 @@
package jadx.core.deobf;
import jadx.core.dex.info.MethodInfo;
import java.util.Set;
/* package */ class OverridedMethodsNode {
import jadx.core.dex.info.MethodInfo;
class OverridedMethodsNode {
private Set<MethodInfo> methods;
@@ -1,9 +1,10 @@
package jadx.core.deobf;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.List;
import java.util.Stack;
public class PackageNode {
@@ -29,11 +30,11 @@ public class PackageNode {
public String getFullName() {
if (cachedPackageFullName == null) {
Stack<PackageNode> pp = getParentPackages();
Deque<PackageNode> pp = getParentPackages();
StringBuilder result = new StringBuilder();
result.append(pp.pop().getName());
while (pp.size() > 0) {
while (!pp.isEmpty()) {
result.append(SEPARATOR_CHAR);
result.append(pp.pop().getName());
}
@@ -59,12 +60,12 @@ public class PackageNode {
public String getFullAlias() {
if (cachedPackageFullAlias == null) {
Stack<PackageNode> pp = getParentPackages();
Deque<PackageNode> pp = getParentPackages();
StringBuilder result = new StringBuilder();
if (pp.size() > 0) {
if (!pp.isEmpty()) {
result.append(pp.pop().getAlias());
while (pp.size() > 0) {
while (!pp.isEmpty()) {
result.append(SEPARATOR_CHAR);
result.append(pp.pop().getAlias());
}
@@ -86,7 +87,7 @@ public class PackageNode {
public void addInnerPackage(PackageNode pkg) {
if (innerPackages.isEmpty()) {
innerPackages = new ArrayList<PackageNode>();
innerPackages = new ArrayList<>();
}
innerPackages.add(pkg);
pkg.parentPackage = this;
@@ -114,16 +115,15 @@ public class PackageNode {
*
* @return stack with parent packages
*/
private Stack<PackageNode> getParentPackages() {
Stack<PackageNode> pp = new Stack<PackageNode>();
private Deque<PackageNode> getParentPackages() {
Deque<PackageNode> pp = new ArrayDeque<>();
PackageNode currentP = this;
PackageNode parentP = currentP.getParentPackage();
while (currentP != parentP) {
pp.push(currentP);
currentP = parentP;
parentP = currentP.getParentPackage();
PackageNode currentPkg = this;
PackageNode parentPkg = currentPkg.getParentPackage();
while (currentPkg != parentPkg) {
pp.push(currentPkg);
currentPkg = parentPkg;
parentPkg = currentPkg.getParentPackage();
}
return pp;
}
@@ -26,6 +26,7 @@ public enum AFlag {
SKIP_ARG, // skip argument in invoke call
ANONYMOUS_CONSTRUCTOR,
ANONYMOUS_CLASS,
THIS,
ELSE_IF_CHAIN,
@@ -9,7 +9,8 @@ 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.JadxError;
import jadx.core.dex.attributes.nodes.JadxWarn;
import jadx.core.dex.attributes.nodes.JumpInfo;
import jadx.core.dex.attributes.nodes.LoopInfo;
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
@@ -29,25 +30,31 @@ import jadx.core.dex.trycatch.SplitterBlockAttr;
*/
public class AType<T extends IAttribute> {
public static final AType<AttrList<JumpInfo>> JUMP = new AType<AttrList<JumpInfo>>();
public static final AType<AttrList<LoopInfo>> LOOP = new AType<AttrList<LoopInfo>>();
public static final AType<AttrList<EdgeInsnAttr>> EDGE_INSN = new AType<AttrList<EdgeInsnAttr>>();
public static final AType<AttrList<JumpInfo>> JUMP = new AType<>();
public static final AType<AttrList<LoopInfo>> LOOP = new AType<>();
public static final AType<AttrList<EdgeInsnAttr>> EDGE_INSN = new AType<>();
public static final AType<ExcHandlerAttr> EXC_HANDLER = new AType<ExcHandlerAttr>();
public static final AType<CatchAttr> CATCH_BLOCK = new AType<CatchAttr>();
public static final AType<SplitterBlockAttr> SPLITTER_BLOCK = new AType<SplitterBlockAttr>();
public static final AType<ForceReturnAttr> FORCE_RETURN = new AType<ForceReturnAttr>();
public static final AType<FieldInitAttr> FIELD_INIT = new AType<FieldInitAttr>();
public static final AType<FieldReplaceAttr> FIELD_REPLACE = new AType<FieldReplaceAttr>();
public static final AType<JadxErrorAttr> JADX_ERROR = new AType<JadxErrorAttr>();
public static final AType<MethodInlineAttr> METHOD_INLINE = new AType<MethodInlineAttr>();
public static final AType<EnumClassAttr> ENUM_CLASS = new AType<EnumClassAttr>();
public static final AType<EnumMapAttr> ENUM_MAP = new AType<EnumMapAttr>();
public static final AType<AnnotationsList> ANNOTATION_LIST = new AType<AnnotationsList>();
public static final AType<MethodParameters> ANNOTATION_MTH_PARAMETERS = new AType<MethodParameters>();
public static final AType<PhiListAttr> PHI_LIST = new AType<PhiListAttr>();
public static final AType<SourceFileAttr> SOURCE_FILE = new AType<SourceFileAttr>();
public static final AType<DeclareVariablesAttr> DECLARE_VARIABLES = new AType<DeclareVariablesAttr>();
public static final AType<LoopLabelAttr> LOOP_LABEL = new AType<LoopLabelAttr>();
public static final AType<IgnoreEdgeAttr> IGNORE_EDGE = new AType<IgnoreEdgeAttr>();
public static final AType<AttrList<JadxError>> JADX_ERROR = new AType<>();
public static final AType<AttrList<JadxWarn>> JADX_WARN = new AType<>();
public static final AType<AttrList<String>> COMMENTS = new AType<>();
public static final AType<ExcHandlerAttr> EXC_HANDLER = new AType<>();
public static final AType<CatchAttr> CATCH_BLOCK = new AType<>();
public static final AType<SplitterBlockAttr> SPLITTER_BLOCK = new AType<>();
public static final AType<ForceReturnAttr> FORCE_RETURN = new AType<>();
public static final AType<FieldInitAttr> FIELD_INIT = new AType<>();
public static final AType<FieldReplaceAttr> FIELD_REPLACE = new AType<>();
public static final AType<MethodInlineAttr> METHOD_INLINE = new AType<>();
public static final AType<EnumClassAttr> ENUM_CLASS = new AType<>();
public static final AType<EnumMapAttr> ENUM_MAP = new AType<>();
public static final AType<AnnotationsList> ANNOTATION_LIST = new AType<>();
public static final AType<MethodParameters> ANNOTATION_MTH_PARAMETERS = new AType<>();
public static final AType<PhiListAttr> PHI_LIST = new AType<>();
public static final AType<SourceFileAttr> SOURCE_FILE = new AType<>();
public static final AType<DeclareVariablesAttr> DECLARE_VARIABLES = new AType<>();
public static final AType<LoopLabelAttr> LOOP_LABEL = new AType<>();
public static final AType<IgnoreEdgeAttr> IGNORE_EDGE = new AType<>();
private AType() {
}
}
@@ -1,14 +1,14 @@
package jadx.core.dex.attributes;
import jadx.core.utils.Utils;
import java.util.LinkedList;
import java.util.ArrayList;
import java.util.List;
import jadx.core.utils.Utils;
public class AttrList<T> implements IAttribute {
private final AType<AttrList<T>> type;
private final List<T> list = new LinkedList<T>();
private final List<T> list = new ArrayList<>();
public AttrList(AType<AttrList<T>> type) {
this.type = type;
@@ -1,9 +1,9 @@
package jadx.core.dex.attributes;
import jadx.core.dex.attributes.annotations.Annotation;
import java.util.List;
import jadx.core.dex.attributes.annotations.Annotation;
public abstract class AttrNode implements IAttributeNode {
private static final AttributeStorage EMPTY_ATTR_STORAGE = new EmptyAttrStorage();
@@ -96,4 +96,8 @@ public abstract class AttrNode implements IAttributeNode {
public String getAttributesString() {
return storage.toString();
}
public boolean isAttrStorageEmpty() {
return storage.isEmpty();
}
}
@@ -1,9 +1,5 @@
package jadx.core.dex.attributes;
import jadx.core.dex.attributes.annotations.Annotation;
import jadx.core.dex.attributes.annotations.AnnotationsList;
import jadx.core.utils.Utils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
@@ -12,6 +8,10 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import jadx.core.dex.attributes.annotations.Annotation;
import jadx.core.dex.attributes.annotations.AnnotationsList;
import jadx.core.utils.Utils;
/**
* Storage for different attribute types:
* 1. flags - boolean attribute (set or not)
@@ -24,7 +24,7 @@ public class AttributeStorage {
public AttributeStorage() {
flags = EnumSet.noneOf(AFlag.class);
attributes = new IdentityHashMap<AType<?>, IAttribute>();
attributes = new IdentityHashMap<>();
}
public void add(AFlag flag) {
@@ -38,7 +38,7 @@ public class AttributeStorage {
public <T> void add(AType<AttrList<T>> type, T obj) {
AttrList<T> list = get(type);
if (list == null) {
list = new AttrList<T>(type);
list = new AttrList<>(type);
add(list);
}
list.getList().add(obj);
@@ -84,7 +84,7 @@ public class AttributeStorage {
}
public void remove(IAttribute attr) {
AType<?> type = attr.getType();
AType<? extends IAttribute> type = attr.getType();
IAttribute a = attributes.get(type);
if (a == attr) {
attributes.remove(type);
@@ -101,7 +101,7 @@ public class AttributeStorage {
if (size == 0) {
return Collections.emptyList();
}
List<String> list = new ArrayList<String>(size);
List<String> list = new ArrayList<>(size);
for (AFlag a : flags) {
list.add(a.toString());
}
@@ -1,10 +1,10 @@
package jadx.core.dex.attributes;
import jadx.core.dex.attributes.annotations.Annotation;
import java.util.Collections;
import java.util.List;
import jadx.core.dex.attributes.annotations.Annotation;
public final class EmptyAttrStorage extends AttributeStorage {
@Override
@@ -34,18 +34,22 @@ public final class EmptyAttrStorage extends AttributeStorage {
@Override
public void clear() {
// ignore
}
@Override
public void remove(AFlag flag) {
// ignore
}
@Override
public <T extends IAttribute> void remove(AType<T> type) {
// ignore
}
@Override
public void remove(IAttribute attr) {
// ignore
}
@Override
@@ -1,7 +1,5 @@
package jadx.core.dex.attributes;
public interface IAttribute {
AType<?> getType();
<T extends IAttribute> AType<T> getType();
}
@@ -1,9 +1,9 @@
package jadx.core.dex.attributes;
import jadx.core.dex.attributes.annotations.Annotation;
import java.util.List;
import jadx.core.dex.attributes.annotations.Annotation;
public interface IAttributeNode {
void add(AFlag flag);
@@ -1,9 +1,9 @@
package jadx.core.dex.attributes.annotations;
import jadx.core.dex.instructions.args.ArgType;
import java.util.Map;
import jadx.core.dex.instructions.args.ArgType;
public class Annotation {
public enum Visibility {
@@ -44,5 +44,4 @@ public class Annotation {
public String toString() {
return "Annotation[" + visibility + ", " + atype + ", " + values + "]";
}
}
@@ -1,15 +1,15 @@
package jadx.core.dex.attributes.annotations;
import jadx.core.dex.attributes.AType;
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;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.utils.Utils;
public class AnnotationsList implements IAttribute {
public static final AnnotationsList EMPTY = new AnnotationsList(Collections.<Annotation>emptyList());
@@ -17,7 +17,7 @@ public class AnnotationsList implements IAttribute {
private final Map<String, Annotation> map;
public AnnotationsList(List<Annotation> anList) {
map = new HashMap<String, Annotation>(anList.size());
map = new HashMap<>(anList.size());
for (Annotation a : anList) {
map.put(a.getAnnotationClass(), a);
}
@@ -48,5 +48,4 @@ public class AnnotationsList implements IAttribute {
public String toString() {
return Utils.listToString(map.values());
}
}
@@ -1,18 +1,18 @@
package jadx.core.dex.attributes.annotations;
import java.util.ArrayList;
import java.util.List;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.utils.Utils;
import java.util.ArrayList;
import java.util.List;
public class MethodParameters implements IAttribute {
private final List<AnnotationsList> paramList;
public MethodParameters(int paramCount) {
paramList = new ArrayList<AnnotationsList>(paramCount);
paramList = new ArrayList<>(paramCount);
}
public List<AnnotationsList> getParamList() {
@@ -28,5 +28,4 @@ public class MethodParameters implements IAttribute {
public String toString() {
return Utils.listToString(paramList);
}
}
@@ -1,19 +1,19 @@
package jadx.core.dex.attributes.nodes;
import java.util.LinkedList;
import java.util.List;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.utils.Utils;
import java.util.LinkedList;
import java.util.List;
/**
* List of variables to be declared at region start.
*/
public class DeclareVariablesAttr implements IAttribute {
private final List<RegisterArg> vars = new LinkedList<RegisterArg>();
private final List<RegisterArg> vars = new LinkedList<>();
public Iterable<RegisterArg> getVars() {
return vars;
@@ -1,5 +1,8 @@
package jadx.core.dex.attributes.nodes;
import java.util.ArrayList;
import java.util.List;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.info.FieldInfo;
@@ -7,9 +10,6 @@ import jadx.core.dex.instructions.mods.ConstructorInsn;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.MethodNode;
import java.util.ArrayList;
import java.util.List;
public class EnumClassAttr implements IAttribute {
public static class EnumField {
@@ -54,7 +54,7 @@ public class EnumClassAttr implements IAttribute {
private MethodNode staticMethod;
public EnumClassAttr(int fieldsCount) {
this.fields = new ArrayList<EnumField>(fieldsCount);
this.fields = new ArrayList<>(fieldsCount);
}
public List<EnumField> getFields() {
@@ -78,5 +78,4 @@ public class EnumClassAttr implements IAttribute {
public String toString() {
return "Enum fields: " + fields;
}
}
@@ -1,16 +1,18 @@
package jadx.core.dex.attributes.nodes;
import java.util.HashMap;
import java.util.Map;
import org.jetbrains.annotations.Nullable;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.nodes.FieldNode;
import java.util.HashMap;
import java.util.Map;
public class EnumMapAttr implements IAttribute {
public static class KeyValueMap {
private final Map<Object, Object> map = new HashMap<Object, Object>();
private final Map<Object, Object> map = new HashMap<>();
public Object get(Object key) {
return map.get(key);
@@ -21,9 +23,14 @@ public class EnumMapAttr implements IAttribute {
}
}
private final Map<FieldNode, KeyValueMap> fieldsMap = new HashMap<FieldNode, KeyValueMap>();
@Nullable
private Map<FieldNode, KeyValueMap> fieldsMap;
@Nullable
public KeyValueMap getMap(FieldNode field) {
if (fieldsMap == null) {
return null;
}
return fieldsMap.get(field);
}
@@ -31,11 +38,18 @@ public class EnumMapAttr implements IAttribute {
KeyValueMap map = getMap(field);
if (map == null) {
map = new KeyValueMap();
if (fieldsMap == null) {
fieldsMap = new HashMap<>();
}
fieldsMap.put(field, map);
}
map.put(key, value);
}
public boolean isEmpty() {
return fieldsMap == null || fieldsMap.isEmpty();
}
@Override
public AType<EnumMapAttr> getType() {
return AType.ENUM_MAP;
@@ -45,5 +59,4 @@ public class EnumMapAttr implements IAttribute {
public String toString() {
return "Enum fields map: " + fieldsMap;
}
}
@@ -26,5 +26,4 @@ public class ForceReturnAttr implements IAttribute {
public String toString() {
return "FORCE_RETURN " + Utils.listToString(returnInsn.getArguments());
}
}
@@ -1,16 +1,16 @@
package jadx.core.dex.attributes.nodes;
import java.util.HashSet;
import java.util.Set;
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);
private final Set<BlockNode> blocks = new HashSet<>(3);
public Set<BlockNode> getBlocks() {
return blocks;
@@ -1,33 +1,38 @@
package jadx.core.dex.attributes.nodes;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.utils.Utils;
public class JadxErrorAttr implements IAttribute {
public class JadxError {
private final String error;
private final Throwable cause;
public JadxErrorAttr(Throwable cause) {
public JadxError(Throwable cause) {
this(null, cause);
}
public JadxError(String error, Throwable cause) {
this.error = error;
this.cause = cause;
}
public String getError() {
return error;
}
public Throwable getCause() {
return cause;
}
@Override
public AType<JadxErrorAttr> getType() {
return AType.JADX_ERROR;
}
@Override
public String toString() {
StringBuilder str = new StringBuilder();
str.append("JadxError: ");
if (cause == null) {
str.append("null");
} else {
if (error != null) {
str.append(error);
str.append(' ');
}
if (cause != null) {
str.append(cause.getClass());
str.append(":");
str.append(cause.getMessage());
@@ -36,5 +41,4 @@ public class JadxErrorAttr implements IAttribute {
}
return str.toString();
}
}
@@ -0,0 +1,21 @@
package jadx.core.dex.attributes.nodes;
import java.util.Objects;
public class JadxWarn {
private final String warn;
public JadxWarn(String warn) {
this.warn = Objects.requireNonNull(warn);
}
public String getWarn() {
return warn;
}
@Override
public String toString() {
return "JadxWarn: " + warn;
}
}
@@ -1,16 +1,16 @@
package jadx.core.dex.attributes.nodes;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.Edge;
import jadx.core.utils.BlockUtils;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.Edge;
import jadx.core.utils.BlockUtils;
public class LoopInfo {
private final BlockNode start;
@@ -43,7 +43,7 @@ public class LoopInfo {
* Exit nodes belongs to loop (contains in {@code loopBlocks})
*/
public Set<BlockNode> getExitNodes() {
Set<BlockNode> nodes = new HashSet<BlockNode>();
Set<BlockNode> nodes = new HashSet<>();
Set<BlockNode> blocks = getLoopBlocks();
for (BlockNode block : blocks) {
// exit: successor node not from this loop, (don't change to getCleanSuccessors)
@@ -60,7 +60,7 @@ public class LoopInfo {
* Return loop exit edges.
*/
public List<Edge> getExitEdges() {
List<Edge> edges = new LinkedList<Edge>();
List<Edge> edges = new LinkedList<>();
Set<BlockNode> blocks = getLoopBlocks();
for (BlockNode block : blocks) {
for (BlockNode s : block.getSuccessors()) {
@@ -1,15 +1,15 @@
package jadx.core.dex.attributes.nodes;
import java.util.LinkedList;
import java.util.List;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.instructions.PhiInsn;
import java.util.LinkedList;
import java.util.List;
public class PhiListAttr implements IAttribute {
private final List<PhiInsn> list = new LinkedList<PhiInsn>();
private final List<PhiInsn> list = new LinkedList<>();
@Override
public AType<PhiListAttr> getType() {
@@ -1,11 +1,12 @@
package jadx.core.dex.info;
import jadx.core.Consts;
import com.android.dx.rop.code.AccessFlags;
import jadx.core.Consts;
public class AccessInfo {
public static final int VISIBILITY_FLAGS = AccessFlags.ACC_PUBLIC | AccessFlags.ACC_PROTECTED | AccessFlags.ACC_PRIVATE;
private final int accFlags;
public enum AFType {
@@ -30,11 +31,24 @@ public class AccessInfo {
return this;
}
public AccessInfo add(int flag) {
if (!containsFlag(flag)) {
return new AccessInfo(accFlags | flag, type);
}
return this;
}
public AccessInfo changeVisibility(int flag) {
int currentVisFlags = accFlags & VISIBILITY_FLAGS;
if (currentVisFlags == flag) {
return this;
}
int unsetAllVisFlags = accFlags & ~VISIBILITY_FLAGS;
return new AccessInfo(unsetAllVisFlags | flag, type);
}
public AccessInfo getVisibility() {
int f = accFlags & AccessFlags.ACC_PUBLIC
| accFlags & AccessFlags.ACC_PROTECTED
| accFlags & AccessFlags.ACC_PRIVATE;
return new AccessInfo(f, type);
return new AccessInfo(accFlags & VISIBILITY_FLAGS, type);
}
public boolean isPublic() {
@@ -140,10 +154,8 @@ public class AccessInfo {
if (isBridge()) {
code.append("/* bridge */ ");
}
if (Consts.DEBUG) {
if (isVarArgs()) {
code.append("/* varargs */ ");
}
if (Consts.DEBUG && isVarArgs()) {
code.append("/* varargs */ ");
}
break;
@@ -1,12 +1,15 @@
package jadx.core.dex.info;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.DexNode;
import jadx.core.utils.exceptions.JadxRuntimeException;
import java.io.File;
public final class ClassInfo {
import org.jetbrains.annotations.NotNull;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.exceptions.JadxRuntimeException;
public final class ClassInfo implements Comparable<ClassInfo> {
private final ArgType type;
private String pkg;
@@ -17,50 +20,50 @@ public final class ClassInfo {
// class info after rename (deobfuscation)
private ClassInfo alias;
private ClassInfo(DexNode dex, ArgType type) {
this(dex, type, true);
private ClassInfo(RootNode root, ArgType type) {
this(root, type, true);
}
private ClassInfo(DexNode dex, ArgType type, boolean inner) {
private ClassInfo(RootNode root, ArgType type, boolean inner) {
if (!type.isObject() || type.isGeneric()) {
throw new JadxRuntimeException("Not class type: " + type);
}
this.type = type;
this.alias = this;
splitNames(dex, inner);
splitNames(root, inner);
}
public static ClassInfo fromType(DexNode dex, ArgType type) {
public static ClassInfo fromType(RootNode root, ArgType type) {
if (type.isArray()) {
type = ArgType.OBJECT;
}
ClassInfo cls = dex.getInfoStorage().getCls(type);
ClassInfo cls = root.getInfoStorage().getCls(type);
if (cls != null) {
return cls;
}
cls = new ClassInfo(dex, type);
return dex.getInfoStorage().putCls(cls);
cls = new ClassInfo(root, type);
return root.getInfoStorage().putCls(cls);
}
public static ClassInfo fromDex(DexNode dex, int clsIndex) {
if (clsIndex == DexNode.NO_INDEX) {
return null;
}
return fromType(dex, dex.getType(clsIndex));
return fromType(dex.root(), dex.getType(clsIndex));
}
public static ClassInfo fromName(DexNode dex, String clsName) {
return fromType(dex, ArgType.object(clsName));
public static ClassInfo fromName(RootNode root, String clsName) {
return fromType(root, ArgType.object(clsName));
}
public static ClassInfo extCls(DexNode dex, ArgType type) {
ClassInfo classInfo = fromName(dex, type.getObject());
public static ClassInfo extCls(RootNode root, ArgType type) {
ClassInfo classInfo = fromName(root, type.getObject());
return classInfo.alias;
}
public void rename(DexNode dex, String fullName) {
ClassInfo newAlias = new ClassInfo(dex, ArgType.object(fullName), isInner());
public void rename(RootNode root, String fullName) {
ClassInfo newAlias = new ClassInfo(root, ArgType.object(fullName), isInner());
if (!alias.getFullName().equals(newAlias.getFullName())) {
this.alias = newAlias;
}
@@ -74,7 +77,7 @@ public final class ClassInfo {
return alias;
}
private void splitNames(DexNode dex, boolean canBeInner) {
private void splitNames(RootNode root, boolean canBeInner) {
String fullObjectName = type.getObject();
String clsName;
int dot = fullObjectName.lastIndexOf('.');
@@ -89,7 +92,11 @@ 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(dex, parClsName);
if (pkg.isEmpty()) {
parClsName = clsName.substring(0, sep);
}
parentClass = fromName(root, parClsName);
clsName = clsName.substring(sep + 1);
} else {
parentClass = null;
@@ -106,11 +113,15 @@ public final class ClassInfo {
return pkg.isEmpty() ? shortName : pkg + "." + shortName;
}
public String makeRawFullName() {
return makeFullClsName(this.name, true);
}
public String getFullPath() {
ClassInfo alias = getAlias();
return alias.getPackage().replace('.', File.separatorChar)
ClassInfo usedAlias = getAlias();
return usedAlias.getPackage().replace('.', File.separatorChar)
+ File.separatorChar
+ alias.getNameWithoutPackage().replace('.', '_');
+ usedAlias.getNameWithoutPackage().replace('.', '_');
}
public String getFullName() {
@@ -156,8 +167,8 @@ public final class ClassInfo {
return parentClass != null;
}
public void notInner(DexNode dex) {
splitNames(dex, false);
public void notInner(RootNode root) {
splitNames(root, false);
}
public ArgType getType() {
@@ -185,4 +196,9 @@ public final class ClassInfo {
}
return false;
}
@Override
public int compareTo(@NotNull ClassInfo o) {
return fullName.compareTo(o.fullName);
}
}
@@ -1,15 +1,5 @@
package jadx.core.dex.info;
import jadx.api.IJadxArgs;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.PrimitiveType;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.ResRefField;
import jadx.core.dex.nodes.parser.FieldInitAttr;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -18,11 +8,21 @@ import java.util.Set;
import org.jetbrains.annotations.Nullable;
import jadx.api.JadxArgs;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.PrimitiveType;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.parser.FieldInitAttr;
import jadx.core.utils.ErrorsCounter;
public class ConstStorage {
private static final class Values {
private final Map<Object, FieldNode> values = new HashMap<Object, FieldNode>();
private final Set<Object> duplicates = new HashSet<Object>();
private static final class ValueStorage {
private final Map<Object, FieldNode> values = new HashMap<>();
private final Set<Object> duplicates = new HashSet<>();
public Map<Object, FieldNode> getValues() {
return values;
@@ -55,12 +55,12 @@ public class ConstStorage {
}
private final boolean replaceEnabled;
private final Values globalValues = new Values();
private final Map<ClassNode, Values> classes = new HashMap<ClassNode, Values>();
private final ValueStorage globalValues = new ValueStorage();
private final Map<ClassNode, ValueStorage> classes = new HashMap<>();
private Map<Integer, String> resourcesNames = new HashMap<Integer, String>();
private Map<Integer, String> resourcesNames = new HashMap<>();
public ConstStorage(IJadxArgs args) {
public ConstStorage(JadxArgs args) {
this.replaceEnabled = args.isReplaceConsts();
}
@@ -90,10 +90,10 @@ public class ConstStorage {
}
}
private Values getClsValues(ClassNode cls) {
Values classValues = classes.get(cls);
private ValueStorage getClsValues(ClassNode cls) {
ValueStorage classValues = classes.get(cls);
if (classValues == null) {
classValues = new Values();
classValues = new ValueStorage();
classes.put(cls, classValues);
}
return classValues;
@@ -101,23 +101,23 @@ public class ConstStorage {
@Nullable
public FieldNode getConstField(ClassNode cls, Object value, boolean searchGlobal) {
DexNode dex = cls.dex();
if (value instanceof Integer) {
String str = resourcesNames.get(value);
if (str != null) {
return new ResRefField(dex, str.replace('/', '.'));
}
}
if (!replaceEnabled) {
return null;
}
DexNode dex = cls.dex();
if (value instanceof Integer) {
FieldNode rField = getResourceField((Integer) value, dex);
if (rField != null) {
return rField;
}
}
boolean foundInGlobal = globalValues.contains(value);
if (foundInGlobal && !searchGlobal) {
return null;
}
ClassNode current = cls;
while (current != null) {
Values classValues = classes.get(current);
ValueStorage classValues = classes.get(current);
if (classValues != null) {
FieldNode field = classValues.get(value);
if (field != null) {
@@ -139,6 +139,31 @@ public class ConstStorage {
return null;
}
@Nullable
private FieldNode getResourceField(Integer value, DexNode dex) {
String str = resourcesNames.get(value);
if (str == null) {
return null;
}
ClassNode appResClass = dex.root().getAppResClass();
if (appResClass == null) {
return null;
}
String[] parts = str.split("/", 2);
if (parts.length != 2) {
return null;
}
String typeName = parts[0];
String fieldName = parts[1];
for (ClassNode innerClass : appResClass.getInnerClasses()) {
if (innerClass.getShortName().equals(typeName)) {
return innerClass.searchFieldByName(fieldName);
}
}
ErrorsCounter.classWarn(appResClass, "Not found resource field with id: " + value + ", name: " + str.replace('/', '.'));
return null;
}
@Nullable
public FieldNode getConstFieldByLiteralArg(ClassNode cls, LiteralArg arg) {
PrimitiveType type = arg.getType().getPrimitiveType();
@@ -161,12 +186,14 @@ public class ConstStorage {
return getConstField(cls, literal, Math.abs(literal) > 1000);
case FLOAT:
float f = Float.intBitsToFloat((int) literal);
return getConstField(cls, f, f != 0.0);
return getConstField(cls, f, Float.compare(f, 0) == 0);
case DOUBLE:
double d = Double.longBitsToDouble(literal);
return getConstField(cls, d, d != 0);
return getConstField(cls, d, Double.compare(d, 0) == 0);
default:
return null;
}
return null;
}
public void setResourcesNames(Map<Integer, String> resourcesNames) {
@@ -1,11 +1,11 @@
package jadx.core.dex.info;
import com.android.dex.FieldId;
import jadx.core.codegen.TypeGen;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.DexNode;
import com.android.dex.FieldId;
public final class FieldInfo {
private final ClassInfo declClass;
@@ -22,7 +22,7 @@ public final class FieldInfo {
public static FieldInfo from(DexNode dex, ClassInfo declClass, String name, ArgType type) {
FieldInfo field = new FieldInfo(declClass, name, type);
return dex.getInfoStorage().getField(field);
return dex.root().getInfoStorage().getField(field);
}
public static FieldInfo fromDex(DexNode dex, int index) {
@@ -57,10 +57,18 @@ public final class FieldInfo {
return declClass.getFullName() + "." + name + ":" + TypeGen.signature(type);
}
public String getRawFullId() {
return declClass.makeRawFullName() + "." + name + ":" + TypeGen.signature(type);
}
public boolean isRenamed() {
return !name.equals(alias);
}
public boolean equalsNameAndType(FieldInfo other) {
return name.equals(other.name) && type.equals(other.type);
}
@Override
public boolean equals(Object o) {
if (this == o) {
@@ -1,15 +1,16 @@
package jadx.core.dex.info;
import jadx.core.dex.instructions.args.ArgType;
import java.util.HashMap;
import java.util.Map;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.DexNode;
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>();
private final Map<ArgType, ClassInfo> classes = new HashMap<>();
private final Map<Integer, MethodInfo> methods = new HashMap<>();
private final Map<FieldInfo, FieldInfo> fields = new HashMap<>();
public ClassInfo getCls(ArgType type) {
return classes.get(type);
@@ -22,13 +23,17 @@ public class InfoStorage {
}
}
public MethodInfo getMethod(int mtdId) {
return methods.get(mtdId);
private int generateMethodLookupId(DexNode dex, int mthId) {
return dex.getDexId() << 16 | mthId;
}
public MethodInfo putMethod(int mthId, MethodInfo mth) {
public MethodInfo getMethod(DexNode dex, int mtdId) {
return methods.get(generateMethodLookupId(dex, mtdId));
}
public MethodInfo putMethod(DexNode dex, int mthId, MethodInfo mth) {
synchronized (methods) {
MethodInfo prev = methods.put(mthId, mth);
MethodInfo prev = methods.put(generateMethodLookupId(dex, mthId), mth);
return prev == null ? mth : prev;
}
}
@@ -1,15 +1,15 @@
package jadx.core.dex.info;
import jadx.core.codegen.TypeGen;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.DexNode;
import jadx.core.utils.Utils;
import java.util.List;
import com.android.dex.MethodId;
import com.android.dex.ProtoId;
import jadx.core.codegen.TypeGen;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.DexNode;
import jadx.core.utils.Utils;
public final class MethodInfo {
private final String name;
@@ -34,12 +34,12 @@ public final class MethodInfo {
}
public static MethodInfo fromDex(DexNode dex, int mthIndex) {
MethodInfo mth = dex.getInfoStorage().getMethod(mthIndex);
MethodInfo mth = dex.root().getInfoStorage().getMethod(dex, mthIndex);
if (mth != null) {
return mth;
}
mth = new MethodInfo(dex, mthIndex);
return dex.getInfoStorage().putMethod(mthIndex, mth);
return dex.root().getInfoStorage().putMethod(dex, mthIndex, mth);
}
public String makeSignature(boolean includeRetType) {
@@ -68,6 +68,10 @@ public final class MethodInfo {
return declClass.getFullName() + "." + shortId;
}
public String getRawFullId() {
return declClass.makeRawFullName() + "." + shortId;
}
/**
* Method name and signature
*/
@@ -146,5 +150,4 @@ public final class MethodInfo {
return declClass.getFullName() + "." + name
+ "(" + Utils.listToString(args) + "):" + retType;
}
}
@@ -1,5 +1,7 @@
package jadx.core.dex.instructions;
import com.android.dx.io.instructions.DecodedInstruction;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
@@ -7,8 +9,6 @@ import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.InsnUtils;
import com.android.dx.io.instructions.DecodedInstruction;
public class ArithNode extends InsnNode {
private final ArithOp op;
@@ -80,5 +80,4 @@ public class ArithNode extends InsnNode {
+ op.getSymbol() + " "
+ getArg(1);
}
}
@@ -0,0 +1,8 @@
package jadx.core.dex.instructions;
import jadx.core.dex.info.MethodInfo;
public interface CallMthInterface {
public MethodInfo getCallMth();
}
@@ -1,5 +1,10 @@
package jadx.core.dex.instructions;
import java.util.ArrayList;
import java.util.List;
import com.android.dx.io.instructions.FillArrayDataPayloadDecodedInstruction;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.LiteralArg;
@@ -8,11 +13,6 @@ 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;
@@ -66,7 +66,7 @@ public final class FillArrayNode extends InsnNode {
}
public List<LiteralArg> getLiteralArgs() {
List<LiteralArg> list = new ArrayList<LiteralArg>(size);
List<LiteralArg> list = new ArrayList<>(size);
Object array = data;
if (array instanceof int[]) {
for (int b : (int[]) array) {
@@ -1,10 +1,10 @@
package jadx.core.dex.instructions;
import org.jetbrains.annotations.NotNull;
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;
@@ -1,11 +1,10 @@
package jadx.core.dex.instructions;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.InsnUtils;
public class GotoNode extends InsnNode {
public class GotoNode extends TargetInsnNode {
protected int target;
protected final int target;
public GotoNode(int target) {
this(InsnType.GOTO, target, 0);
@@ -1,5 +1,9 @@
package jadx.core.dex.instructions;
import java.util.List;
import com.android.dx.io.instructions.DecodedInstruction;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.PrimitiveType;
@@ -7,8 +11,6 @@ import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.InsnUtils;
import com.android.dx.io.instructions.DecodedInstruction;
import static jadx.core.utils.BlockUtils.getBlockByOffset;
import static jadx.core.utils.BlockUtils.selectOther;
@@ -47,7 +49,6 @@ public class IfNode extends GotoNode {
BlockNode tmp = thenBlock;
thenBlock = elseBlock;
elseBlock = tmp;
target = thenBlock.getStartOffset();
}
public void changeCondition(IfOp op, InsnArg arg1, InsnArg arg2) {
@@ -56,15 +57,31 @@ public class IfNode extends GotoNode {
setArg(1, arg2);
}
@Override
public void initBlocks(BlockNode curBlock) {
thenBlock = getBlockByOffset(target, curBlock.getSuccessors());
if (curBlock.getSuccessors().size() == 1) {
List<BlockNode> successors = curBlock.getSuccessors();
thenBlock = getBlockByOffset(target, successors);
if (successors.size() == 1) {
elseBlock = thenBlock;
} else {
elseBlock = selectOther(thenBlock, curBlock.getSuccessors());
elseBlock = selectOther(thenBlock, successors);
}
}
@Override
public boolean replaceTargetBlock(BlockNode origin, BlockNode replace) {
boolean replaced = false;
if (thenBlock == origin) {
thenBlock = replace;
replaced = true;
}
if (elseBlock == origin) {
elseBlock = replace;
replaced = true;
}
return replaced;
}
public BlockNode getThenBlock() {
return thenBlock;
}
@@ -73,6 +90,11 @@ public class IfNode extends GotoNode {
return elseBlock;
}
@Override
public int getTarget() {
return thenBlock == null ? target : thenBlock.getStartOffset();
}
@Override
public boolean isSame(InsnNode obj) {
if (this == obj) {
@@ -1,5 +1,19 @@
package jadx.core.dex.instructions;
import java.io.EOFException;
import com.android.dex.Code;
import com.android.dx.io.OpcodeInfo;
import com.android.dx.io.Opcodes;
import com.android.dx.io.instructions.DecodedInstruction;
import com.android.dx.io.instructions.FillArrayDataPayloadDecodedInstruction;
import com.android.dx.io.instructions.PackedSwitchPayloadDecodedInstruction;
import com.android.dx.io.instructions.ShortArrayCodeInput;
import com.android.dx.io.instructions.SparseSwitchPayloadDecodedInstruction;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType;
@@ -12,16 +26,8 @@ import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.exceptions.DecodeException;
import com.android.dex.Code;
import com.android.dx.io.OpcodeInfo;
import com.android.dx.io.Opcodes;
import com.android.dx.io.instructions.DecodedInstruction;
import com.android.dx.io.instructions.FillArrayDataPayloadDecodedInstruction;
import com.android.dx.io.instructions.PackedSwitchPayloadDecodedInstruction;
import com.android.dx.io.instructions.ShortArrayCodeInput;
import com.android.dx.io.instructions.SparseSwitchPayloadDecodedInstruction;
public class InsnDecoder {
private static final Logger LOG = LoggerFactory.getLogger(InsnDecoder.class);
private final MethodNode method;
private final DexNode dex;
@@ -37,26 +43,36 @@ public class InsnDecoder {
int size = encodedInstructions.length;
DecodedInstruction[] decoded = new DecodedInstruction[size];
ShortArrayCodeInput in = new ShortArrayCodeInput(encodedInstructions);
try {
while (in.hasMore()) {
decoded[in.cursor()] = DecodedInstruction.decode(in);
decoded[in.cursor()] = decodeRawInsn(in);
}
} catch (Exception e) {
throw new DecodeException(method, "", e);
throw new DecodeException(method, e.getMessage(), e);
}
insnArr = decoded;
}
private DecodedInstruction decodeRawInsn(ShortArrayCodeInput in) throws EOFException {
int opcodeUnit = in.read();
int opcode = Opcodes.extractOpcodeFromUnit(opcodeUnit);
OpcodeInfo.Info opcodeInfo;
try {
opcodeInfo = OpcodeInfo.get(opcode);
} catch (IllegalArgumentException e) {
LOG.warn("Ignore decode error: '{}', replace with NOP instruction", e.getMessage());
opcodeInfo = OpcodeInfo.NOP;
}
return opcodeInfo.getFormat().decode(opcodeUnit, in);
}
public InsnNode[] process() throws DecodeException {
InsnNode[] instructions = new InsnNode[insnArr.length];
for (int i = 0; i < insnArr.length; i++) {
DecodedInstruction rawInsn = insnArr[i];
if (rawInsn != null) {
InsnNode insn = decode(rawInsn, i);
if (insn != null) {
insn.setOffset(i);
}
insn.setOffset(i);
instructions[i] = insn;
} else {
instructions[i] = null;
@@ -66,13 +82,14 @@ public class InsnDecoder {
return instructions;
}
@NotNull
private InsnNode decode(DecodedInstruction insn, int offset) throws DecodeException {
switch (insn.getOpcode()) {
case Opcodes.NOP:
case Opcodes.PACKED_SWITCH_PAYLOAD:
case Opcodes.SPARSE_SWITCH_PAYLOAD:
case Opcodes.FILL_ARRAY_DATA_PAYLOAD:
return null;
return new InsnNode(InsnType.NOP, 0);
// move-result will be process in invoke and filled-new-array instructions
case Opcodes.MOVE_RESULT:
@@ -95,17 +112,15 @@ public class InsnDecoder {
InsnArg.lit(insn, ArgType.WIDE));
case Opcodes.CONST_STRING:
case Opcodes.CONST_STRING_JUMBO: {
InsnNode node = new ConstStringNode(dex.getString(insn.getIndex()));
node.setResult(InsnArg.reg(insn, 0, ArgType.STRING));
return node;
}
case Opcodes.CONST_STRING_JUMBO:
InsnNode constStrInsn = new ConstStringNode(dex.getString(insn.getIndex()));
constStrInsn.setResult(InsnArg.reg(insn, 0, ArgType.STRING));
return constStrInsn;
case Opcodes.CONST_CLASS: {
InsnNode node = new ConstClassNode(dex.getType(insn.getIndex()));
node.setResult(InsnArg.reg(insn, 0, ArgType.CLASS));
return node;
}
case Opcodes.CONST_CLASS:
InsnNode constClsInsn = new ConstClassNode(dex.getType(insn.getIndex()));
constClsInsn.setResult(InsnArg.reg(insn, 0, ArgType.CLASS));
return constClsInsn;
case Opcodes.MOVE:
case Opcodes.MOVE_16:
@@ -307,6 +322,11 @@ public class InsnDecoder {
case Opcodes.NEG_DOUBLE:
return neg(insn, ArgType.DOUBLE);
case Opcodes.NOT_INT:
return not(insn, ArgType.INT);
case Opcodes.NOT_LONG:
return not(insn, ArgType.LONG);
case Opcodes.INT_TO_BYTE:
return cast(insn, ArgType.INT, ArgType.BYTE);
case Opcodes.INT_TO_CHAR:
@@ -400,20 +420,18 @@ public class InsnDecoder {
null,
InsnArg.reg(insn, 0, method.getReturnType()));
case Opcodes.INSTANCE_OF: {
InsnNode node = new IndexInsnNode(InsnType.INSTANCE_OF, dex.getType(insn.getIndex()), 1);
node.setResult(InsnArg.reg(insn, 0, ArgType.BOOLEAN));
node.addArg(InsnArg.reg(insn, 1, ArgType.UNKNOWN_OBJECT));
return node;
}
case Opcodes.INSTANCE_OF:
InsnNode instInsn = new IndexInsnNode(InsnType.INSTANCE_OF, dex.getType(insn.getIndex()), 1);
instInsn.setResult(InsnArg.reg(insn, 0, ArgType.BOOLEAN));
instInsn.addArg(InsnArg.reg(insn, 1, ArgType.UNKNOWN_OBJECT));
return instInsn;
case Opcodes.CHECK_CAST: {
case Opcodes.CHECK_CAST:
ArgType castType = dex.getType(insn.getIndex());
InsnNode node = new IndexInsnNode(InsnType.CHECK_CAST, castType, 1);
node.setResult(InsnArg.reg(insn, 0, castType));
node.addArg(InsnArg.reg(insn, 0, ArgType.UNKNOWN_OBJECT));
return node;
}
InsnNode checkCastInsn = new IndexInsnNode(InsnType.CHECK_CAST, castType, 1);
checkCastInsn.setResult(InsnArg.reg(insn, 0, castType));
checkCastInsn.addArg(InsnArg.reg(insn, 0, ArgType.UNKNOWN_OBJECT));
return checkCastInsn;
case Opcodes.IGET:
case Opcodes.IGET_BOOLEAN:
@@ -421,13 +439,12 @@ public class InsnDecoder {
case Opcodes.IGET_CHAR:
case Opcodes.IGET_SHORT:
case Opcodes.IGET_WIDE:
case Opcodes.IGET_OBJECT: {
FieldInfo field = FieldInfo.fromDex(dex, insn.getIndex());
InsnNode node = new IndexInsnNode(InsnType.IGET, field, 1);
node.setResult(InsnArg.reg(insn, 0, field.getType()));
node.addArg(InsnArg.reg(insn, 1, field.getDeclClass().getType()));
return node;
}
case Opcodes.IGET_OBJECT:
FieldInfo igetFld = FieldInfo.fromDex(dex, insn.getIndex());
InsnNode igetInsn = new IndexInsnNode(InsnType.IGET, igetFld, 1);
igetInsn.setResult(InsnArg.reg(insn, 0, igetFld.getType()));
igetInsn.addArg(InsnArg.reg(insn, 1, igetFld.getDeclClass().getType()));
return igetInsn;
case Opcodes.IPUT:
case Opcodes.IPUT_BOOLEAN:
@@ -435,13 +452,12 @@ public class InsnDecoder {
case Opcodes.IPUT_CHAR:
case Opcodes.IPUT_SHORT:
case Opcodes.IPUT_WIDE:
case Opcodes.IPUT_OBJECT: {
FieldInfo field = FieldInfo.fromDex(dex, insn.getIndex());
InsnNode node = new IndexInsnNode(InsnType.IPUT, field, 2);
node.addArg(InsnArg.reg(insn, 0, field.getType()));
node.addArg(InsnArg.reg(insn, 1, field.getDeclClass().getType()));
return node;
}
case Opcodes.IPUT_OBJECT:
FieldInfo iputFld = FieldInfo.fromDex(dex, insn.getIndex());
InsnNode iputInsn = new IndexInsnNode(InsnType.IPUT, iputFld, 2);
iputInsn.addArg(InsnArg.reg(insn, 0, iputFld.getType()));
iputInsn.addArg(InsnArg.reg(insn, 1, iputFld.getDeclClass().getType()));
return iputInsn;
case Opcodes.SGET:
case Opcodes.SGET_BOOLEAN:
@@ -449,12 +465,11 @@ public class InsnDecoder {
case Opcodes.SGET_CHAR:
case Opcodes.SGET_SHORT:
case Opcodes.SGET_WIDE:
case Opcodes.SGET_OBJECT: {
FieldInfo field = FieldInfo.fromDex(dex, insn.getIndex());
InsnNode node = new IndexInsnNode(InsnType.SGET, field, 0);
node.setResult(InsnArg.reg(insn, 0, field.getType()));
return node;
}
case Opcodes.SGET_OBJECT:
FieldInfo sgetFld = FieldInfo.fromDex(dex, insn.getIndex());
InsnNode sgetInsn = new IndexInsnNode(InsnType.SGET, sgetFld, 0);
sgetInsn.setResult(InsnArg.reg(insn, 0, sgetFld.getType()));
return sgetInsn;
case Opcodes.SPUT:
case Opcodes.SPUT_BOOLEAN:
@@ -462,19 +477,17 @@ public class InsnDecoder {
case Opcodes.SPUT_CHAR:
case Opcodes.SPUT_SHORT:
case Opcodes.SPUT_WIDE:
case Opcodes.SPUT_OBJECT: {
FieldInfo field = FieldInfo.fromDex(dex, insn.getIndex());
InsnNode node = new IndexInsnNode(InsnType.SPUT, field, 1);
node.addArg(InsnArg.reg(insn, 0, field.getType()));
return node;
}
case Opcodes.SPUT_OBJECT:
FieldInfo sputFld = FieldInfo.fromDex(dex, insn.getIndex());
InsnNode sputInsn = new IndexInsnNode(InsnType.SPUT, sputFld, 1);
sputInsn.addArg(InsnArg.reg(insn, 0, sputFld.getType()));
return sputInsn;
case Opcodes.ARRAY_LENGTH: {
InsnNode node = new InsnNode(InsnType.ARRAY_LENGTH, 1);
node.setResult(InsnArg.reg(insn, 0, ArgType.INT));
node.addArg(InsnArg.reg(insn, 1, ArgType.array(ArgType.UNKNOWN)));
return node;
}
case Opcodes.ARRAY_LENGTH:
InsnNode arrLenInsn = new InsnNode(InsnType.ARRAY_LENGTH, 1);
arrLenInsn.setResult(InsnArg.reg(insn, 0, ArgType.INT));
arrLenInsn.addArg(InsnArg.reg(insn, 1, ArgType.array(ArgType.UNKNOWN)));
return arrLenInsn;
case Opcodes.AGET:
return arrayGet(insn, ArgType.NARROW);
@@ -563,9 +576,10 @@ public class InsnDecoder {
return insn(InsnType.MONITOR_EXIT,
null,
InsnArg.reg(insn, 0, ArgType.UNKNOWN_OBJECT));
}
throw new DecodeException("Unknown instruction: " + OpcodeInfo.getName(insn.getOpcode()));
default:
throw new DecodeException("Unknown instruction: '" + OpcodeInfo.getName(insn.getOpcode()) + "'");
}
}
private InsnNode decodeSwitch(DecodedInstruction insn, int offset, boolean packed) {
@@ -681,6 +695,13 @@ public class InsnDecoder {
return inode;
}
private InsnNode not(DecodedInstruction insn, ArgType type) {
InsnNode inode = new InsnNode(InsnType.NOT, 1);
inode.setResult(InsnArg.reg(insn, 0, type));
inode.addArg(InsnArg.reg(insn, 1, type));
return inode;
}
private InsnNode insn(InsnType type, RegisterArg res) {
InsnNode node = new InsnNode(type, 0);
node.setResult(res);
@@ -8,6 +8,7 @@ public enum InsnType {
ARITH,
NEG,
NOT,
MOVE,
CAST,
@@ -1,5 +1,7 @@
package jadx.core.dex.instructions;
import com.android.dx.io.instructions.DecodedInstruction;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
@@ -7,9 +9,7 @@ import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.Utils;
import com.android.dx.io.instructions.DecodedInstruction;
public class InvokeNode extends InsnNode {
public class InvokeNode extends InsnNode implements CallMthInterface {
private final InvokeType type;
private final MethodInfo mth;
@@ -1,12 +1,12 @@
package jadx.core.dex.instructions;
import org.jetbrains.annotations.NotNull;
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;
@@ -1,5 +1,10 @@
package jadx.core.dex.instructions;
import java.util.LinkedHashMap;
import java.util.Map;
import org.jetbrains.annotations.NotNull;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
@@ -10,18 +15,13 @@ import jadx.core.utils.InstructionRemover;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
import java.util.IdentityHashMap;
import java.util.Map;
import org.jetbrains.annotations.NotNull;
public final class PhiInsn extends InsnNode {
private final Map<RegisterArg, BlockNode> blockBinds;
public PhiInsn(int regNum, int predecessors) {
super(InsnType.PHI, predecessors);
this.blockBinds = new IdentityHashMap<RegisterArg, BlockNode>(predecessors);
this.blockBinds = new LinkedHashMap<>(predecessors);
setResult(InsnArg.reg(regNum, ArgType.UNKNOWN));
add(AFlag.DONT_INLINE);
}
@@ -1,17 +1,24 @@
package jadx.core.dex.instructions;
import java.util.Arrays;
import java.util.List;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.InsnUtils;
import java.util.Arrays;
import static jadx.core.utils.BlockUtils.getBlockByOffset;
public class SwitchNode extends InsnNode {
public class SwitchNode extends TargetInsnNode {
private final Object[] keys;
private final int[] targets;
private final int def; // next instruction
private BlockNode[] targetBlocks;
private BlockNode defTargetBlock;
public SwitchNode(InsnArg arg, Object[] keys, int[] targets, int def) {
super(InsnType.SWITCH, 1);
this.keys = keys;
@@ -36,6 +43,42 @@ public class SwitchNode extends InsnNode {
return def;
}
public BlockNode[] getTargetBlocks() {
return targetBlocks;
}
public BlockNode getDefTargetBlock() {
return defTargetBlock;
}
@Override
public void initBlocks(BlockNode curBlock) {
List<BlockNode> successors = curBlock.getSuccessors();
int len = targets.length;
targetBlocks = new BlockNode[len];
for (int i = 0; i < len; i++) {
targetBlocks[i] = getBlockByOffset(targets[i], successors);
}
defTargetBlock = getBlockByOffset(def, successors);
}
@Override
public boolean replaceTargetBlock(BlockNode origin, BlockNode replace) {
int count = 0;
int len = targetBlocks.length;
for (int i = 0; i < len; i++) {
if (targetBlocks[i] == origin) {
targetBlocks[i] = replace;
count++;
}
}
if (defTargetBlock == origin) {
defTargetBlock = replace;
count++;
}
return count > 0;
}
@Override
public boolean isSame(InsnNode obj) {
if (this == obj) {
@@ -0,0 +1,18 @@
package jadx.core.dex.instructions;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.InsnNode;
public abstract class TargetInsnNode extends InsnNode {
public TargetInsnNode(InsnType type, int argsCount) {
super(type, argsCount);
}
public void initBlocks(BlockNode curBlock) {
}
public boolean replaceTargetBlock(BlockNode origin, BlockNode replace) {
return false;
}
}
@@ -1,16 +1,16 @@
package jadx.core.dex.instructions.args;
import jadx.core.Consts;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.parser.SignatureParser;
import jadx.core.utils.Utils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.jetbrains.annotations.Nullable;
import jadx.core.Consts;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.parser.SignatureParser;
import jadx.core.utils.Utils;
public abstract class ArgType {
public static final ArgType INT = primitive(PrimitiveType.INT);
@@ -140,16 +140,16 @@ public abstract class ArgType {
}
private static class ObjectType extends KnownType {
private final String object;
private final String objName;
public ObjectType(String obj) {
this.object = Utils.cleanObjectName(obj);
this.hash = object.hashCode();
this.objName = Utils.cleanObjectName(obj);
this.hash = objName.hashCode();
}
@Override
public String getObject() {
return object;
return objName;
}
@Override
@@ -164,12 +164,12 @@ public abstract class ArgType {
@Override
boolean internalEquals(Object obj) {
return object.equals(((ObjectType) obj).object);
return objName.equals(((ObjectType) obj).objName);
}
@Override
public String toString() {
return object;
return objName;
}
}
@@ -280,7 +280,7 @@ public abstract class ArgType {
}
private static final class ArrayArg extends KnownType {
public static final PrimitiveType[] ARRAY_POSSIBLES = new PrimitiveType[]{PrimitiveType.ARRAY};
private static final PrimitiveType[] ARRAY_POSSIBLES = new PrimitiveType[]{PrimitiveType.ARRAY};
private final ArgType arrayElement;
public ArrayArg(ArgType arrayElement) {
@@ -495,7 +495,7 @@ public abstract class ArgType {
return null;
} else {
// both types unknown
List<PrimitiveType> types = new ArrayList<PrimitiveType>();
List<PrimitiveType> types = new ArrayList<>();
for (PrimitiveType type : a.getPossibleTypes()) {
if (b.contains(type)) {
types.add(type);
@@ -622,8 +622,10 @@ public abstract class ArgType {
return DOUBLE;
case 'V':
return VOID;
default:
return null;
}
return null;
}
public int getRegCount() {
@@ -1,9 +1,9 @@
package jadx.core.dex.instructions.args;
import jadx.core.dex.info.FieldInfo;
import org.jetbrains.annotations.Nullable;
import jadx.core.dex.info.FieldInfo;
// TODO: don't extend RegisterArg (now used as a result of instruction)
public final class FieldArg extends RegisterArg {
@@ -57,10 +57,7 @@ public final class FieldArg extends RegisterArg {
if (!field.equals(fieldArg.field)) {
return false;
}
if (instArg != null ? !instArg.equals(fieldArg.instArg) : fieldArg.instArg != null) {
return false;
}
return true;
return instArg != null ? instArg.equals(fieldArg.instArg) : fieldArg.instArg == null;
}
@Override
@@ -1,17 +1,16 @@
package jadx.core.dex.instructions.args;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.InsnUtils;
import java.util.ArrayList;
import java.util.List;
import com.android.dx.io.instructions.DecodedInstruction;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.android.dx.io.instructions.DecodedInstruction;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.InsnUtils;
/**
* Instruction argument,
@@ -101,7 +100,7 @@ public abstract class InsnArg extends Typed {
}
public static void updateParentInsn(InsnNode fromInsn, InsnNode toInsn) {
List<RegisterArg> args = new ArrayList<RegisterArg>();
List<RegisterArg> args = new ArrayList<>();
fromInsn.getRegisterArgs(args);
for (RegisterArg reg : args) {
reg.setParentInsn(toInsn);
@@ -141,7 +140,6 @@ public abstract class InsnArg extends Typed {
}
public boolean isThis() {
// must be implemented in RegisterArg and MthParameterArg
return false;
return contains(AFlag.THIS);
}
}
@@ -1,10 +1,10 @@
package jadx.core.dex.instructions.args;
import org.jetbrains.annotations.NotNull;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.exceptions.JadxRuntimeException;
import org.jetbrains.annotations.NotNull;
public final class InsnWrapArg extends InsnArg {
private final InsnNode wrappedInsn;
@@ -35,7 +35,6 @@ public final class NamedArg extends InsnArg implements Named {
return false;
}
return name.equals(((NamedArg) o).name);
}
@Override
@@ -1,17 +1,18 @@
package jadx.core.dex.instructions.args;
import java.util.Objects;
import org.jetbrains.annotations.NotNull;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.PhiInsn;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.InsnUtils;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RegisterArg extends InsnArg implements Named {
private static final Logger LOG = LoggerFactory.getLogger(RegisterArg.class);
public static final String THIS_ARG_NAME = "this";
protected final int regNum;
// not null after SSATransform pass
@@ -44,6 +45,9 @@ public class RegisterArg extends InsnArg implements Named {
}
public String getName() {
if (isThis()) {
return THIS_ARG_NAME;
}
if (sVar == null) {
return null;
}
@@ -51,7 +55,7 @@ public class RegisterArg extends InsnArg implements Named {
}
public void setName(String name) {
if (sVar != null) {
if (sVar != null && name != null) {
sVar.setName(name);
}
}
@@ -64,6 +68,21 @@ public class RegisterArg extends InsnArg implements Named {
return n.equals(((Named) arg).getName());
}
public void mergeName(InsnArg arg) {
if (arg instanceof Named) {
Named otherArg = (Named) arg;
String otherName = otherArg.getName();
String name = getName();
if (!Objects.equals(name, otherName)) {
if (name == null) {
setName(otherName);
} else if (otherName == null) {
otherArg.setName(name);
}
}
}
}
@Override
public void setType(ArgType type) {
if (sVar != null) {
@@ -102,22 +121,6 @@ public class RegisterArg extends InsnArg implements Named {
return InsnUtils.getConstValueByInsn(dex, parInsn);
}
@Override
public boolean isThis() {
if ("this".equals(getName())) {
return true;
}
// maybe it was moved from 'this' register
InsnNode ai = getAssignInsn();
if (ai != null && ai.getType() == InsnType.MOVE) {
InsnArg arg = ai.getArg(0);
if (arg != this) {
return arg.isThis();
}
}
return false;
}
public InsnNode getAssignInsn() {
if (sVar == null) {
return null;
@@ -143,7 +146,7 @@ public class RegisterArg extends InsnArg implements Named {
@Override
public int hashCode() {
return regNum * 31 + type.hashCode();
return regNum;
}
@Override
@@ -151,23 +154,13 @@ public class RegisterArg extends InsnArg implements Named {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof RegisterArg)) {
return false;
}
RegisterArg other = (RegisterArg) obj;
if (regNum != other.regNum) {
return false;
}
if (!type.equals(other.type)) {
return false;
}
if (sVar != null && !sVar.equals(other.getSVar())) {
return false;
}
return true;
return regNum == other.regNum
&& type.equals(other.type)
&& Objects.equals(sVar, other.getSVar());
}
@Override
@@ -183,6 +176,9 @@ public class RegisterArg extends InsnArg implements Named {
}
sb.append(" ");
sb.append(type);
if (!isAttrStorageEmpty()) {
sb.append(' ').append(getAttributesString());
}
sb.append(")");
return sb.toString();
}
@@ -1,14 +1,14 @@
package jadx.core.dex.instructions.args;
import jadx.core.dex.attributes.AttrNode;
import jadx.core.dex.instructions.PhiInsn;
import java.util.ArrayList;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import jadx.core.dex.attributes.AttrNode;
import jadx.core.dex.instructions.PhiInsn;
public class SSAVar extends AttrNode {
private final int regNum;
@@ -20,7 +20,7 @@ public class SSAVar extends AttrNode {
@NotNull
private RegisterArg assign;
private final List<RegisterArg> useList = new ArrayList<RegisterArg>(2);
private final List<RegisterArg> useList = new ArrayList<>(2);
@Nullable
private PhiInsn usedInPhi;
@@ -4,8 +4,6 @@ import org.jetbrains.annotations.NotNull;
public class TypeImmutableArg extends RegisterArg {
private boolean isThis;
public TypeImmutableArg(int rn, ArgType type) {
super(rn, type);
}
@@ -20,48 +18,9 @@ public class TypeImmutableArg extends RegisterArg {
// not allowed
}
public void markAsThis() {
this.isThis = true;
}
@Override
public boolean isThis() {
return isThis;
}
@Override
public String getName() {
if (isThis) {
return "this";
}
return super.getName();
}
@Override
void setSVar(@NotNull SSAVar sVar) {
if (isThis) {
sVar.setName("this");
}
sVar.setTypeImmutable(type);
super.setSVar(sVar);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof TypeImmutableArg)) {
return false;
}
if (!super.equals(obj)) {
return false;
}
return isThis == ((TypeImmutableArg) obj).isThis;
}
@Override
public int hashCode() {
return 31 * super.hashCode() + (isThis ? 1 : 0);
}
}

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