Compare commits

...

330 Commits

Author SHA1 Message Date
Skylot 913a5b5d0f v0.6.1 2016-12-05 11:06:17 +03:00
Skylot c594137c19 build: remove sonar plugin from gradle config (fix #140) 2016-12-05 10:48:56 +03:00
Skylot b2f41e95bf core: export as android gradle project 2016-03-27 15:28:06 +03:00
Skylot e733c91783 gui: support images view/unpack 2016-03-26 17:19:54 +03:00
Skylot 4e982722a5 core: fix incorrect package for R class (#99) 2016-03-19 22:55:57 +03:00
Skylot 2b1f815c58 core: refactor streams closing 2016-03-19 19:14:24 +03:00
Skylot 0fff1a6754 core: fix warning from dx library 2016-03-19 18:21:52 +03:00
Skylot d95d268ec5 core: test enum implementing interface 2016-03-19 16:21:32 +03:00
Skylot b4930bc40c gui: fix issues in search dialog 2016-03-19 16:19:08 +03:00
Skylot 5f302238ad core: allow to disable constant dereference (#106) 2016-03-13 12:43:24 +03:00
Skylot 7cba2c3f81 gui: remove suffix tree search cache 2016-03-08 15:00:19 +03:00
Skylot 218c39b1ec core: option for control escaping of unicode characters (#103) 2016-03-07 19:25:57 +03:00
Skylot e915f4fcd7 core: show missing class references only once 2016-01-31 15:20:07 +03:00
Skylot bc9164b952 core: refactor file loading, add 'aar' support (fix #95) 2015-12-26 19:16:05 +03:00
Skylot 7c34be267f res: fix escape for apostrophes and quotes in string resources 2015-11-15 16:20:57 +03:00
skylot 042464438c Merge pull request #100 from netmaxt3r/master
multidex support for apk & zip
2015-11-15 16:11:32 +03:00
Nizam Moidu cf68e4722a multidex support for apk & zip 2015-11-11 12:22:47 +04:00
Skylot 7be37ff76e update gradle to 2.7 2015-10-10 14:32:10 +03:00
Skylot 1118236075 test: added module for check recompilation of test app 2015-10-10 14:26:23 +03:00
Skylot ef8a685621 resources: initial version of .arsc file decode 2015-10-09 21:41:38 +03:00
Skylot e4fef402c9 resources: don't check type chunk header size (fix #89) 2015-09-25 22:58:54 +03:00
Skylot 5528afa404 core: fix type inference for filled array (#87) 2015-09-23 22:34:32 +03:00
Skylot e3189fae37 gui: add deobfuscation button to menu 2015-09-23 22:31:38 +03:00
Skylot 6d963b378c gui: fix results render issues is search dialog 2015-09-23 21:57:32 +03:00
Skylot 895ddfa38f gui: cache renderer results in find/usage dialogs 2015-09-19 20:11:04 +03:00
Skylot 28e334a0ba gui: fix code cell renderer in find/usage dialogs 2015-09-19 20:10:43 +03:00
Skylot d060f5b877 gui: scroll to node when sync with editor 2015-09-19 20:10:09 +03:00
Skylot 7b70d617e0 core: fix variables inline (#86) 2015-09-19 16:31:08 +03:00
Skylot 261ba4645d resources: support text chuck in binary xml (fix #84) 2015-09-16 21:23:55 +03:00
Skylot 2ab7524e71 core: better args class 2015-09-08 21:29:41 +03:00
Skylot d55969bc65 core: fix some 'try/catch/finally' cases 2015-09-05 20:55:37 +03:00
Skylot 9976894091 core: skip decoding for plain text xml (fix #82) 2015-08-29 15:50:42 +03:00
skylot 76a0608a04 Merge pull request #83 from vbauer/fix-warnings
Fix console warnings during compilation (gradle build)
2015-08-29 13:28:56 +03:00
Vladislav Bauer 0d93d335a1 Fix console warnings during compilation (gradle build) 2015-08-28 20:15:51 +06:00
skylot ffb9788047 Merge pull request #81 from NeoSpb/fix_deobf
fix deobfuscation
2015-08-15 20:20:03 +03:00
NeoSpb 5dd82eede9 core: fix deobfuscation when class is in the root package (package path is empty) 2015-08-14 16:15:10 +03:00
Skylot 14b90466ef gui: restore last window position and size 2015-08-10 21:54:20 +03:00
Skylot 43592c3e49 gui: improve memory usage (#79)
- don't use suffix tree in search
- decrease default working threads count (only 1 for background jobs)
- use string refs for store only one code string without duplicates
- use cache for creating UI nodes
- allow to disable autostart for background jobs (decompilation and index)
2015-08-09 12:29:33 +03:00
Skylot b46093b3cc core: add method info cache 2015-08-09 12:12:17 +03:00
Skylot 2b9c092705 core: fix field initialization extract from try/catch block (fix #78) 2015-08-01 21:57:30 +03:00
Skylot bc73010d4e gui: add find usage feature, run decompilation and index jobs in background (#74, #75) 2015-07-26 18:06:26 +03:00
Skylot 2d8d416483 core: add cache for JavaNodes, fix definition annotations 2015-07-26 17:19:08 +03:00
skylot f549a0691e Merge pull request #76 from jpstotz/master
Enable file drop operation for loading it.
2015-07-22 16:36:07 +03:00
Jan Peter Stotz 96c2fb6f54 Enable file drop operation for loading it. 2015-07-22 14:57:28 +02:00
Skylot f6d475292c gui: add key shortcuts for menu actions. 2015-07-14 19:38:14 +03:00
Skylot bd4d4f49ff gui: add full text search (#74) 2015-07-13 22:26:26 +03:00
Skylot 5a24eac375 core: fix exit node search for synchronized block (fix #72) 2015-07-04 15:20:15 +03:00
Skylot a684118dbb core: move field initialization from constructors if possible (#71) 2015-07-01 23:01:54 +03:00
Skylot a324376e60 core: replace assertions with jadx exceptions throw 2015-06-27 21:15:57 +03:00
Skylot 04e50afaba core: fix synthetic method inline (fix #71) 2015-06-27 18:27:43 +03:00
Skylot 69494c9212 core: add method for copy instruction nodes 2015-06-27 18:27:38 +03:00
Skylot b2f0f02541 core: fix incorrectly removed 'return' in 'switch' block (fix #70) 2015-06-26 21:30:51 +03:00
Skylot 71f249113d core: allow to skip sub-blocks for region visitor. 2015-06-26 21:26:08 +03:00
Skylot 1d84c00161 core: fix 'break' in complex 'if' in loop (fix #67) 2015-06-14 15:57:37 +03:00
Skylot 5bc7e19a28 core: don't show rename comment if class name not changed 2015-06-04 20:50:25 +03:00
Skylot c46703a05d gui: run jadx-gui without console 2015-05-31 17:14:55 +03:00
Skylot 129a7c39af gui: add log viewer 2015-05-31 17:11:46 +03:00
Skylot ac3f3e8385 gui: add common popup actions for text fields. 2015-05-31 16:14:34 +03:00
skylot bc8ad4df86 Merge pull request #64 from NeoSpb/fix_deobfuscator
Fix deobfuscator
2015-05-25 20:11:00 +03:00
NeoSpb 53ac3ec582 core: fix deobfuscation for overridden methods (make identical name ('mo{index}')
for overridden methods, older 'jobf' file must be removed)
2015-05-18 21:03:53 +03:00
NeoSpb d2d43711c2 Make optional using source file name as alias for class name (some obfuscator
set the source file property with wrong value and break deobfuscation)
default: disabled
2015-05-18 21:03:51 +03:00
NeoSpb 510035b7b7 core: fix used name/path to the deobfuscation map file
(used the same name/path as the APK file, but extension 'jobf')
2015-05-18 21:03:50 +03:00
skylot c923d19bcc Merge pull request #63 from jpstotz/master
Make jadx-gui.jar runnable
2015-05-16 13:07:18 +03:00
Jan Peter Stotz bff9597360 Add Main-Class and Class-Path attributes to MANIFEST.MF of jadx-gui jar file. 2015-05-12 10:52:43 +02:00
Skylot 78b39a60e8 core: fixed invoke arguments list (fix #61) 2015-05-11 20:33:16 +03:00
Skylot 932966b6b8 core: skip synthetic arguments in anonymous class constructor 2015-05-02 20:53:22 +03:00
Skylot 85a18e6d75 core: don't insert break in method exit blocks (fix #60) 2015-05-02 20:29:15 +03:00
Skylot 5d86bf9788 core: fix loop processing after exception handler remove (fix #59) 2015-05-02 17:51:15 +03:00
Skylot 406d9878d8 core: fix invoke args skipping 2015-04-26 15:03:23 +03:00
Skylot 4e6c5cb27a core: inline anonymous classes with arguments 2015-04-25 21:40:03 +03:00
Skylot a9c0185bf5 core: fix type resolver in 'if' 2015-04-18 19:12:06 +03:00
Skylot 0111172a03 travis: tune cache options 2015-04-10 22:26:57 +03:00
Skylot 57541488d3 version 0.6.1 bump 2015-04-10 22:25:18 +03:00
Skylot 3782aa7d0a core: fix wildcard type in iterable loop 2015-04-07 23:12:39 +03:00
Skylot d5740c1b08 core: fix 'finally' extract in 'if' 2015-04-07 23:12:39 +03:00
Skylot 3357979cc9 core: remove unused method 2015-04-07 23:12:39 +03:00
Skylot 2f548dd9eb core: fix help for jadx-gui, improve code 2015-04-06 22:30:48 +03:00
Skylot f715d6ce68 core: fix inherited methods renaming 2015-04-05 17:43:17 +03:00
Skylot 350b605400 core: use aliased name for save class to file 2015-04-05 16:20:31 +03:00
Skylot 6a99d00487 core: fix enum fields name after obfuscation (fix #51) 2015-04-05 15:56:58 +03:00
Skylot f87bf3f14d core: fix class renaming by source file info 2015-04-05 15:56:58 +03:00
Skylot 87347c0a04 core: move enum restore pass to later stage 2015-04-05 15:10:19 +03:00
Skylot 217737b3e8 core: add jadx visitors annotation for describe dependencies 2015-04-05 15:01:11 +03:00
Skylot efd8bd13da core: rename classes in default package 2015-04-04 21:40:08 +03:00
Skylot 051bb63a81 core: rename classes for case-insensitive systems (fix #24) 2015-04-04 20:56:15 +03:00
Skylot e4f4de6c8d core: fix imports for inner classes 2015-04-04 17:52:13 +03:00
Skylot e6aa85e01d core: skip tests as workaround for java compiler crush 2015-03-31 22:45:06 +03:00
Skylot cc4d94321e core: update android files to 5.1 (fix #58) 2015-03-31 22:25:04 +03:00
Skylot c1292dff75 core refactor: don't use static field in ArgType class 2015-03-29 15:15:56 +03:00
Skylot 1d81cab4a1 core: change anonymous class marking 2015-03-29 14:46:52 +03:00
Skylot 2815cef1bb gui: show info string if no recent files available 2015-03-29 14:45:19 +03:00
Skylot d4523c4e53 core: remove 'static' modifier for inner interfaces 2015-03-29 14:43:44 +03:00
Skylot 5d894b6150 core: don't process dependencies of dependencies 2015-03-29 14:38:15 +03:00
Skylot 2eddbb9119 core: move class renaming code from ClassInfo to RenameVisitor 2015-03-26 23:51:53 +03:00
Skylot a2513240ff core: fix method parameters annotation parsing (fix #57) 2015-03-26 23:50:32 +03:00
Skylot 0d509f94b7 core: fix various processing issues 2015-03-26 23:50:32 +03:00
Skylot e4fbbcf2d6 core: skip annotations parsing if error occurs (#57) 2015-03-25 22:30:22 +03:00
Skylot 9afacf72f8 core: move 'escape' method to string utils 2015-03-25 22:22:09 +03:00
Skylot 78a7e65a2d core: filter out java core classes from printed stacktraces 2015-03-25 22:18:27 +03:00
Skylot 3314de8dde core: rename fields and methods in deobfuscation pass. 2015-03-24 23:24:20 +03:00
Skylot 8dab9b83be core: fix various codegen errors 2015-03-17 23:29:15 +03:00
Skylot 7b264ef2be gui: add font selection dialog 2015-03-16 22:44:21 +03:00
Skylot 5a6600f748 core: fix try/catch wrap logic (fix #47) 2015-03-15 18:47:14 +03:00
Skylot 14ed0c3a3d core: rename classes with unicode characters or reserved names 2015-03-14 20:35:41 +03:00
Skylot 229d78f1ef gui: add preferences dialog 2015-03-14 20:35:37 +03:00
Skylot f770e4ef42 add full license text file 2015-03-14 19:18:59 +03:00
Skylot 66aa2f8f2a fix issues reported by coverity and code style 2015-03-09 14:00:59 +03:00
Skylot 99d831c498 core: use source file information for deobfuscation, fix code style issues 2015-03-08 17:37:24 +03:00
Skylot a532287ddf core: refactor deobfuscator 2015-03-08 14:46:01 +03:00
Skylot 7844e554aa core: refactor info classes for store only one instance 2015-03-08 14:18:12 +03:00
Skylot 10de4ff490 core: process dependant classes before code generation 2015-03-08 14:18:12 +03:00
Skylot eed65421ea core: fix incorrect argument removing in anonymous constructor, inline synthetic field increment method 2015-03-07 20:09:51 +03:00
Skylot 7accc6e516 core: fix synchronized block processing (fix #46) 2015-03-07 17:50:50 +03:00
Skylot fa8f9ccfaa core: move debug code to separate class 2015-03-07 17:50:33 +03:00
Skylot 8a264ca321 update gradle and dependencies 2015-03-07 17:03:53 +03:00
Skylot f366eac7eb core: fix switch in loop (fix #52) 2015-03-01 18:27:30 +03:00
Skylot 46d3992b41 core: fix 'finally' extract (fix #53 and #54) 2015-03-01 15:31:43 +03:00
Skylot 164123f542 core: improve variable names after 'toString' invoke 2015-03-01 15:21:13 +03:00
Skylot 72c301dc54 core: print error on failed method decode 2015-02-25 22:15:26 +03:00
Skylot e8fd1e1dc7 core: fix debug info processing NPE 2015-02-24 23:20:54 +03:00
Skylot 2b7f8931a4 core: fix source line for some return instructions 2015-02-21 18:09:14 +03:00
Skylot ec3b71e5b6 core: don't hardcode attributes count 2015-02-21 17:01:04 +03:00
Skylot f7303881aa core: fix annotations processing for method arguments 2015-02-21 16:58:54 +03:00
Skylot 1b98be0b0a core: fix array type for new-array instruction (fix #50) 2015-02-17 14:53:08 +03:00
Skylot e5b84d942e core: fix types for constant replace 2015-02-15 17:44:05 +03:00
Skylot 22e9ac22ba core: fix field search with obfuscated names 2015-02-14 19:28:42 +03:00
Skylot 8a6cdec796 core: refactor fill-array instruction processing and constants replace (fix #48) 2015-02-14 17:58:46 +03:00
skylot c5c4499a55 Merge pull request #49 from NeoSpb/basic_deobfuscation
core: added deobfuscation feature (basic functionality)
2015-02-10 22:18:29 +03:00
NeoSpb 30138f7a38 core: added deobfuscation feature (basic functionality) 2015-02-10 20:37:12 +03:00
Skylot 883429fa47 core: fix enum class processing for obfuscated code 2015-02-07 21:18:53 +03:00
Skylot 380ee75d9a core: fix constants replace for constructors and other instructions 2015-02-07 21:18:46 +03:00
Skylot 99d9814083 don't use concatenation in logger, fix other small code style issues 2015-02-07 17:58:19 +03:00
Skylot 141398aeac core: replace 'move' instruction instead argument inline 2015-01-31 14:28:01 +03:00
Skylot 07cef6fd62 update dependencies versions 2015-01-31 14:28:01 +03:00
Skylot aac041f960 core: fix logs and code style 2015-01-31 14:28:01 +03:00
skylot 6ef1600041 Merge pull request #44 from NeoSpb/fixdbgparser
Fix processing of debug info
2015-01-17 19:43:18 +03:00
NeoSpb 733836ea2d core: fix processing of debug info (if local variable used before declaring a debug info) 2015-01-13 19:42:57 +03:00
Skylot b4767626d9 core: prevent ClassCastException in StringBuilder chain converter 2015-01-12 23:34:03 +03:00
Skylot 84edfac8fa resources: improve string pool decoding and errors reporting 2015-01-12 23:33:36 +03:00
Skylot 69252ce721 core: fix processing 'try/catch' in 'if' block 2015-01-12 23:32:48 +03:00
Skylot df1152516a core: print original value near replaced with field value in switch 2015-01-10 21:30:21 +03:00
Skylot 02f9c25f52 core: support fall through cases in switch 2015-01-10 21:19:55 +03:00
Skylot 7fb3988173 resources: skip padding zeroes for UTF-8 string pool 2015-01-09 16:13:01 +03:00
Skylot a50352780b core: use resources ids in manifest decoding 2015-01-07 14:47:26 +03:00
Skylot ff093aeebb core: fix strings pool parsing in '.arsc' file 2015-01-07 12:45:08 +03:00
Skylot aa691af664 core: replace resources ids with names from '.arsc' file 2015-01-07 12:18:45 +03:00
Skylot e0ffb01852 core: first implementation of '.arsc' parser 2015-01-06 19:22:45 +03:00
Skylot 53be92c616 core: fix decoding UTF-8 strings in xml resources 2015-01-03 17:06:41 +03:00
Skylot 5f8f454b55 gui: show resources 2015-01-02 20:46:51 +03:00
Skylot 3700ecb717 core: add resources methods to jadx API 2015-01-02 20:46:44 +03:00
Skylot 811b0e7f30 core: fix 'break' insertion for switch/case blocks (fix #41) 2014-12-31 21:25:26 +03:00
Skylot 08ea61f4df core: don't traverse exception handlers twice (includes in TryCatchRegion) 2014-12-31 21:14:50 +03:00
Skylot 1d5368f5a2 core: improve out block detection in switch (issue #38) 2014-12-27 23:28:48 +03:00
Skylot 90fb95e785 core: check arguments for field arithmetic operations (fix #40) 2014-12-27 20:17:03 +03:00
skylot 0f97f07461 Merge pull request #39 from NeoSpb/warn_switch
core: show warning when failed to detect out node in non trivial switch
2014-12-26 23:43:37 +03:00
NeoSpb 7fe6b842a6 core: show warning when failed to detect out node in non trivial switch 2014-12-26 22:13:49 +03:00
skylot 02a97bcb3a Merge pull request #37 from NeoSpb/fix_gui_save_manifest
fix save AndroidManifest.xml when jadx-gui used
2014-12-26 21:48:42 +03:00
NeoSpb fd4289aa64 fix save AndroidManifest.xml when jadx-gui used 2014-12-26 21:14:43 +03:00
Skylot 716db8b964 manifest: restore application references and Android values (enums, flags) 2014-12-24 23:27:26 +03:00
Skylot b55975a35a core: reformat code and fix small issues in BinaryXMLParser 2014-12-23 23:27:50 +03:00
skylot 4cb34394b4 Merge pull request #36 from YASME-Tim/xmlparser
Added first implementation of an AndroidManifest.xml Parser! ;)
2014-12-23 21:50:23 +03:00
YASME-Tim aacb83290e Added option flag to make androidmanifest.xml decompiling optional. 2014-12-22 22:11:04 +01:00
YASME-Tim ddab4c269d Removed debug output. 2014-12-22 14:04:28 +01:00
YASME-Tim 6ddb0036fa Added style decoding and a first decoding for data type 17. 2014-12-22 13:36:10 +01:00
YASME-Tim 0f7ca8cea4 Added a whitespace before oneLiner ends. 2014-12-22 11:56:06 +01:00
YASME-Tim c4367e25a9 Removed call in main method. 2014-12-22 00:44:29 +01:00
YASME-Tim e081aadd27 Added xml header 2014-12-22 00:44:02 +01:00
YASME-Tim 2bacab7dc0 Removed old less-warnings branch commit changes. 2014-12-22 00:40:09 +01:00
YASME-Tim 824db6be2b Removed BinaryXMLParser Call in main method. 2014-12-22 00:15:30 +01:00
YASME-Tim 2fdb26146b Refactored attribute value printing. 2014-12-22 00:14:46 +01:00
YASME-Tim b87d1a7fe1 Fixed XML oneLiners. Added another attribute value data type 2014-12-22 00:11:37 +01:00
YASME-Tim c242a62bcc Write xml to a given output file instead of stdout. 2014-12-21 23:26:02 +01:00
YASME-Tim 6c91bce663 Correct tab numbers. Some little things still missing. 2014-12-21 23:03:15 +01:00
YASME-Tim 7fd46633a3 First near working example for first sample. 2014-12-21 22:37:50 +01:00
YASME-Tim 3c425990f6 Boundaries check. Testing with other given xml binaries. 2014-12-21 22:30:32 +01:00
YASME-Tim 55f16cc3ec Boundaries check. Testing with other given xml binaries. 2014-12-21 22:30:21 +01:00
YASME-Tim e01789bb0d Added first implementation of the AndroidManifest XML Parser 2014-12-21 22:05:44 +01:00
skylot e3696af8ea Merge pull request #34 from YASME-Tim/zip-file
Added support for files ending in .zip.
2014-12-20 11:20:26 +03:00
YASME-Tim a26d7b5a8b Removed some warnings about collections without type specifiers. 2014-12-19 23:40:35 +01:00
YASME-Tim c4fe9150bf Added support for files ending in .zip. 2014-12-19 22:42:14 +01:00
Skylot ffc642048e core: fix type check for loop over iterable. 2014-12-18 22:24:28 +03:00
Skylot 8de6190a81 core: fix type inference for arguments in Phi node (fix #33) 2014-12-17 23:18:44 +03:00
Skylot d6e2c69202 travis: use container based build 2014-12-17 23:18:44 +03:00
Skylot 1a85fa8e3c core: fix complex conditions with mode alternation (fix #31) 2014-12-13 18:24:36 +03:00
skylot c7b8508c6f Merge pull request #29 from riboribo/master
Extended convertInvoke to support more StringBuilder formats
2014-12-03 22:50:27 +03:00
Bob Flavin c35f6e2543 Extended convertInvoke to handle calls to StringBuilder constructor with
arguments, ie: new StringBuilder("str")  or  new
StringBuilder(String.valueOf(var))
2014-12-03 12:34:02 -05:00
Bob Flavin 8052a90d04 Extended string concatenation code to handle arguments in 'new
StringBuilder()' constructer, ie  'new StringBuilder("str")' or 'new
StringBuilder(String.valueof(varName))'
2014-12-03 11:58:04 -05:00
Skylot 3d20d7d330 core: improve 'finally' extraction, refactor instructions 2014-11-29 20:48:04 +03:00
Skylot 5e722c6827 fix issues reported by coverity 2014-11-29 20:48:04 +03:00
Skylot 10198bc87f gui: update RSyntaxTextArea version, refactor new version checks 2014-11-29 20:47:54 +03:00
Skylot a6b4043e8c update gradle and dependencies 2014-11-29 16:08:29 +03:00
Skylot 9cea0163fa core: fix BlockNode hashCode function 2014-11-29 14:43:51 +03:00
Skylot 577176dd31 core: implement 'finally' block extraction 2014-11-26 22:00:47 +03:00
Skylot a135eb44f3 core: check registers numbers, fix fallback mode 2014-11-13 22:42:52 +03:00
Skylot 252ed0e1e4 bump version 2014-11-09 16:48:21 +03:00
Skylot fcb120a3ed core: suppress type error exception 2014-11-09 15:34:19 +03:00
Skylot 988628a2e7 core: fix variable declaration used in several loops 2014-11-09 14:55:33 +03:00
Skylot c24cdf5cc1 core: fix constructor instruction replacement 2014-11-08 20:38:22 +03:00
Skylot d748e004d2 core: fix missing parenthesis in conditions 2014-11-08 20:38:22 +03:00
Skylot 380b73d1b9 core: sort methods by source line number 2014-11-08 20:38:17 +03:00
Skylot ef85e29a9b core: improve 'break' and 'continue' insertion 2014-11-07 23:03:32 +03:00
Skylot 1daf5d1090 core: fix condition processing (resolve #25) 2014-11-07 21:39:27 +03:00
Skylot 9d2c0e4aea core: fix type inference and const inline for arrays 2014-11-07 20:07:18 +03:00
Skylot 7277ebb9c4 core: expand arrays for vararg arguments 2014-11-04 15:42:48 +03:00
Skylot c18074f6aa core: insert 'continue' instruction 2014-11-03 22:31:21 +03:00
Skylot 8a706193e7 core: fix indexed loop checks 2014-11-03 22:04:42 +03:00
Skylot 9d77f5f5df update dx library and dependencies 2014-11-03 15:22:49 +03:00
Skylot 6951d0e646 core: use NotNull and Nullable annotations 2014-11-03 15:13:52 +03:00
Skylot 73dd55eac2 core: fix code style issues 2014-11-03 15:11:48 +03:00
Skylot b5a9389cc6 core: fix variables inline in 'catch' block 2014-11-03 14:53:27 +03:00
Skylot d905c96fbe core: refactor 'catch' clause variable processing 2014-11-02 19:06:41 +03:00
Skylot 03f03f85af core: replace removed synthetic constructor 2014-11-01 15:46:57 +03:00
Skylot 2b00a8a406 core: disable parenthesis remove (break code in most cases) 2014-10-25 22:36:21 +04:00
Skylot f31c2dcd21 core: fix processing 'if' at loop end 2014-10-24 23:12:42 +04:00
Skylot 7699cfac02 tests: fix build on Windows 2014-10-23 21:30:46 +04:00
Skylot 5c48a457b4 fix code style issues 2014-10-19 19:07:15 +04:00
Skylot b5f439e1aa tests: reformat code 2014-10-19 18:01:32 +04:00
Skylot 202fe5a0a9 core: fix links for fields in nested classes 2014-10-18 23:08:15 +04:00
Skylot 68ccf57bd4 core: fix type detection for method arguments 2014-10-18 23:08:10 +04:00
Skylot 84970759d8 core: fix switch over enum with several enums in class 2014-10-14 22:38:29 +04:00
Skylot 53cac58ebe core: fix processing of last instruction in 'try' block 2014-10-11 22:44:26 +04:00
Skylot adc32ed319 fix minor issues 2014-10-11 22:44:26 +04:00
Skylot 7f0815a7b2 core tests: add option for compile test without debug info 2014-10-11 22:21:40 +04:00
Skylot 68f5565b63 core: improve processing of 'try/catch' and 'break' in loops 2014-10-11 22:21:30 +04:00
Skylot c552fb857d core tests: replace several classes in dynamic class loader, add additional checks 2014-10-07 22:19:54 +04:00
Skylot 8a4ec47b92 core: support break with label for simple cases 2014-09-29 23:44:36 +04:00
Skylot d281126337 core: fix processing try/catch in loop 2014-09-27 20:09:25 +04:00
Skylot 4fb6ada5ec core: fix type inference for phi nodes 2014-09-26 22:19:23 +04:00
Skylot ab924faa1e core: don't remove empty catch blocks 2014-09-22 22:48:25 +04:00
Skylot b12b129af7 travis: add jdk8 to build matrix 2014-09-22 22:34:44 +04:00
Skylot 017c6b4d42 core tests: compile decompiled code 2014-09-22 22:25:42 +04:00
Skylot d55cd5fbb4 core tests: organize directories 2014-09-22 22:02:42 +04:00
Skylot 13a6b1c8c6 core: add 'show inconsistent code' parameter 2014-09-20 13:43:55 +04:00
Skylot 0bc37e5d32 gui: fix jump manager 2014-09-20 13:01:20 +04:00
Skylot 46c8572887 core: restore switch over enum 2014-09-18 20:59:39 +04:00
Skylot e6b919007c gui: add new version notification 2014-09-16 22:03:18 +04:00
Skylot ac5a6096bb core: fix constructor call for moved arg (fix #20) 2014-09-13 18:55:17 +04:00
Skylot db527fbbda core: add api for write tests using smali 2014-09-13 18:55:17 +04:00
skylot 8f201f1fee Merge pull request #19 from NeoSpb/fix3
core: fix processing of debug info (markup of local variables)
2014-09-13 14:19:19 +04:00
Anton Dyachenko d1e0762c12 core: fix processing of debug info (markup of local variables) 2014-09-12 19:24:44 +04:00
Skylot 010ae99c69 core: restore simple for-each loop over iterable object 2014-09-07 16:49:02 +04:00
Skylot a4632d6e86 core: fix high memory usage while process conditions 2014-09-04 22:35:47 +04:00
Skylot 2a3162f869 core: don't set 'skip' flag for failed nested 'if' merge (issue #18) 2014-09-02 22:46:12 +04:00
skylot 2063fd0742 Merge pull request #17 from NeoSpb/fix2
Fix2 by NeoSpb
2014-09-02 20:47:14 +04:00
Anton Dyachenko 128fe8a839 core: fix resolving the instance field in the 2nd and more nested inner class 2014-09-02 20:05:15 +04:00
Anton Dyachenko 2478fc3a1b core: fix instance initializer producing (don't generate super() call) 2014-09-02 19:55:26 +04:00
Skylot 5a68d3bef7 core: restore for-each loop over array 2014-09-01 23:09:04 +04:00
Skylot 195eeceb62 core: restore simple indexed loops 2014-08-30 23:15:51 +04:00
Skylot ec8309af49 core: fix processing 'if' at loop end 2014-08-20 22:02:00 +04:00
skylot 627a4dc802 add contribution section to readme 2014-08-19 23:25:25 +04:00
Skylot e2018535ef core: add ternary conditions processing 2014-08-19 22:27:51 +04:00
Skylot ee56610f06 core: allow method name be same as class name (issue #15) 2014-08-18 20:45:50 +04:00
skylot fb9ff7748a Merge pull request #14 from NeoSpb/gui_preferences
gui: add saving preferences (open/save paths, flatten packages)
2014-08-17 13:22:08 +04:00
Anton Dyachenko cdfb46d9d3 gui: add saving preferences (open/save paths, flatten packages) 2014-08-17 12:01:46 +04:00
Skylot 5545a94a9e core: process nested ternary operators 2014-08-16 17:39:30 +04:00
Skylot 9e811d959b core: add method for print line numbers 2014-08-16 17:16:56 +04:00
Skylot 957d5394d2 refactor: add static methods for create DotGraphVisitor 2014-08-16 17:06:50 +04:00
Skylot 95afe1219e core: don't cache dex strings (old workaround for bug in dx) 2014-08-16 15:07:06 +04:00
Skylot 07937f1d71 bump version to 0.5.3 2014-08-16 15:06:55 +04:00
Skylot 671be0af0a add jadx-gui screenshot 2014-08-15 23:08:18 +04:00
Skylot 7e9278f992 don't hardcode maximum Java heap size 2014-08-15 22:39:34 +04:00
Skylot 9194441c47 add ASM to NOTICE file 2014-08-15 22:38:50 +04:00
Skylot 4f307c0085 core: allow subblock replace for 'if' region 2014-08-14 22:38:29 +04:00
Skylot 3bdda55102 core: inline filled array creation 2014-08-14 22:23:13 +04:00
Skylot b657b0fb1f core: fix 'if' processing in 'do/while' loop 2014-08-12 23:00:29 +04:00
Skylot 4935ae6da5 core: hide value parser constants 2014-08-12 22:58:20 +04:00
Skylot 72a50eae43 core: fix missing blocks in loop region 2014-08-11 22:29:10 +04:00
Skylot fa37b90cff core: fix processing try/catch in other catch 2014-08-10 22:36:42 +04:00
Skylot 052a8db606 core: resolve minor issues 2014-08-09 19:32:13 +04:00
Skylot 88ccba166e core: don't inline variables defined in 'try' and used in 'catch' 2014-08-08 22:10:10 +04:00
Skylot 58998089a6 core: redone 'if' structure checking 2014-08-07 22:20:47 +04:00
Skylot f0a73b329e core: fix processing conditions in loop 2014-08-06 22:28:29 +04:00
Skylot c97678a477 refactor: make ErrorsCounter non static 2014-08-05 22:48:31 +04:00
Skylot 2ad739275f core: handle special values for numbers 2014-08-04 22:07:10 +04:00
Skylot caad78885d core: check for duplicated code generation 2014-08-02 16:39:14 +04:00
Skylot a234227b9f core: fix errors in try/catch processing (issue #13) 2014-08-02 16:33:52 +04:00
Skylot 16f736e773 core: fix missing 'catch' code 2014-07-30 23:05:39 +04:00
Skylot 1fe24ad11d travis: cache dependencies 2014-07-29 23:41:10 +04:00
Skylot 33c5e0827a core: always check arguments before inline 2014-07-29 22:59:53 +04:00
Skylot cbd36aeb8f core: fix unused variables declaration 2014-07-29 22:34:18 +04:00
Skylot 2963bb3f41 core: fix issues reported by coverity 2014-07-28 23:19:48 +04:00
Skylot 09a6ceac63 gui: replace underline to color highlight (experimental) 2014-07-28 22:50:55 +04:00
Skylot 75d8a01cab core: improve error reporting 2014-07-28 22:50:55 +04:00
Skylot 0968f75e9a core: fix condition in loops (issue #9) 2014-07-28 22:50:42 +04:00
Skylot bc0db88afa update gradle and dependencies versions 2014-07-18 23:29:36 +04:00
Skylot 5f11f12d0c core: remove redundant spaces for enums 2014-07-18 21:21:24 +04:00
Skylot 2d18950542 core: add some integration tests 2014-07-17 23:32:18 +04:00
Skylot 50d314445a core: fix code style 2014-07-17 23:31:07 +04:00
Skylot f8d57d9265 core: decompile '.class' files 2014-07-15 23:45:25 +04:00
Skylot ebbe6db378 core: fix complex 'if' processing (issues #9 and #12) 2014-07-12 21:26:14 +04:00
skylot 543cad3a23 Merge pull request #11 from Fruiter/master
core: fix nested try-catch blocks processing
2014-07-07 21:13:23 +04:00
fruiter 41cc83dbf6 core: fix nested try-catch blocks processing 2014-07-06 20:15:20 -04:00
Skylot ce7101be88 core: always inline 'this' (issue #10) 2014-06-28 15:39:35 +04:00
Skylot 0a241e3a9c core tests: add custom string matchers 2014-06-28 15:38:50 +04:00
Skylot 37857e88ea core: fix switch statement processing (issue #9 case 2) 2014-06-24 14:08:20 +04:00
Skylot 6fbcf46a8b core: refactor return remover visitor 2014-06-24 14:08:20 +04:00
Skylot a36bc8f29a core: add serial uid to JadxRuntimeException 2014-06-24 14:08:20 +04:00
Skylot 813b7bca6e core: sort error nodes in execution report 2014-06-23 23:37:39 +04:00
Skylot e2945f2a42 core: limit region traversal iterations count 2014-06-23 23:37:39 +04:00
Skylot eaf623a560 gui: fix sync with editor 2014-06-20 20:20:35 +04:00
Skylot 26aa504590 core: guard endless regions processing 2014-06-20 17:08:07 +04:00
Skylot e4dde3f4b6 core: fix class cast exception 2014-06-15 21:35:34 +04:00
Skylot 9c90699c40 core: fix parsing of generic signature with inner classes 2014-06-15 17:42:48 +04:00
Skylot b67cd50e8a gui: add definitions search window 2014-06-14 19:38:24 +04:00
Skylot d2acaa03f5 core: guess variable name from assign instruction 2014-06-06 21:22:20 +04:00
Skylot f2aa4cd10b core: make better variables naming 2014-06-05 19:40:57 +04:00
Skylot b940b99e75 core: fix issues in variable names and try/catch blocks 2014-06-03 23:08:40 +04:00
Skylot 868e0706ea core: fix source line number parsing and saving 2014-05-31 16:58:03 +04:00
Skylot 324f544ba2 gui: show source line numbers 2014-05-31 16:17:40 +04:00
Skylot 0a1981f90e gui: add hyperlinks for classes and fields 2014-05-25 23:14:29 +04:00
Skylot 0a36bfb088 core: fix try-catch blocks processing 2014-05-24 19:55:29 +04:00
Skylot 0d94af099b core: improve 'if' detection with 'return' instruction 2014-05-18 00:28:43 +04:00
Skylot 4a6115ed64 core: refactor attribute storage 2014-05-14 21:12:39 +04:00
Skylot 42eb319751 fix issues reported by Coverity 2014-05-12 22:08:33 +04:00
Skylot 343bddc6ad core: fix 'break' detection in loop 2014-05-12 22:08:33 +04:00
Skylot 632a742ea9 core: fix method inline 2014-05-12 22:08:33 +04:00
Skylot 08c9d1228a core: inline 'cmp' instruction 2014-05-12 22:08:33 +04:00
Skylot 11d8b28fb4 core: fix variable rename 2014-05-12 22:08:33 +04:00
Skylot 12b6371209 core: fix basic block initial id 2014-05-12 22:08:33 +04:00
Skylot 24d22aaafb core: fix 'if' detection 2014-05-12 22:08:33 +04:00
Skylot ebf7822628 use spock framework for unit tests 2014-05-12 22:08:33 +04:00
Skylot 7669fa1582 update dependencies, add coveralls badge 2014-05-12 22:08:33 +04:00
Skylot e49ba61917 core: use SSA representation for instruction arguments 2014-05-12 22:08:32 +04:00
Skylot 96db1c2479 core: reformat code 2014-05-12 21:59:37 +04:00
skylot 7abdb41a9e Merge pull request #7 from bkerler/master
Add support for dx 1.8 library
2014-05-12 21:28:55 +04:00
Bjoern Kerler 14f6d2f3b0 Add support for dx 1.8 library 2014-05-12 09:12:34 +02:00
Skylot 4e4b4975ad core: fix method redecompilation (issue #6) 2014-04-24 23:44:30 +04:00
skylot 93fafcf886 Merge pull request #5 from dnet/master
removed unused private method getCodePanel(int)
2014-04-02 22:03:57 +04:00
András Veres-Szentkirályi 82cc88a1b9 removed unused private method getCodePanel(int) 2014-04-02 17:10:34 +02:00
Skylot 5c94e0bccc core: improve exceptions handling 2014-03-27 23:24:20 +04:00
582 changed files with 34470 additions and 6882 deletions
+3
View File
@@ -0,0 +1,3 @@
[submodule "jadx-test-app/test-app"]
path = jadx-test-app/test-app
url = git://github.com/skylot/jadx-test-app.git
+17 -1
View File
@@ -1,12 +1,28 @@
language: java
jdk:
- oraclejdk8
- oraclejdk7
- openjdk7
- openjdk6
before_install:
- chmod +x gradlew
script:
- TERM=dumb ./gradlew clean build dist
after_success:
- TERM=dumb ./gradlew jacocoTestReport coveralls
sudo: false
cache:
directories:
- $HOME/.gradle/caches/
- $HOME/.gradle/wrapper/
before_cache:
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
notifications:
email:
- skylot@gmail.com
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+80 -12
View File
@@ -1,8 +1,8 @@
The majority of jadx is written and copyrighted by me (Skylot)
and released under the Apache 2.0 license:
and released under the Apache 2.0 license (see LICENSE file for full license text):
*******************************************************************************
Copyright 2013, Skylot
Copyright 2015, Skylot
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -18,8 +18,8 @@ limitations under the License.
*******************************************************************************
Various portions of the code including dx library are taken from
the Android Open Source Project, and are used in accordance with
Various portions of the code including dx library are taken from
the Android Open Source Project, and are used in accordance with
the following license:
*******************************************************************************
@@ -74,10 +74,10 @@ Copyright (c) 2004-2011 QOS.ch
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
@@ -92,24 +92,60 @@ Logback source code and binaries are dual-licensed under the EPL v1.0 and the LG
*******************************************************************************
Logback: the reliable, generic, fast and flexible logging framework.
Copyright (C) 1999-2012, QOS.ch. All rights reserved.
Copyright (C) 1999-2012, QOS.ch. All rights reserved.
This program and the accompanying materials are dual-licensed under
either the terms of the Eclipse Public License v1.0 as published by
the Eclipse Foundation
or (per the licensee's choosing)
under the terms of the GNU Lesser General Public License version 2.1
as published by the Free Software Foundation.
*******************************************************************************
ASM library:
*******************************************************************************
Copyright (c) 2000-2011 INRIA, France Telecom
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holders nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
THE POSSIBILITY OF SUCH DAMAGE.
*******************************************************************************
Jadx-gui components
===================
RSyntaxTextArea library licensed under modified BSD liense:
RSyntaxTextArea library (https://github.com/bobbylight/RSyntaxTextArea)
licensed under modified BSD license:
*******************************************************************************
Copyright (c) 2012, Robert Futrell
@@ -139,7 +175,39 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*******************************************************************************
Concurrent Trees (https://code.google.com/p/concurrent-trees/)
licenced under Apache License 2.0:
*******************************************************************************
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*******************************************************************************
Image Viewer (https://github.com/kazocsaba/imageviewer)
*******************************************************************************
Copyright (c) 2008-2012 Kazó Csaba
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*******************************************************************************
JFontChooser Component - http://sourceforge.jp/projects/jfontchooser/
Icons copied from several places:
- Eclipse Project (JDT UI) - licensed under EPL v1.0 (http://www.eclipse.org/legal/epl-v10.html)
- famfamfam silk icon set (http://www.famfamfam.com/lab/icons/silk/) - licensed under Creative Commons Attribution 2.5 License (http://creativecommons.org/licenses/by/2.5/)
- famfamfam silk icon set (http://www.famfamfam.com/lab/icons/silk/) - licensed
under Creative Commons Attribution 2.5 License (http://creativecommons.org/licenses/by/2.5/)
+56 -17
View File
@@ -1,24 +1,28 @@
## JADX
## 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)
[![License](http://img.shields.io/:license-apache-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0.html)
**jadx** - Dex to Java decompiler
Command line and GUI tools for produce Java source code from Android Dex and Apk files
Note: jadx-gui now in experimental stage
![jadx-gui screenshot](http://skylot.github.io/jadx/jadx-gui.png)
### Downloads
- [unstable](https://drone.io/github.com/skylot/jadx/files)
[![Build Status](https://drone.io/github.com/skylot/jadx/status.png)](https://drone.io/github.com/skylot/jadx/latest)
[![Build Status](https://travis-ci.org/skylot/jadx.png?branch=master)](https://travis-ci.org/skylot/jadx)
- from [github](https://github.com/skylot/jadx/releases)
- from [sourceforge](http://sourceforge.net/projects/jadx/files/)
- from [sourceforge](http://sourceforge.net/projects/jadx/files/)
### Building from source
### Building from source
git clone https://github.com/skylot/jadx.git
cd jadx
./gradlew dist
(on Windows, use `gradlew.bat` instead of `./gradlew`)
Scripts for run jadx will be placed in `build/jadx/bin`
@@ -36,19 +40,54 @@ Run **jadx** on itself:
### Usage
```
jadx[-gui] [options] <input file> (.dex, .apk or .jar)
jadx[-gui] [options] <input file> (.dex, .apk, .jar or .class)
options:
-d, --output-dir - output directory
-j, --threads-count - processing threads count
-f, --fallback - make simple dump (using goto instead of 'if', 'for', etc)
--cfg - save methods control flow graph to dot file
--raw-cfg - save methods control flow graph (use raw instructions)
-v, --verbose - verbose output
-h, --help - print this help
-d, --output-dir - output directory
-j, --threads-count - processing threads count
-r, --no-res - do not decode resources
-s, --no-src - do not decompile source code
-e, --export-gradle - save as android gradle project
--show-bad-code - show inconsistent code (incorrectly decompiled)
--no-replace-consts - don't replace constant value with matching constant field
--escape-unicode - escape non latin characters in strings (with \u)
--deobf - activate deobfuscation
--deobf-min - min length of name
--deobf-max - max length of name
--deobf-rewrite-cfg - force to save deobfuscation map
--deobf-use-sourcename - use source file name as class name alias
--cfg - save methods control flow graph to dot file
--raw-cfg - save methods control flow graph (use raw instructions)
-f, --fallback - make simple dump (using goto instead of 'if', 'for', etc)
-v, --verbose - verbose output
-h, --help - print this help
Example:
jadx -d out classes.dex
```
### Troubleshooting
##### Out of memory error:
- Reduce processing threads count (`-j` option)
- Increase maximum java heap size:
* command line (example for linux):
`JAVA_OPTS="-Xmx4G" jadx -j 1 some.apk`
* edit 'jadx' script (jadx.bat on Windows) and setup bigger heap size:
`DEFAULT_JVM_OPTS="-Xmx2500M"`
### Contribution
To support this project you can:
- Post thoughts about new features/optimizations that important to you
- Submit bug using one of following patterns:
* Java code examples which decompiles incorrectly
* Error log and link to _public available_ apk file or app page on Google play
And any other comments will be very helpfull,
because at current stage of development it is very time consuming
to **find** new bugs, design and implement new features.
Also I need to **prioritize** these task for complete most important at first.
---------------------------------------
*Licensed under the Apache 2.0 License*
*Copyright 2013 by Skylot*
*Copyright 2015 by Skylot*
+50 -21
View File
@@ -1,24 +1,33 @@
buildscript {
repositories {
mavenCentral()
jcenter()
}
}
plugins {
id "com.github.kt3k.coveralls" version "2.3.1"
id "info.solidsoft.pitest" version "1.1.4"
// id "com.github.ben-manes.versions" version "0.8"
}
ext.jadxVersion = file('version').readLines().get(0)
version = jadxVersion
apply plugin: 'sonar-runner'
subprojects {
apply plugin: 'java'
apply plugin: 'groovy'
apply plugin: 'jacoco'
apply plugin: 'idea'
apply plugin: 'eclipse'
sourceCompatibility = 1.6
targetCompatibility = 1.6
apply plugin: 'com.github.kt3k.coveralls'
version = jadxVersion
gradle.projectsEvaluated {
tasks.withType(Compile) {
if (!"${it}".contains(':jadx-samples:')) {
options.compilerArgs << '-Xlint' << '-Xlint:unchecked' << '-Xlint:deprecation'
}
tasks.withType(JavaCompile) {
sourceCompatibility = JavaVersion.VERSION_1_6
targetCompatibility = JavaVersion.VERSION_1_6
if (!"$it".contains(':jadx-samples:')) {
options.compilerArgs << '-Xlint' << '-Xlint:unchecked' << '-Xlint:deprecation'
}
}
@@ -30,21 +39,34 @@ subprojects {
}
dependencies {
compile 'org.slf4j:slf4j-api:1.7.6'
testCompile 'junit:junit:4.11'
testCompile 'org.mockito:mockito-core:1.9.5'
testCompile 'ch.qos.logback:logback-classic:1.1.1'
compile 'org.slf4j:slf4j-api:1.7.10'
testCompile 'ch.qos.logback:logback-classic:1.1.2'
testCompile 'junit:junit:4.12'
testCompile 'org.hamcrest:hamcrest-library:1.3'
testCompile 'org.mockito:mockito-core:1.10.19'
testCompile 'org.spockframework:spock-core:1.0-groovy-2.4'
testCompile 'cglib:cglib-nodep:3.1'
}
repositories {
mavenCentral()
mavenLocal()
jcenter()
}
jacocoTestReport {
reports {
xml.enabled = true // coveralls plugin depends on xml format report
html.enabled = true
}
}
}
task copyArtifacts(type: Sync, dependsOn: ['jadx-cli:installApp', 'jadx-gui:installApp']) {
task copyArtifacts(type: Sync, dependsOn: ['jadx-cli:installDist', 'jadx-gui:installDist']) {
destinationDir file("$buildDir/jadx")
['jadx-cli', 'jadx-gui'].each {
from tasks.getByPath(":${it}:installApp").destinationDir
from tasks.getByPath(":${it}:installDist").destinationDir
}
}
@@ -61,13 +83,20 @@ task dist(dependsOn: pack) {
task samples(dependsOn: 'jadx-samples:samples') {
}
task build(dependsOn: [dist, samples]) {
task testAppCheck(dependsOn: 'jadx-test-app:testAppCheck') {
}
task clean(type: Delete) {
task pitest(overwrite: true, dependsOn: 'jadx-core:pitest') {
}
task cleanBuildDir(type: Delete) {
delete buildDir
}
build.dependsOn(dist, samples)
clean.dependsOn(cleanBuildDir)
task wrapper(type: Wrapper) {
gradleVersion = '1.11'
gradleVersion = '2.7'
}
Binary file not shown.
+1 -1
View File
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=http\://services.gradle.org/distributions/gradle-1.11-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-2.7-bin.zip
+3 -12
View File
@@ -5,18 +5,8 @@ applicationName = 'jadx'
dependencies {
compile(project(':jadx-core'))
compile 'com.beust:jcommander:1.30'
compile 'ch.qos.logback:logback-classic:1.1.1'
}
startScripts {
doLast {
// increase default max heap size
String var = 'DEFAULT_JVM_OPTS='
String args = '-Xmx1300M'
unixScript.text = unixScript.text.replace(var + '""', var + '"' + args + '"')
windowsScript.text = windowsScript.text.replace(var, var + args)
}
compile 'com.beust:jcommander:1.47'
compile 'ch.qos.logback:logback-classic:1.1.2'
}
applicationDistribution.with {
@@ -24,6 +14,7 @@ applicationDistribution.with {
from '../.'
include 'README.md'
include 'NOTICE'
include 'LICENSE'
}
}
+24 -24
View File
@@ -1,7 +1,6 @@
package jadx.cli;
import jadx.api.Decompiler;
import jadx.core.utils.ErrorsCounter;
import jadx.api.JadxDecompiler;
import jadx.core.utils.exceptions.JadxException;
import java.io.File;
@@ -12,39 +11,39 @@ import org.slf4j.LoggerFactory;
public class JadxCLI {
private static final Logger LOG = LoggerFactory.getLogger(JadxCLI.class);
public static void main(String[] args) {
public static void main(String[] args) throws JadxException {
try {
JadxCLIArgs jadxArgs = new JadxCLIArgs(args);
checkArgs(jadxArgs);
processAndSave(jadxArgs);
} catch (Exception e) {
LOG.error(e.getMessage());
JadxCLIArgs jadxArgs = new JadxCLIArgs();
if (processArgs(jadxArgs, args)) {
processAndSave(jadxArgs);
}
} catch (Throwable e) {
LOG.error("jadx error: {}", e.getMessage(), e);
System.exit(1);
}
}
private static void processAndSave(JadxCLIArgs jadxArgs) {
try {
Decompiler jadx = new Decompiler(jadxArgs);
jadx.loadFiles(jadxArgs.getInput());
jadx.setOutputDir(jadxArgs.getOutDir());
jadx.save();
static void processAndSave(JadxCLIArgs jadxArgs) throws JadxException {
JadxDecompiler jadx = new JadxDecompiler(jadxArgs);
jadx.setOutputDir(jadxArgs.getOutDir());
jadx.loadFiles(jadxArgs.getInput());
jadx.save();
if (jadx.getErrorsCount() != 0) {
jadx.printErrorsReport();
LOG.error("finished with errors");
} else {
LOG.info("done");
} catch (Throwable e) {
LOG.error("jadx error:", e);
}
int errorsCount = ErrorsCounter.getErrorCount();
if (errorsCount != 0) {
ErrorsCounter.printReport();
}
System.exit(errorsCount);
}
private static void checkArgs(JadxCLIArgs jadxArgs) throws JadxException {
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();
System.exit(1);
return false;
}
File outputDir = jadxArgs.getOutDir();
if (outputDir == null) {
@@ -57,12 +56,13 @@ public class JadxCLI {
} else {
outDirName = name + "-jadx-out";
}
LOG.info("output directory: " + outDirName);
LOG.info("output directory: {}", outDirName);
outputDir = new File(outDirName);
jadxArgs.setOutputDir(outputDir);
}
if (outputDir.exists() && !outputDir.isDirectory()) {
throw new JadxException("Output directory exists as file " + outputDir);
}
return true;
}
}
+135 -29
View File
@@ -1,36 +1,72 @@
package jadx.cli;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
import jadx.api.IJadxArgs;
import jadx.core.Consts;
import jadx.api.JadxDecompiler;
import jadx.core.utils.exceptions.JadxException;
import java.io.File;
import java.io.PrintStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.beust.jcommander.IStringConverter;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.ParameterDescription;
import com.beust.jcommander.ParameterException;
public final class JadxCLIArgs implements IJadxArgs {
public class JadxCLIArgs implements IJadxArgs {
@Parameter(description = "<input file> (.dex, .apk or .jar)")
@Parameter(description = "<input file> (.dex, .apk, .jar or .class)")
protected List<String> files;
@Parameter(names = {"-d", "--output-dir"}, description = "output directory")
protected String outDirName;
@Parameter(names = {"-j", "--threads-count"}, description = "processing threads count")
protected int threadsCount = Runtime.getRuntime().availableProcessors();
protected int threadsCount = Math.max(1, Runtime.getRuntime().availableProcessors() / 2);
@Parameter(names = {"-f", "--fallback"}, description = "make simple dump (using goto instead of 'if', 'for', etc)", help = true)
protected boolean fallbackMode = false;
@Parameter(names = {"-r", "--no-res"}, description = "do not decode resources")
protected boolean skipResources = false;
@Parameter(names = {"-s", "--no-src"}, description = "do not decompile source code")
protected boolean skipSources = false;
@Parameter(names = {"-e", "--export-gradle"}, description = "save as android gradle project")
protected boolean exportAsGradleProject = false;
@Parameter(names = {"--show-bad-code"}, description = "show inconsistent code (incorrectly decompiled)")
protected boolean showInconsistentCode = false;
@Parameter(names = "--no-replace-consts", converter = InvertedBooleanConverter.class,
description = "don't replace constant value with matching constant field")
protected boolean replaceConsts = true;
@Parameter(names = {"--escape-unicode"}, description = "escape non latin characters in strings (with \\u)")
protected boolean escapeUnicode = false;
@Parameter(names = {"--deobf"}, description = "activate deobfuscation")
protected boolean deobfuscationOn = false;
@Parameter(names = {"--deobf-min"}, description = "min length of name")
protected int deobfuscationMinLength = 2;
@Parameter(names = {"--deobf-max"}, description = "max length of name")
protected int deobfuscationMaxLength = 64;
@Parameter(names = {"--deobf-rewrite-cfg"}, description = "force to save deobfuscation map")
protected boolean deobfuscationForceSave = false;
@Parameter(names = {"--deobf-use-sourcename"}, description = "use source file name as class name alias")
protected boolean deobfuscationUseSourceNameAsAlias = false;
@Parameter(names = {"--cfg"}, description = "save methods control flow graph to dot file")
protected boolean cfgOutput = false;
@@ -38,6 +74,9 @@ public final class JadxCLIArgs implements IJadxArgs {
@Parameter(names = {"--raw-cfg"}, description = "save methods control flow graph (use raw instructions)")
protected boolean rawCfgOutput = false;
@Parameter(names = {"-f", "--fallback"}, description = "make simple dump (using goto instead of 'if', 'for', etc)")
protected boolean fallbackMode = false;
@Parameter(names = {"-v", "--verbose"}, description = "verbose output")
protected boolean verbose = false;
@@ -47,25 +86,25 @@ public final class JadxCLIArgs implements IJadxArgs {
private final List<File> input = new ArrayList<File>(1);
private File outputDir;
public JadxCLIArgs(String[] args) {
parse(args);
processArgs();
public boolean processArgs(String[] args) {
return parse(args) && process();
}
private void parse(String[] args) {
private boolean parse(String[] args) {
try {
new JCommander(this, args);
return true;
} catch (ParameterException e) {
System.err.println("Arguments parse error: " + e.getMessage());
printUsage();
System.exit(1);
return false;
}
}
public void processArgs() {
private boolean process() {
if (isPrintHelp()) {
printUsage();
System.exit(0);
return false;
}
try {
if (threadsCount <= 0) {
@@ -90,13 +129,18 @@ public final class JadxCLIArgs implements IJadxArgs {
if (isVerbose()) {
ch.qos.logback.classic.Logger rootLogger =
(ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
rootLogger.setLevel(ch.qos.logback.classic.Level.DEBUG);
// remove INFO ThresholdFilter
Appender<ILoggingEvent> appender = rootLogger.getAppender("STDOUT");
if (appender != null) {
appender.clearAllFilters();
}
}
} catch (JadxException e) {
System.err.println("ERROR: " + e.getMessage());
printUsage();
System.exit(1);
return false;
}
return true;
}
public void printUsage() {
@@ -104,34 +148,33 @@ public final class JadxCLIArgs implements IJadxArgs {
// 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: " + Consts.JADX_VERSION);
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 = this.getClass().getDeclaredFields();
Field[] fields = JadxCLIArgs.class.getDeclaredFields();
for (Field f : fields) {
for (ParameterDescription p : params) {
String name = f.getName();
if (name.equals(p.getParameterized().getName())) {
StringBuilder opt = new StringBuilder();
opt.append(' ').append(p.getNames());
addSpaces(opt, maxNamesLen - opt.length() + 2);
opt.append("- ").append(p.getDescription());
out.println(opt.toString());
break;
}
String name = f.getName();
ParameterDescription p = paramsMap.get(name);
if (p == null) {
continue;
}
StringBuilder opt = new StringBuilder();
opt.append(' ').append(p.getNames());
addSpaces(opt, maxNamesLen - opt.length() + 2);
opt.append("- ").append(p.getDescription());
out.println(opt);
}
out.println("Example:");
out.println(" jadx -d out classes.dex");
@@ -143,10 +186,18 @@ public final class JadxCLIArgs implements IJadxArgs {
}
}
public static class InvertedBooleanConverter implements IStringConverter<Boolean> {
@Override
public Boolean convert(String value) {
return "false".equals(value);
}
}
public List<File> getInput() {
return input;
}
@Override
public File getOutDir() {
return outputDir;
}
@@ -159,6 +210,16 @@ public final class JadxCLIArgs implements IJadxArgs {
return printHelp;
}
@Override
public boolean isSkipResources() {
return skipResources;
}
@Override
public boolean isSkipSources() {
return skipSources;
}
@Override
public int getThreadsCount() {
return threadsCount;
@@ -179,8 +240,53 @@ public final class JadxCLIArgs implements IJadxArgs {
return fallbackMode;
}
@Override
public boolean isShowInconsistentCode() {
return showInconsistentCode;
}
@Override
public boolean isVerbose() {
return verbose;
}
@Override
public boolean isDeobfuscationOn() {
return deobfuscationOn;
}
@Override
public int getDeobfuscationMinLength() {
return deobfuscationMinLength;
}
@Override
public int getDeobfuscationMaxLength() {
return deobfuscationMaxLength;
}
@Override
public boolean isDeobfuscationForceSave() {
return deobfuscationForceSave;
}
@Override
public boolean useSourceNameAsClassAlias() {
return deobfuscationUseSourceNameAsAlias;
}
@Override
public boolean escapeUnicode() {
return escapeUnicode;
}
@Override
public boolean isReplaceConsts() {
return replaceConsts;
}
@Override
public boolean isExportAsGradleProject() {
return exportAsGradleProject;
}
}
+5 -2
View File
@@ -1,12 +1,15 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
<encoder>
<pattern>%-5level - %msg%n</pattern>
<pattern>%d{HH:mm:ss} %-5level - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<root level="DEBUG">
<appender-ref ref="STDOUT"/>
</root>
@@ -0,0 +1,22 @@
package jadx.cli;
import org.junit.Test;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
public class JadxCLIArgsTest {
@Test
public void testInvertedBooleanOption() throws Exception {
assertThat(parse("--no-replace-consts").isReplaceConsts(), is(false));
assertThat(parse("").isReplaceConsts(), is(true));
}
private JadxCLIArgs parse(String... args) {
JadxCLIArgs jadxArgs = new JadxCLIArgs();
boolean res = jadxArgs.processArgs(args);
assertThat(res, is(true));
return jadxArgs;
}
}
+23 -3
View File
@@ -1,8 +1,28 @@
ext.jadxClasspath = 'clsp-data/android-4.3.jar'
ext.jadxClasspath = 'clsp-data/android-5.1.jar'
apply plugin: "info.solidsoft.pitest"
dependencies {
compile 'com.google.android.tools:dx:1.7'
runtime files(jadxClasspath)
compile files('lib/dx-1.10.jar')
compile 'commons-io:commons-io:2.4'
compile 'org.ow2.asm:asm:5.0.3'
compile 'com.intellij:annotations:12.0'
compile 'uk.com.robust-it:cloning:1.9.2'
testCompile 'org.smali:smali:2.0.3'
}
task packTests(type: Jar) {
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.
Binary file not shown.
@@ -2,24 +2,32 @@ package jadx.api;
public final class CodePosition {
private final JavaClass cls;
private final JavaNode node;
private final int line;
private final int offset;
public CodePosition(JavaClass cls, int line, int offset) {
this.cls = cls;
public CodePosition(JavaNode node, int line, int offset) {
this.node = node;
this.line = line;
this.offset = offset;
}
public CodePosition(int line, int offset) {
this.cls = null;
this.node = null;
this.line = line;
this.offset = offset;
}
public JavaNode getNode() {
return node;
}
public JavaClass getJavaClass() {
return cls;
JavaClass parent = node.getDeclaringClass();
if (parent == null && node instanceof JavaClass) {
return (JavaClass) node;
}
return parent;
}
public int getLine() {
@@ -49,6 +57,6 @@ public final class CodePosition {
@Override
public String toString() {
return line + ":" + offset + (cls != null ? " " + cls : "");
return line + ":" + offset + (node != null ? " " + node : "");
}
}
@@ -1,206 +0,0 @@
package jadx.api;
import jadx.core.Jadx;
import jadx.core.ProcessClass;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.dex.visitors.SaveCode;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.InputFile;
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.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Jadx API usage example:
* <pre><code>
* Decompiler jadx = new Decompiler();
* jadx.loadFile(new File("classes.dex"));
* jadx.setOutputDir(new File("out"));
* jadx.save();
* </code></pre>
* <p/>
* Instead of 'save()' you can get list of decompiled classes:
* <pre><code>
* for(JavaClass cls : jadx.getClasses()) {
* System.out.println(cls.getCode());
* }
* </code></pre>
*/
public final class Decompiler {
private static final Logger LOG = LoggerFactory.getLogger(Decompiler.class);
private final IJadxArgs args;
private final List<InputFile> inputFiles = new ArrayList<InputFile>();
private File outDir;
private RootNode root;
private List<IDexTreeVisitor> passes;
private List<JavaClass> classes;
public Decompiler() {
this.args = new DefaultJadxArgs();
init();
}
public Decompiler(IJadxArgs jadxArgs) {
this.args = jadxArgs;
init();
}
public void setOutputDir(File outDir) {
this.outDir = outDir;
init();
}
void init() {
if (outDir == null) {
outDir = new File("jadx-output");
}
this.passes = Jadx.getPassesList(args, outDir);
}
public void loadFile(File file) throws IOException, DecodeException {
loadFiles(Collections.singletonList(file));
}
public void loadFiles(List<File> files) throws IOException, DecodeException {
if (files.isEmpty()) {
throw new JadxRuntimeException("Empty file list");
}
inputFiles.clear();
for (File file : files) {
inputFiles.add(new InputFile(file));
}
parse();
}
public void save() {
try {
ExecutorService ex = getSaveExecutor();
ex.shutdown();
ex.awaitTermination(1, TimeUnit.DAYS);
} catch (InterruptedException e) {
LOG.error("Save interrupted", e);
}
}
public ThreadPoolExecutor getSaveExecutor() {
if (root == null) {
throw new JadxRuntimeException("No loaded files");
}
int threadsCount = args.getThreadsCount();
LOG.debug("processing threads count: {}", threadsCount);
final List<IDexTreeVisitor> passList = new ArrayList<IDexTreeVisitor>(passes);
SaveCode savePass = new SaveCode(outDir, args);
passList.add(savePass);
LOG.info("processing ...");
ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(threadsCount);
for (final ClassNode cls : root.getClasses(false)) {
executor.execute(new Runnable() {
@Override
public void run() {
ProcessClass.process(cls, passList);
}
});
}
return executor;
}
public List<JavaClass> getClasses() {
if (classes == null) {
List<ClassNode> classNodeList = root.getClasses(false);
List<JavaClass> clsList = new ArrayList<JavaClass>(classNodeList.size());
for (ClassNode classNode : classNodeList) {
clsList.add(new JavaClass(this, classNode));
}
classes = Collections.unmodifiableList(clsList);
}
return classes;
}
public List<JavaPackage> getPackages() {
Map<String, List<JavaClass>> map = new HashMap<String, List<JavaClass>>();
for (JavaClass javaClass : getClasses()) {
String pkg = javaClass.getPackage();
List<JavaClass> clsList = map.get(pkg);
if (clsList == null) {
clsList = new ArrayList<JavaClass>();
map.put(pkg, clsList);
}
clsList.add(javaClass);
}
List<JavaPackage> packages = new ArrayList<JavaPackage>(map.size());
for (Map.Entry<String, List<JavaClass>> entry : map.entrySet()) {
packages.add(new JavaPackage(entry.getKey(), entry.getValue()));
}
Collections.sort(packages);
for (JavaPackage pkg : packages) {
Collections.sort(pkg.getClasses(), new Comparator<JavaClass>() {
@Override
public int compare(JavaClass o1, JavaClass o2) {
return o1.getShortName().compareTo(o2.getShortName());
}
});
}
return Collections.unmodifiableList(packages);
}
public int getErrorsCount() {
return ErrorsCounter.getErrorCount();
}
void parse() throws DecodeException {
reset();
root = new RootNode();
LOG.info("loading ...");
root.load(inputFiles);
}
private void reset() {
ClassInfo.clearCache();
ErrorsCounter.reset();
classes = null;
}
void processClass(ClassNode cls) {
LOG.info("processing class {} ...", cls);
ProcessClass.process(cls, passes);
}
RootNode getRoot() {
return root;
}
JavaClass findJavaClass(ClassNode cls) {
if (cls == null) {
return null;
}
for (JavaClass javaClass : getClasses()) {
if (javaClass.getClassNode().equals(cls)) {
return javaClass;
}
}
return null;
}
}
@@ -1,29 +0,0 @@
package jadx.api;
public class DefaultJadxArgs implements IJadxArgs {
@Override
public int getThreadsCount() {
return Runtime.getRuntime().availableProcessors();
}
@Override
public boolean isCFGOutput() {
return false;
}
@Override
public boolean isRawCFGOutput() {
return false;
}
@Override
public boolean isFallbackMode() {
return false;
}
@Override
public boolean isVerbose() {
return false;
}
}
@@ -1,6 +1,10 @@
package jadx.api;
import java.io.File;
public interface IJadxArgs {
File getOutDir();
int getThreadsCount();
boolean isCFGOutput();
@@ -9,5 +13,33 @@ public interface IJadxArgs {
boolean isFallbackMode();
boolean isShowInconsistentCode();
boolean isVerbose();
boolean isSkipResources();
boolean isSkipSources();
boolean isDeobfuscationOn();
int getDeobfuscationMinLength();
int getDeobfuscationMaxLength();
boolean isDeobfuscationForceSave();
boolean useSourceNameAsClassAlias();
boolean escapeUnicode();
/**
* Replace constant values with static final fields with same value
*/
boolean isReplaceConsts();
/**
* Save as gradle project
*/
boolean isExportAsGradleProject();
}
@@ -0,0 +1,183 @@
package jadx.api;
import java.io.File;
public class JadxArgs implements IJadxArgs {
private File outDir = new File("jadx-output");
private int threadsCount = Math.max(1, Runtime.getRuntime().availableProcessors() - 1);
private boolean cfgOutput = false;
private boolean rawCFGOutput = false;
private boolean isVerbose = false;
private boolean fallbackMode = false;
private boolean showInconsistentCode = false;
private boolean isSkipResources = false;
private boolean isSkipSources = false;
private boolean isDeobfuscationOn = false;
private boolean isDeobfuscationForceSave = false;
private boolean useSourceNameAsClassAlias = false;
private int deobfuscationMinLength = 0;
private int deobfuscationMaxLength = Integer.MAX_VALUE;
private boolean escapeUnicode = false;
private boolean replaceConsts = true;
private boolean exportAsGradleProject = false;
@Override
public File getOutDir() {
return outDir;
}
public void setOutDir(File outDir) {
this.outDir = outDir;
}
@Override
public int getThreadsCount() {
return threadsCount;
}
public void setThreadsCount(int threadsCount) {
this.threadsCount = threadsCount;
}
@Override
public boolean isCFGOutput() {
return cfgOutput;
}
public void setCfgOutput(boolean cfgOutput) {
this.cfgOutput = cfgOutput;
}
@Override
public boolean isRawCFGOutput() {
return rawCFGOutput;
}
public void setRawCFGOutput(boolean rawCFGOutput) {
this.rawCFGOutput = rawCFGOutput;
}
@Override
public boolean isFallbackMode() {
return fallbackMode;
}
public void setFallbackMode(boolean fallbackMode) {
this.fallbackMode = fallbackMode;
}
@Override
public boolean isShowInconsistentCode() {
return showInconsistentCode;
}
public void setShowInconsistentCode(boolean showInconsistentCode) {
this.showInconsistentCode = showInconsistentCode;
}
@Override
public boolean isVerbose() {
return isVerbose;
}
public void setVerbose(boolean verbose) {
isVerbose = verbose;
}
@Override
public boolean isSkipResources() {
return isSkipResources;
}
public void setSkipResources(boolean skipResources) {
isSkipResources = skipResources;
}
@Override
public boolean isSkipSources() {
return isSkipSources;
}
public void setSkipSources(boolean skipSources) {
isSkipSources = skipSources;
}
@Override
public boolean isDeobfuscationOn() {
return isDeobfuscationOn;
}
public void setDeobfuscationOn(boolean deobfuscationOn) {
isDeobfuscationOn = deobfuscationOn;
}
@Override
public boolean isDeobfuscationForceSave() {
return isDeobfuscationForceSave;
}
public void setDeobfuscationForceSave(boolean deobfuscationForceSave) {
isDeobfuscationForceSave = deobfuscationForceSave;
}
@Override
public boolean useSourceNameAsClassAlias() {
return useSourceNameAsClassAlias;
}
public void setUseSourceNameAsClassAlias(boolean useSourceNameAsClassAlias) {
this.useSourceNameAsClassAlias = useSourceNameAsClassAlias;
}
@Override
public int getDeobfuscationMinLength() {
return deobfuscationMinLength;
}
public void setDeobfuscationMinLength(int deobfuscationMinLength) {
this.deobfuscationMinLength = deobfuscationMinLength;
}
@Override
public int getDeobfuscationMaxLength() {
return deobfuscationMaxLength;
}
public void setDeobfuscationMaxLength(int deobfuscationMaxLength) {
this.deobfuscationMaxLength = deobfuscationMaxLength;
}
@Override
public boolean escapeUnicode() {
return escapeUnicode;
}
public void setEscapeUnicode(boolean escapeUnicode) {
this.escapeUnicode = escapeUnicode;
}
@Override
public boolean isReplaceConsts() {
return replaceConsts;
}
public void setReplaceConsts(boolean replaceConsts) {
this.replaceConsts = replaceConsts;
}
@Override
public boolean isExportAsGradleProject() {
return exportAsGradleProject;
}
public void setExportAsGradleProject(boolean exportAsGradleProject) {
this.exportAsGradleProject = exportAsGradleProject;
}
}
@@ -0,0 +1,341 @@
package jadx.api;
import jadx.core.Jadx;
import jadx.core.ProcessClass;
import jadx.core.codegen.CodeGen;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.dex.visitors.SaveCode;
import jadx.core.export.ExportGradleProject;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.InputFile;
import jadx.core.xmlgen.BinaryXMLParser;
import jadx.core.xmlgen.ResourcesSaver;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Jadx API usage example:
* <pre><code>
* JadxDecompiler jadx = new JadxDecompiler();
* jadx.loadFile(new File("classes.dex"));
* jadx.setOutputDir(new File("out"));
* jadx.save();
* </code></pre>
* <p/>
* Instead of 'save()' you can get list of decompiled classes:
* <pre><code>
* for(JavaClass cls : jadx.getClasses()) {
* System.out.println(cls.getCode());
* }
* </code></pre>
*/
public final class JadxDecompiler {
private static final Logger LOG = LoggerFactory.getLogger(JadxDecompiler.class);
private final IJadxArgs args;
private final List<InputFile> inputFiles = new ArrayList<InputFile>();
private File outDir;
private RootNode root;
private List<IDexTreeVisitor> passes;
private CodeGen codeGen;
private List<JavaClass> classes;
private List<ResourceFile> resources;
private BinaryXMLParser xmlParser;
private Map<ClassNode, JavaClass> classesMap = new HashMap<ClassNode, JavaClass>();
private Map<MethodNode, JavaMethod> methodsMap = new HashMap<MethodNode, JavaMethod>();
private Map<FieldNode, JavaField> fieldsMap = new HashMap<FieldNode, JavaField>();
public JadxDecompiler() {
this(new JadxArgs());
}
public JadxDecompiler(IJadxArgs jadxArgs) {
this.args = jadxArgs;
this.outDir = jadxArgs.getOutDir();
reset();
init();
}
public void setOutputDir(File outDir) {
this.outDir = outDir;
init();
}
void init() {
if (outDir == null) {
outDir = new JadxArgs().getOutDir();
}
this.passes = Jadx.getPassesList(args, outDir);
this.codeGen = new CodeGen(args);
}
void reset() {
classes = null;
resources = null;
xmlParser = null;
root = null;
passes = null;
codeGen = null;
}
public static String getVersion() {
return Jadx.getVersion();
}
public void loadFile(File file) throws JadxException {
loadFiles(Collections.singletonList(file));
}
public void loadFiles(List<File> files) throws JadxException {
if (files.isEmpty()) {
throw new JadxException("Empty file list");
}
inputFiles.clear();
for (File file : files) {
try {
InputFile.addFilesFrom(file, inputFiles);
} catch (IOException e) {
throw new JadxException("Error load file: " + file, e);
}
}
parse();
}
public void save() {
save(!args.isSkipSources(), !args.isSkipResources());
}
public void saveSources() {
save(true, false);
}
public void saveResources() {
save(false, true);
}
private void save(boolean saveSources, boolean saveResources) {
try {
ExecutorService ex = getSaveExecutor(saveSources, saveResources);
ex.shutdown();
ex.awaitTermination(1, TimeUnit.DAYS);
} catch (InterruptedException e) {
throw new JadxRuntimeException("Save interrupted", e);
}
}
public ExecutorService getSaveExecutor() {
return getSaveExecutor(!args.isSkipSources(), !args.isSkipResources());
}
private ExecutorService getSaveExecutor(boolean saveSources, boolean saveResources) {
if (root == null) {
throw new JadxRuntimeException("No loaded files");
}
int threadsCount = args.getThreadsCount();
LOG.debug("processing threads count: {}", threadsCount);
LOG.info("processing ...");
ExecutorService executor = Executors.newFixedThreadPool(threadsCount);
File sourcesOutDir;
File resOutDir;
if (args.isExportAsGradleProject()) {
ExportGradleProject export = new ExportGradleProject(root, outDir);
export.init();
sourcesOutDir = export.getSrcOutDir();
resOutDir = export.getResOutDir();
} else {
sourcesOutDir = outDir;
resOutDir = outDir;
}
if (saveSources) {
appendSourcesSave(executor, sourcesOutDir);
}
if (saveResources) {
appendResourcesSave(executor, resOutDir);
}
return executor;
}
private void appendResourcesSave(ExecutorService executor, File outDir) {
for (ResourceFile resourceFile : getResources()) {
executor.execute(new ResourcesSaver(outDir, resourceFile));
}
}
private void appendSourcesSave(ExecutorService executor, final File outDir) {
for (final JavaClass cls : getClasses()) {
if (cls.getClassNode().contains(AFlag.DONT_GENERATE)) {
continue;
}
executor.execute(new Runnable() {
@Override
public void run() {
cls.decompile();
SaveCode.save(outDir, args, cls.getClassNode());
}
});
}
}
public List<JavaClass> getClasses() {
if (root == null) {
return Collections.emptyList();
}
if (classes == null) {
List<ClassNode> classNodeList = root.getClasses(false);
List<JavaClass> clsList = new ArrayList<JavaClass>(classNodeList.size());
classesMap.clear();
for (ClassNode classNode : classNodeList) {
JavaClass javaClass = new JavaClass(classNode, this);
clsList.add(javaClass);
classesMap.put(classNode, javaClass);
}
classes = Collections.unmodifiableList(clsList);
}
return classes;
}
public List<ResourceFile> getResources() {
if (resources == null) {
if (root == null) {
return Collections.emptyList();
}
resources = new ResourcesLoader(this).load(inputFiles);
}
return resources;
}
public List<JavaPackage> getPackages() {
List<JavaClass> classList = getClasses();
if (classList.isEmpty()) {
return Collections.emptyList();
}
Map<String, List<JavaClass>> map = new HashMap<String, List<JavaClass>>();
for (JavaClass javaClass : classList) {
String pkg = javaClass.getPackage();
List<JavaClass> clsList = map.get(pkg);
if (clsList == null) {
clsList = new ArrayList<JavaClass>();
map.put(pkg, clsList);
}
clsList.add(javaClass);
}
List<JavaPackage> packages = new ArrayList<JavaPackage>(map.size());
for (Map.Entry<String, List<JavaClass>> entry : map.entrySet()) {
packages.add(new JavaPackage(entry.getKey(), entry.getValue()));
}
Collections.sort(packages);
for (JavaPackage pkg : packages) {
Collections.sort(pkg.getClasses(), new Comparator<JavaClass>() {
@Override
public int compare(JavaClass o1, JavaClass o2) {
return o1.getName().compareTo(o2.getName());
}
});
}
return Collections.unmodifiableList(packages);
}
public int getErrorsCount() {
if (root == null) {
return 0;
}
return root.getErrorsCounter().getErrorCount();
}
public void printErrorsReport() {
if (root == null) {
return;
}
root.getClsp().printMissingClasses();
root.getErrorsCounter().printReport();
}
void parse() throws DecodeException {
reset();
init();
root = new RootNode(args);
LOG.info("loading ...");
root.load(inputFiles);
root.initClassPath();
root.loadResources(getResources());
root.initAppResClass();
initVisitors();
}
private void initVisitors() {
for (IDexTreeVisitor pass : passes) {
try {
pass.init(root);
} catch (Exception e) {
LOG.error("Visitor init failed: {}", pass.getClass().getSimpleName(), e);
}
}
}
void processClass(ClassNode cls) {
ProcessClass.process(cls, passes, codeGen);
}
RootNode getRoot() {
return root;
}
synchronized BinaryXMLParser getXmlParser() {
if (xmlParser == null) {
xmlParser = new BinaryXMLParser(root);
}
return xmlParser;
}
Map<ClassNode, JavaClass> getClassesMap() {
return classesMap;
}
Map<MethodNode, JavaMethod> getMethodsMap() {
return methodsMap;
}
Map<FieldNode, JavaField> getFieldsMap() {
return fieldsMap;
}
public IJadxArgs getArgs() {
return args;
}
@Override
public String toString() {
return "jadx decompiler " + getVersion();
}
}
+132 -41
View File
@@ -1,32 +1,45 @@
package jadx.api;
import jadx.core.codegen.CodeWriter;
import jadx.core.dex.attributes.AttributeFlag;
import jadx.core.dex.attributes.LineAttrNode;
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 jadx.core.utils.exceptions.JadxRuntimeException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public final class JavaClass {
import org.jetbrains.annotations.Nullable;
private final Decompiler decompiler;
public final class JavaClass implements JavaNode {
private final JadxDecompiler decompiler;
private final ClassNode cls;
private final JavaClass parent;
private List<JavaClass> innerClasses = Collections.emptyList();
private List<JavaField> fields = Collections.emptyList();
private List<JavaMethod> methods = Collections.emptyList();
JavaClass(Decompiler decompiler, ClassNode classNode) {
JavaClass(ClassNode classNode, JadxDecompiler decompiler) {
this.decompiler = decompiler;
this.cls = classNode;
this.parent = null;
}
/**
* Inner classes constructor
*/
JavaClass(ClassNode classNode, JavaClass parent) {
this.decompiler = null;
this.cls = classNode;
this.parent = parent;
}
public String getCode() {
@@ -34,13 +47,16 @@ public final class JavaClass {
if (code == null) {
decompile();
code = cls.getCode();
if (code == null) {
return "";
}
}
return code != null ? code.toString() : "error processing class";
return code.getCodeStr();
}
public void decompile() {
public synchronized void decompile() {
if (decompiler == null) {
throw new JadxRuntimeException("Can't decompile inner class");
return;
}
if (cls.getCode() == null) {
decompiler.processClass(cls);
@@ -53,14 +69,16 @@ public final class JavaClass {
}
private void load() {
JadxDecompiler rootDecompiler = getRootDecompiler();
int inClsCount = cls.getInnerClasses().size();
if (inClsCount != 0) {
List<JavaClass> list = new ArrayList<JavaClass>(inClsCount);
for (ClassNode inner : cls.getInnerClasses()) {
if (!inner.getAttributes().contains(AttributeFlag.DONT_GENERATE)) {
JavaClass javaClass = new JavaClass(null, inner);
if (!inner.contains(AFlag.DONT_GENERATE)) {
JavaClass javaClass = new JavaClass(inner, this);
javaClass.load();
list.add(javaClass);
rootDecompiler.getClassesMap().put(inner, javaClass);
}
}
this.innerClasses = Collections.unmodifiableList(list);
@@ -70,8 +88,10 @@ public final class JavaClass {
if (fieldsCount != 0) {
List<JavaField> flds = new ArrayList<JavaField>(fieldsCount);
for (FieldNode f : cls.getFields()) {
if (!f.getAttributes().contains(AttributeFlag.DONT_GENERATE)) {
flds.add(new JavaField(f));
if (!f.contains(AFlag.DONT_GENERATE)) {
JavaField javaField = new JavaField(f, this);
flds.add(javaField);
rootDecompiler.getFieldsMap().put(f, javaField);
}
}
this.fields = Collections.unmodifiableList(flds);
@@ -81,8 +101,10 @@ public final class JavaClass {
if (methodsCount != 0) {
List<JavaMethod> mths = new ArrayList<JavaMethod>(methodsCount);
for (MethodNode m : cls.getMethods()) {
if (!m.getAttributes().contains(AttributeFlag.DONT_GENERATE)) {
mths.add(new JavaMethod(this, m));
if (!m.contains(AFlag.DONT_GENERATE)) {
JavaMethod javaMethod = new JavaMethod(this, m);
mths.add(javaMethod);
rootDecompiler.getMethodsMap().put(m, javaMethod);
}
}
Collections.sort(mths, new Comparator<JavaMethod>() {
@@ -95,63 +117,132 @@ public final class JavaClass {
}
}
private JadxDecompiler getRootDecompiler() {
if (parent != null) {
return parent.getRootDecompiler();
}
return decompiler;
}
private Map<CodePosition, Object> getCodeAnnotations() {
decompile();
return cls.getCode().getAnnotations();
}
public CodePosition getDefinitionPosition(int line, int offset) {
public Map<CodePosition, JavaNode> getUsageMap() {
Map<CodePosition, Object> map = getCodeAnnotations();
Object obj = map.get(new CodePosition(line, offset));
if (obj instanceof LineAttrNode) {
ClassNode clsNode = null;
if (obj instanceof ClassNode) {
clsNode = (ClassNode) obj;
} else if (obj instanceof MethodNode) {
clsNode = ((MethodNode) obj).getParentClass();
} else if (obj instanceof FieldNode) {
clsNode = ((FieldNode) obj).getParentClass();
if (map.isEmpty() || decompiler == null) {
return Collections.emptyMap();
}
Map<CodePosition, JavaNode> resultMap = new HashMap<CodePosition, JavaNode>(map.size());
for (Map.Entry<CodePosition, Object> entry : map.entrySet()) {
CodePosition codePosition = entry.getKey();
Object obj = entry.getValue();
if (obj instanceof LineAttrNode) {
JavaNode node = convertNode(obj);
if (node != null) {
resultMap.put(codePosition, node);
}
}
if (clsNode == null) {
return null;
}
clsNode = clsNode.getParentClass();
JavaClass jCls = decompiler.findJavaClass(clsNode);
if (jCls == null) {
return null;
}
jCls.decompile();
int defLine = ((LineAttrNode) obj).getDecompiledLine();
return new CodePosition(jCls, defLine, 0);
}
return resultMap;
}
@Nullable
private JavaNode convertNode(Object obj) {
if (!(obj instanceof LineAttrNode)) {
return null;
}
if (obj instanceof ClassNode) {
return getRootDecompiler().getClassesMap().get(obj);
}
if (obj instanceof MethodNode) {
return getRootDecompiler().getMethodsMap().get(obj);
}
if (obj instanceof FieldNode) {
return getRootDecompiler().getFieldsMap().get(obj);
}
return null;
}
@Nullable
public JavaNode getJavaNodeAtPosition(int line, int offset) {
Map<CodePosition, Object> map = getCodeAnnotations();
if (map.isEmpty()) {
return null;
}
Object obj = map.get(new CodePosition(line, offset));
if (obj == null) {
return null;
}
return convertNode(obj);
}
@Nullable
public CodePosition getDefinitionPosition(int line, int offset) {
JavaNode javaNode = getJavaNodeAtPosition(line, offset);
if (javaNode == null) {
return null;
}
return getDefinitionPosition(javaNode);
}
@Nullable
public CodePosition getDefinitionPosition(JavaNode javaNode) {
JavaClass jCls = javaNode.getTopParentClass();
jCls.decompile();
int defLine = javaNode.getDecompiledLine();
if (defLine == 0) {
return null;
}
return new CodePosition(jCls, defLine, 0);
}
public Integer getSourceLine(int decompiledLine) {
decompile();
return cls.getCode().getLineMapping().get(decompiledLine);
}
@Override
public String getName() {
return cls.getShortName();
}
@Override
public String getFullName() {
return cls.getFullName();
}
public String getShortName() {
return cls.getShortName();
}
public String getPackage() {
return cls.getPackage();
}
@Override
public JavaClass getDeclaringClass() {
return parent;
}
@Override
public JavaClass getTopParentClass() {
return parent == null ? this : parent.getTopParentClass();
}
public AccessInfo getAccessInfo() {
return cls.getAccessFlags();
}
public List<JavaClass> getInnerClasses() {
decompile();
return innerClasses;
}
public List<JavaField> getFields() {
decompile();
return fields;
}
public List<JavaMethod> getMethods() {
decompile();
return methods;
}
@@ -171,6 +262,6 @@ public final class JavaClass {
@Override
public String toString() {
return getFullName();
return cls.getFullName() + "[ " + getFullName() + " ]";
}
}
@@ -4,16 +4,34 @@ import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.FieldNode;
public final class JavaField {
public final class JavaField implements JavaNode {
private final FieldNode field;
private final JavaClass parent;
public JavaField(FieldNode f) {
JavaField(FieldNode f, JavaClass cls) {
this.field = f;
this.parent = cls;
}
@Override
public String getName() {
return field.getName();
return field.getAlias();
}
@Override
public String getFullName() {
return parent.getFullName() + "." + getName();
}
@Override
public JavaClass getDeclaringClass() {
return parent;
}
@Override
public JavaClass getTopParentClass() {
return parent.getTopParentClass();
}
public AccessInfo getAccessFlags() {
@@ -27,4 +45,19 @@ public final class JavaField {
public int getDecompiledLine() {
return field.getDecompiledLine();
}
@Override
public int hashCode() {
return field.hashCode();
}
@Override
public boolean equals(Object o) {
return this == o || o instanceof JavaField && field.equals(((JavaField) o).field);
}
@Override
public String toString() {
return field.toString();
}
}
@@ -6,23 +6,35 @@ import jadx.core.dex.nodes.MethodNode;
import java.util.List;
public final class JavaMethod {
public final class JavaMethod implements JavaNode {
private final MethodNode mth;
private final JavaClass parent;
public JavaMethod(JavaClass cls, MethodNode m) {
JavaMethod(JavaClass cls, MethodNode m) {
this.parent = cls;
this.mth = m;
}
@Override
public String getName() {
return mth.getMethodInfo().getName();
return mth.getAlias();
}
@Override
public String getFullName() {
return mth.getMethodInfo().getFullName();
}
@Override
public JavaClass getDeclaringClass() {
return parent;
}
@Override
public JavaClass getTopParentClass() {
return parent.getTopParentClass();
}
public AccessInfo getAccessFlags() {
return mth.getAccessFlags();
}
@@ -46,4 +58,19 @@ public final class JavaMethod {
public int getDecompiledLine() {
return mth.getDecompiledLine();
}
@Override
public int hashCode() {
return mth.hashCode();
}
@Override
public boolean equals(Object o) {
return this == o || o instanceof JavaMethod && mth.equals(((JavaMethod) o).mth);
}
@Override
public String toString() {
return mth.toString();
}
}
@@ -0,0 +1,14 @@
package jadx.api;
public interface JavaNode {
String getName();
String getFullName();
JavaClass getDeclaringClass();
JavaClass getTopParentClass();
int getDecompiledLine();
}
@@ -2,7 +2,9 @@ package jadx.api;
import java.util.List;
public final class JavaPackage implements Comparable<JavaPackage> {
import org.jetbrains.annotations.NotNull;
public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
private final String name;
private final List<JavaClass> classes;
@@ -11,16 +13,38 @@ public final class JavaPackage implements Comparable<JavaPackage> {
this.classes = classes;
}
@Override
public String getName() {
return name;
}
@Override
public String getFullName() {
// TODO: store full package name
return name;
}
public List<JavaClass> getClasses() {
return classes;
}
@Override
public int compareTo(JavaPackage o) {
public JavaClass getDeclaringClass() {
return null;
}
@Override
public JavaClass getTopParentClass() {
return null;
}
@Override
public int getDecompiledLine() {
return 0;
}
@Override
public int compareTo(@NotNull JavaPackage o) {
return name.compareTo(o.name);
}
@@ -0,0 +1,67 @@
package jadx.api;
import jadx.core.xmlgen.ResContainer;
import java.io.File;
public class ResourceFile {
public static final class ZipRef {
private final File zipFile;
private final String entryName;
public ZipRef(File zipFile, String entryName) {
this.zipFile = zipFile;
this.entryName = entryName;
}
public File getZipFile() {
return zipFile;
}
public String getEntryName() {
return entryName;
}
@Override
public String toString() {
return "ZipRef{" + zipFile + ", '" + entryName + "'}";
}
}
private final JadxDecompiler decompiler;
private final String name;
private final ResourceType type;
private ZipRef zipRef;
ResourceFile(JadxDecompiler decompiler, String name, ResourceType type) {
this.decompiler = decompiler;
this.name = name;
this.type = type;
}
public String getName() {
return name;
}
public ResourceType getType() {
return type;
}
public ResContainer loadContent() {
return ResourcesLoader.loadContent(decompiler, this);
}
void setZipRef(ZipRef zipRef) {
this.zipRef = zipRef;
}
ZipRef getZipRef() {
return zipRef;
}
@Override
public String toString() {
return "ResourceFile{name='" + name + '\'' + ", type=" + type + "}";
}
}
@@ -0,0 +1,19 @@
package jadx.api;
import jadx.core.codegen.CodeWriter;
import jadx.core.xmlgen.ResContainer;
public class ResourceFileContent extends ResourceFile {
private final CodeWriter content;
public ResourceFileContent(String name, ResourceType type, CodeWriter content) {
super(null, name, type);
this.content = content;
}
@Override
public ResContainer loadContent() {
return ResContainer.singleFile(getName(), content);
}
}
@@ -0,0 +1,50 @@
package jadx.api;
public enum ResourceType {
CODE(".dex", ".jar", ".class"),
MANIFEST("AndroidManifest.xml"),
XML(".xml"),
ARSC(".arsc"),
FONT(".ttf"),
IMG(".png", ".gif", ".jpg"),
LIB(".so"),
UNKNOWN;
private final String[] exts;
ResourceType(String... exts) {
this.exts = exts;
}
public String[] getExts() {
return exts;
}
public static ResourceType getFileType(String fileName) {
for (ResourceType type : ResourceType.values()) {
for (String ext : type.getExts()) {
if (fileName.endsWith(ext)) {
return type;
}
}
}
return UNKNOWN;
}
public static boolean isSupportedForUnpack(ResourceType type) {
switch (type) {
case CODE:
case LIB:
case FONT:
case UNKNOWN:
return false;
case MANIFEST:
case XML:
case ARSC:
case IMG:
return true;
}
return false;
}
}
@@ -0,0 +1,165 @@
package jadx.api;
import jadx.api.ResourceFile.ZipRef;
import jadx.core.codegen.CodeWriter;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.files.InputFile;
import jadx.core.xmlgen.ResContainer;
import jadx.core.xmlgen.ResTableParser;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static jadx.core.utils.files.FileUtils.READ_BUFFER_SIZE;
import static jadx.core.utils.files.FileUtils.close;
import static jadx.core.utils.files.FileUtils.copyStream;
// TODO: move to core package
public final class ResourcesLoader {
private static final Logger LOG = LoggerFactory.getLogger(ResourcesLoader.class);
private static final int LOAD_SIZE_LIMIT = 10 * 1024 * 1024;
private final JadxDecompiler jadxRef;
ResourcesLoader(JadxDecompiler jadxRef) {
this.jadxRef = jadxRef;
}
List<ResourceFile> load(List<InputFile> inputFiles) {
List<ResourceFile> list = new ArrayList<ResourceFile>(inputFiles.size());
for (InputFile file : inputFiles) {
loadFile(list, file.getFile());
}
return list;
}
public interface ResourceDecoder {
ResContainer decode(long size, InputStream is) throws IOException;
}
public static ResContainer decodeStream(ResourceFile rf, ResourceDecoder decoder) throws JadxException {
ZipRef zipRef = rf.getZipRef();
if (zipRef == null) {
return null;
}
ZipFile zipFile = null;
InputStream inputStream = null;
ResContainer result = null;
try {
zipFile = new ZipFile(zipRef.getZipFile());
ZipEntry entry = zipFile.getEntry(zipRef.getEntryName());
if (entry == null) {
throw new IOException("Zip entry not found: " + zipRef);
}
inputStream = new BufferedInputStream(zipFile.getInputStream(entry));
result = decoder.decode(entry.getSize(), inputStream);
} catch (Exception e) {
throw new JadxException("Error decode: " + zipRef.getEntryName(), e);
} finally {
try {
if (zipFile != null) {
zipFile.close();
}
} catch (Exception e) {
LOG.error("Error close zip file: {}", zipRef, e);
}
close(inputStream);
}
return result;
}
static ResContainer loadContent(final JadxDecompiler jadxRef, final ResourceFile rf) {
try {
return decodeStream(rf, new ResourceDecoder() {
@Override
public ResContainer decode(long size, InputStream is) throws IOException {
return loadContent(jadxRef, rf, is, size);
}
});
} catch (JadxException e) {
LOG.error("Decode error", e);
CodeWriter cw = new CodeWriter();
cw.add("Error decode ").add(rf.getType().toString().toLowerCase());
cw.startLine(Utils.getStackTrace(e.getCause()));
return ResContainer.singleFile(rf.getName(), cw);
}
}
private static ResContainer loadContent(JadxDecompiler jadxRef, ResourceFile rf,
InputStream inputStream, long size) throws IOException {
switch (rf.getType()) {
case MANIFEST:
case XML:
return ResContainer.singleFile(rf.getName(),
jadxRef.getXmlParser().parse(inputStream));
case ARSC:
return new ResTableParser().decodeFiles(inputStream);
case IMG:
return ResContainer.singleImageFile(rf.getName(), inputStream);
}
if (size > LOAD_SIZE_LIMIT) {
return ResContainer.singleFile(rf.getName(),
new CodeWriter().add("File too big, size: " + String.format("%.2f KB", size / 1024.)));
}
return ResContainer.singleFile(rf.getName(), loadToCodeWriter(inputStream));
}
private void loadFile(List<ResourceFile> list, File file) {
if (file == null) {
return;
}
ZipFile zip = null;
try {
zip = new ZipFile(file);
Enumeration<? extends ZipEntry> entries = zip.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
addEntry(list, file, entry);
}
} catch (IOException e) {
LOG.debug("Not a zip file: {}", file.getAbsolutePath());
} finally {
if (zip != null) {
try {
zip.close();
} catch (Exception e) {
LOG.error("Zip file close error: {}", file.getAbsolutePath(), e);
}
}
}
}
private void addEntry(List<ResourceFile> list, File zipFile, ZipEntry entry) {
if (entry.isDirectory()) {
return;
}
String name = entry.getName();
ResourceType type = ResourceType.getFileType(name);
ResourceFile rf = new ResourceFile(jadxRef, name, type);
rf.setZipRef(new ZipRef(zipFile, name));
list.add(rf);
}
public static CodeWriter loadToCodeWriter(InputStream is) throws IOException {
CodeWriter cw = new CodeWriter();
ByteArrayOutputStream baos = new ByteArrayOutputStream(READ_BUFFER_SIZE);
copyStream(is, baos);
cw.add(baos.toString("UTF-8"));
return cw;
}
}
@@ -1,8 +1,6 @@
package jadx.core;
public class Consts {
public static final String JADX_VERSION = Jadx.getVersion();
public static final boolean DEBUG = false;
public static final String CLASS_OBJECT = "java.lang.Object";
@@ -20,7 +18,7 @@ public class Consts {
public static final String DALVIK_ANNOTATION_DEFAULT = "dalvik.annotation.AnnotationDefault";
public static final String DEFAULT_PACKAGE_NAME = "defpackage";
public static final String ANONYMOUS_CLASS_PREFIX = "AnonymousClass_";
public static final String ANONYMOUS_CLASS_PREFIX = "AnonymousClass";
public static final String MTH_TOSTRING_SIGNATURE = "toString()Ljava/lang/String;";
}
+55 -30
View File
@@ -1,29 +1,39 @@
package jadx.core;
import jadx.api.IJadxArgs;
import jadx.core.codegen.CodeGen;
import jadx.core.dex.visitors.BlockMakerVisitor;
import jadx.core.dex.visitors.ClassModifier;
import jadx.core.dex.visitors.CodeShrinker;
import jadx.core.dex.visitors.ConstInlinerVisitor;
import jadx.core.dex.visitors.ConstInlineVisitor;
import jadx.core.dex.visitors.DebugInfoVisitor;
import jadx.core.dex.visitors.DependencyCollector;
import jadx.core.dex.visitors.DotGraphVisitor;
import jadx.core.dex.visitors.EnumVisitor;
import jadx.core.dex.visitors.ExtractFieldInit;
import jadx.core.dex.visitors.FallbackModeVisitor;
import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.dex.visitors.MethodInlineVisitor;
import jadx.core.dex.visitors.ModVisitor;
import jadx.core.dex.visitors.PrepareForCodeGen;
import jadx.core.dex.visitors.ReSugarCode;
import jadx.core.dex.visitors.RenameVisitor;
import jadx.core.dex.visitors.SimplifyVisitor;
import jadx.core.dex.visitors.blocksmaker.BlockExceptionHandler;
import jadx.core.dex.visitors.blocksmaker.BlockFinallyExtract;
import jadx.core.dex.visitors.blocksmaker.BlockFinish;
import jadx.core.dex.visitors.blocksmaker.BlockProcessor;
import jadx.core.dex.visitors.blocksmaker.BlockSplitter;
import jadx.core.dex.visitors.regions.CheckRegions;
import jadx.core.dex.visitors.regions.IfRegionVisitor;
import jadx.core.dex.visitors.regions.LoopRegionVisitor;
import jadx.core.dex.visitors.regions.ProcessVariables;
import jadx.core.dex.visitors.regions.RegionMakerVisitor;
import jadx.core.dex.visitors.typeresolver.FinishTypeResolver;
import jadx.core.dex.visitors.typeresolver.TypeResolver;
import jadx.core.utils.Utils;
import jadx.core.dex.visitors.regions.ReturnVisitor;
import jadx.core.dex.visitors.ssa.EliminatePhiNodes;
import jadx.core.dex.visitors.ssa.SSATransform;
import jadx.core.dex.visitors.typeinference.FinishTypeInference;
import jadx.core.dex.visitors.typeinference.TypeInference;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
@@ -40,9 +50,6 @@ public class Jadx {
if (Consts.DEBUG) {
LOG.info("debug enabled");
}
if (Jadx.class.desiredAssertionStatus()) {
LOG.info("assertions enabled");
}
}
public static List<IDexTreeVisitor> getPassesList(IJadxArgs args, File outDir) {
@@ -50,56 +57,74 @@ public class Jadx {
if (args.isFallbackMode()) {
passes.add(new FallbackModeVisitor());
} else {
passes.add(new BlockMakerVisitor());
passes.add(new BlockSplitter());
passes.add(new BlockProcessor());
passes.add(new BlockExceptionHandler());
passes.add(new BlockFinallyExtract());
passes.add(new BlockFinish());
passes.add(new TypeResolver());
passes.add(new SSATransform());
passes.add(new DebugInfoVisitor());
passes.add(new TypeInference());
if (args.isRawCFGOutput()) {
passes.add(new DotGraphVisitor(outDir, false, true));
passes.add(DotGraphVisitor.dumpRaw(outDir));
}
passes.add(new ConstInlinerVisitor());
passes.add(new FinishTypeResolver());
passes.add(new ConstInlineVisitor());
passes.add(new FinishTypeInference());
passes.add(new EliminatePhiNodes());
passes.add(new ModVisitor());
passes.add(new EnumVisitor());
if (args.isCFGOutput()) {
passes.add(new DotGraphVisitor(outDir, false));
}
passes.add(new CodeShrinker());
passes.add(new ReSugarCode());
if (args.isCFGOutput()) {
passes.add(DotGraphVisitor.dump(outDir));
}
passes.add(new RegionMakerVisitor());
passes.add(new IfRegionVisitor());
passes.add(new ReturnVisitor());
passes.add(new CodeShrinker());
passes.add(new SimplifyVisitor());
passes.add(new ProcessVariables());
passes.add(new CheckRegions());
if (args.isCFGOutput()) {
passes.add(new DotGraphVisitor(outDir, true));
passes.add(DotGraphVisitor.dumpRegions(outDir));
}
passes.add(new MethodInlineVisitor());
passes.add(new ExtractFieldInit());
passes.add(new ClassModifier());
passes.add(new EnumVisitor());
passes.add(new PrepareForCodeGen());
passes.add(new LoopRegionVisitor());
passes.add(new ProcessVariables());
passes.add(new DependencyCollector());
passes.add(new RenameVisitor());
}
passes.add(new CodeGen(args));
return passes;
}
public static String getVersion() {
try {
Enumeration<URL> resources = Utils.class.getClassLoader().getResources("META-INF/MANIFEST.MF");
while (resources.hasMoreElements()) {
Manifest manifest = new Manifest(resources.nextElement().openStream());
String ver = manifest.getMainAttributes().getValue("jadx-version");
if (ver != null) {
return ver;
ClassLoader classLoader = Jadx.class.getClassLoader();
if (classLoader != null) {
Enumeration<URL> resources = classLoader.getResources("META-INF/MANIFEST.MF");
while (resources.hasMoreElements()) {
Manifest manifest = new Manifest(resources.nextElement().openStream());
String ver = manifest.getMainAttributes().getValue("jadx-version");
if (ver != null) {
return ver;
}
}
}
} catch (IOException e) {
} catch (Exception e) {
LOG.error("Can't get manifest file", e);
}
return "dev";
@@ -1,33 +1,62 @@
package jadx.core;
import jadx.core.codegen.CodeGen;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.visitors.DepthTraversal;
import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.ErrorsCounter;
import java.util.List;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static jadx.core.dex.nodes.ProcessState.GENERATED;
import static jadx.core.dex.nodes.ProcessState.NOT_LOADED;
import static jadx.core.dex.nodes.ProcessState.PROCESSED;
import static jadx.core.dex.nodes.ProcessState.STARTED;
import static jadx.core.dex.nodes.ProcessState.UNLOADED;
public final class ProcessClass {
private static final Logger LOG = LoggerFactory.getLogger(ProcessClass.class);
private ProcessClass() {
}
public static void process(ClassNode cls, List<IDexTreeVisitor> passes) {
try {
cls.load();
for (IDexTreeVisitor visitor : passes) {
DepthTraversal.visit(visitor, cls);
public static void process(ClassNode cls, List<IDexTreeVisitor> passes, @Nullable CodeGen codeGen) {
if (codeGen == null && cls.getState() == PROCESSED) {
return;
}
synchronized (cls) {
try {
if (cls.getState() == NOT_LOADED) {
cls.load();
cls.setState(STARTED);
for (IDexTreeVisitor visitor : passes) {
DepthTraversal.visit(visitor, cls);
}
cls.setState(PROCESSED);
}
if (cls.getState() == PROCESSED && codeGen != null) {
processDependencies(cls, passes);
codeGen.visit(cls);
cls.setState(GENERATED);
}
} catch (Exception e) {
ErrorsCounter.classError(cls, e.getClass().getSimpleName(), e);
} finally {
if (cls.getState() == GENERATED) {
cls.unload();
cls.setState(UNLOADED);
}
}
} catch (DecodeException e) {
LOG.error("Decode exception: " + cls, e);
} catch (Exception e) {
LOG.error("Class process exception: " + cls, e);
} finally {
cls.unload();
}
}
static void processDependencies(ClassNode cls, List<IDexTreeVisitor> passes) {
for (ClassNode depCls : cls.getDependencies()) {
process(depCls, passes, null);
}
}
}
@@ -1,11 +1,11 @@
package jadx.core.clsp;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.FileUtils;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
@@ -27,6 +27,8 @@ import java.util.zip.ZipOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static jadx.core.utils.files.FileUtils.close;
/**
* Classes list for import into classpath graph
*/
@@ -65,6 +67,9 @@ public class ClsSet {
for (ClassNode cls : list) {
if (cls.getAccessFlags().isPublic()) {
NClass nClass = getCls(cls.getRawName(), names);
if (nClass == null) {
throw new JadxRuntimeException("Missing class: " + cls);
}
nClass.setParents(makeParentsArray(cls, names));
classes[k] = nClass;
k++;
@@ -74,15 +79,15 @@ public class ClsSet {
public static NClass[] makeParentsArray(ClassNode cls, Map<String, NClass> names) {
List<NClass> parents = new ArrayList<NClass>(1 + cls.getInterfaces().size());
ClassInfo superClass = cls.getSuperClass();
ArgType superClass = cls.getSuperClass();
if (superClass != null) {
NClass c = getCls(superClass.getRawName(), names);
NClass c = getCls(superClass.getObject(), names);
if (c != null) {
parents.add(c);
}
}
for (ClassInfo iface : cls.getInterfaces()) {
NClass c = getCls(iface.getRawName(), names);
for (ArgType iface : cls.getInterfaces()) {
NClass c = getCls(iface.getObject(), names);
if (c != null) {
parents.add(c);
}
@@ -93,49 +98,55 @@ public class ClsSet {
private static NClass getCls(String fullName, Map<String, NClass> names) {
NClass id = names.get(fullName);
if (id == null && !names.containsKey(fullName)) {
LOG.warn("Class not found: " + fullName);
LOG.debug("Class not found: {}", fullName);
}
return id;
}
void save(File output) throws IOException {
Utils.makeDirsForFile(output);
FileUtils.makeDirsForFile(output);
BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(output));
String outputName = output.getName();
if (outputName.endsWith(CLST_EXTENSION)) {
save(outputStream);
} else if (outputName.endsWith(".jar")) {
ZipOutputStream out = new ZipOutputStream(outputStream);
try {
out.putNextEntry(new ZipEntry(CLST_PKG_PATH + "/" + CLST_FILENAME));
save(out);
out.closeEntry();
} finally {
out.close();
outputStream.close();
try {
String outputName = output.getName();
if (outputName.endsWith(CLST_EXTENSION)) {
save(outputStream);
} else if (outputName.endsWith(".jar")) {
ZipOutputStream out = new ZipOutputStream(outputStream);
try {
out.putNextEntry(new ZipEntry(CLST_PKG_PATH + "/" + CLST_FILENAME));
save(out);
} finally {
close(out);
}
} else {
throw new JadxRuntimeException("Unknown file format: " + outputName);
}
} else {
throw new JadxRuntimeException("Unknown file format: " + outputName);
} finally {
close(outputStream);
}
}
public void save(OutputStream output) throws IOException {
DataOutputStream out = new DataOutputStream(output);
out.writeBytes(JADX_CLS_SET_HEADER);
out.writeByte(VERSION);
try {
out.writeBytes(JADX_CLS_SET_HEADER);
out.writeByte(VERSION);
LOG.info("Classes count: " + classes.length);
out.writeInt(classes.length);
for (NClass cls : classes) {
writeString(out, cls.getName());
}
for (NClass cls : classes) {
NClass[] parents = cls.getParents();
out.writeByte(parents.length);
for (NClass parent : parents) {
out.writeInt(parent.getId());
LOG.info("Classes count: {}", classes.length);
out.writeInt(classes.length);
for (NClass cls : classes) {
writeString(out, cls.getName());
}
for (NClass cls : classes) {
NClass[] parents = cls.getParents();
out.writeByte(parents.length);
for (NClass parent : parents) {
out.writeInt(parent.getId());
}
}
} finally {
close(out);
}
}
@@ -144,55 +155,67 @@ public class ClsSet {
if (input == null) {
throw new JadxRuntimeException("Can't load classpath file: " + CLST_FILENAME);
}
load(input);
try {
load(input);
} finally {
close(input);
}
}
public void load(File input) throws IOException, DecodeException {
String name = input.getName();
InputStream inputStream = new FileInputStream(input);
if (name.endsWith(CLST_EXTENSION)) {
load(inputStream);
} else if (name.endsWith(".jar")) {
ZipInputStream in = new ZipInputStream(inputStream);
try {
ZipEntry entry = in.getNextEntry();
while (entry != null) {
if (entry.getName().endsWith(CLST_EXTENSION)) {
load(in);
try {
if (name.endsWith(CLST_EXTENSION)) {
load(inputStream);
} else if (name.endsWith(".jar")) {
ZipInputStream in = new ZipInputStream(inputStream);
try {
ZipEntry entry = in.getNextEntry();
while (entry != null) {
if (entry.getName().endsWith(CLST_EXTENSION)) {
load(in);
}
entry = in.getNextEntry();
}
entry = in.getNextEntry();
} finally {
close(in);
}
} finally {
in.close();
} else {
throw new JadxRuntimeException("Unknown file format: " + name);
}
} else {
throw new JadxRuntimeException("Unknown file format: " + name);
} finally {
close(inputStream);
}
}
public void load(InputStream input) throws IOException, DecodeException {
DataInputStream in = new DataInputStream(input);
byte[] header = new byte[JADX_CLS_SET_HEADER.length()];
int readHeaderLength = in.read(header);
int version = in.readByte();
if (readHeaderLength != JADX_CLS_SET_HEADER.length()
|| !JADX_CLS_SET_HEADER.equals(new String(header, STRING_CHARSET))
|| version != VERSION) {
throw new DecodeException("Wrong jadx class set header");
}
int count = in.readInt();
classes = new NClass[count];
for (int i = 0; i < count; i++) {
String name = readString(in);
classes[i] = new NClass(name, i);
}
for (int i = 0; i < count; i++) {
int pCount = in.readByte();
NClass[] parents = new NClass[pCount];
for (int j = 0; j < pCount; j++) {
parents[j] = classes[in.readInt()];
try {
byte[] header = new byte[JADX_CLS_SET_HEADER.length()];
int readHeaderLength = in.read(header);
int version = in.readByte();
if (readHeaderLength != JADX_CLS_SET_HEADER.length()
|| !JADX_CLS_SET_HEADER.equals(new String(header, STRING_CHARSET))
|| version != VERSION) {
throw new DecodeException("Wrong jadx class set header");
}
classes[i].setParents(parents);
int count = in.readInt();
classes = new NClass[count];
for (int i = 0; i < count; i++) {
String name = readString(in);
classes[i] = new NClass(name, i);
}
for (int i = 0; i < count; i++) {
int pCount = in.readByte();
NClass[] parents = new NClass[pCount];
for (int j = 0; j < pCount; j++) {
parents[j] = classes[in.readInt()];
}
classes[i].setParents(parents);
}
} finally {
close(in);
}
}
@@ -5,6 +5,7 @@ import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -25,6 +26,8 @@ public class ClspGraph {
private final Map<String, Set<String>> ancestorCache = new WeakHashMap<String, Set<String>>();
private Map<String, NClass> nameMap;
private final Set<String> missingClasses = new HashSet<String>();
public void load() throws IOException, DecodeException {
ClsSet set = new ClsSet();
set.load();
@@ -45,16 +48,10 @@ public class ClspGraph {
throw new JadxRuntimeException("Classpath must be loaded first");
}
int size = classes.size();
for (ClassNode cls : classes) {
size += cls.getInnerClasses().size();
}
NClass[] nClasses = new NClass[size];
int k = 0;
for (ClassNode cls : classes) {
nClasses[k++] = addClass(cls);
for (ClassNode inner : cls.getInnerClasses()) {
nClasses[k++] = addClass(inner);
}
}
for (int i = 0; i < size; i++) {
nClasses[i].setParents(ClsSet.makeParentsArray(classes.get(i), nameMap));
@@ -62,8 +59,9 @@ public class ClspGraph {
}
private NClass addClass(ClassNode cls) {
NClass nClass = new NClass(cls.getRawName(), -1);
nameMap.put(cls.getRawName(), nClass);
String rawName = cls.getRawName();
NClass nClass = new NClass(rawName, -1);
nameMap.put(rawName, nClass);
return nClass;
}
@@ -77,15 +75,15 @@ public class ClspGraph {
return clsName;
}
NClass cls = nameMap.get(implClsName);
if (cls != null) {
if (isImplements(clsName, implClsName)) {
return implClsName;
}
Set<String> anc = getAncestors(clsName);
return searchCommonParent(anc, cls);
if (cls == null) {
missingClasses.add(clsName);
return null;
}
LOG.debug("Missing class: {}", implClsName);
return null;
if (isImplements(clsName, implClsName)) {
return implClsName;
}
Set<String> anc = getAncestors(clsName);
return searchCommonParent(anc, cls);
}
private String searchCommonParent(Set<String> anc, NClass cls) {
@@ -93,11 +91,10 @@ public class ClspGraph {
String name = p.getName();
if (anc.contains(name)) {
return name;
} else {
String r = searchCommonParent(anc, p);
if (r != null) {
return r;
}
}
String r = searchCommonParent(anc, p);
if (r != null) {
return r;
}
}
return null;
@@ -109,17 +106,17 @@ public class ClspGraph {
return result;
}
NClass cls = nameMap.get(clsName);
if (cls != null) {
result = new HashSet<String>();
addAncestorsNames(cls, result);
if (result.isEmpty()) {
result = Collections.emptySet();
}
ancestorCache.put(clsName, result);
return result;
if (cls == null) {
missingClasses.add(clsName);
return Collections.emptySet();
}
LOG.debug("Missing class: {}", clsName);
return Collections.emptySet();
result = new HashSet<String>();
addAncestorsNames(cls, result);
if (result.isEmpty()) {
result = Collections.emptySet();
}
ancestorCache.put(clsName, result);
return result;
}
private void addAncestorsNames(NClass cls, Set<String> result) {
@@ -128,4 +125,19 @@ public class ClspGraph {
addAncestorsNames(p, result);
}
}
public void printMissingClasses() {
int count = missingClasses.size();
if (count == 0) {
return;
}
LOG.warn("Found {} references to unknown classes", count);
if (LOG.isDebugEnabled()) {
List<String> clsNames = new ArrayList<String>(missingClasses);
Collections.sort(clsNames);
for (String cls : clsNames) {
LOG.debug(" {}", cls);
}
}
}
}
@@ -1,5 +1,6 @@
package jadx.core.clsp;
import jadx.api.JadxArgs;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.files.InputFile;
@@ -35,33 +36,38 @@ public class ConvertToClsSet {
if (f.isDirectory()) {
addFilesFromDirectory(f, inputFiles);
} else {
inputFiles.add(new InputFile(f));
InputFile.addFilesFrom(f, inputFiles);
}
}
for (InputFile inputFile : inputFiles) {
LOG.info("Loaded: " + inputFile.getFile());
LOG.info("Loaded: {}", inputFile.getFile());
}
RootNode root = new RootNode();
RootNode root = new RootNode(new JadxArgs());
root.load(inputFiles);
ClsSet set = new ClsSet();
set.load(root);
set.save(output);
LOG.info("Output: " + output);
LOG.info("Output: {}", output);
LOG.info("done");
}
private static void addFilesFromDirectory(File dir, List<InputFile> inputFiles) throws IOException, DecodeException {
private static void addFilesFromDirectory(File dir, List<InputFile> inputFiles) {
File[] files = dir.listFiles();
if (files == null) {
return;
}
for (File file : files) {
if (file.isDirectory()) {
addFilesFromDirectory(file, inputFiles);
}
if (file.getName().endsWith(".dex")) {
inputFiles.add(new InputFile(file));
} else {
try {
InputFile.addFilesFrom(file, inputFiles);
} catch (Exception e) {
LOG.warn("Skip file: {}, load error: {}", file, e.getMessage());
}
}
}
}
}
@@ -1,7 +1,7 @@
package jadx.core.codegen;
import jadx.core.Consts;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttributeNode;
import jadx.core.dex.attributes.annotations.Annotation;
import jadx.core.dex.attributes.annotations.AnnotationsList;
@@ -42,8 +42,12 @@ public class AnnotationGen {
}
public void addForParameter(CodeWriter code, MethodParameters paramsAnnotations, int n) {
AnnotationsList aList = paramsAnnotations.getParamList().get(n);
if (aList == null || aList.size() == 0) {
List<AnnotationsList> paramList = paramsAnnotations.getParamList();
if (n >= paramList.size()) {
return;
}
AnnotationsList aList = paramList.get(n);
if (aList == null || aList.isEmpty()) {
return;
}
for (Annotation a : aList.getAll()) {
@@ -53,8 +57,8 @@ public class AnnotationGen {
}
private void add(IAttributeNode node, CodeWriter code) {
AnnotationsList aList = (AnnotationsList) node.getAttributes().get(AttributeType.ANNOTATION_LIST);
if (aList == null || aList.size() == 0) {
AnnotationsList aList = node.get(AType.ANNOTATION_LIST);
if (aList == null || aList.isEmpty()) {
return;
}
for (Annotation a : aList.getAll()) {
@@ -73,7 +77,7 @@ public class AnnotationGen {
private void formatAnnotation(CodeWriter code, Annotation a) {
code.add('@');
code.add(classGen.useClass(a.getType()));
classGen.useType(code, a.getType());
Map<String, Object> vl = a.getValues();
if (!vl.isEmpty()) {
code.add('(');
@@ -96,13 +100,13 @@ public class AnnotationGen {
@SuppressWarnings("unchecked")
public void addThrows(MethodNode mth, CodeWriter code) {
Annotation an = mth.getAttributes().getAnnotation(Consts.DALVIK_THROWS);
Annotation an = mth.getAnnotation(Consts.DALVIK_THROWS);
if (an != null) {
Object exs = an.getDefaultValue();
code.add(" throws ");
for (Iterator<ArgType> it = ((List<ArgType>) exs).iterator(); it.hasNext(); ) {
ArgType ex = it.next();
code.add(TypeGen.translate(classGen, ex));
classGen.useType(code, ex);
if (it.hasNext()) {
code.add(", ");
}
@@ -111,7 +115,7 @@ public class AnnotationGen {
}
public Object getAnnotationDefaultValue(String name) {
Annotation an = cls.getAttributes().getAnnotation(Consts.DALVIK_ANNOTATION_DEFAULT);
Annotation an = cls.getAnnotation(Consts.DALVIK_ANNOTATION_DEFAULT);
if (an != null) {
Annotation defAnnotation = (Annotation) an.getDefaultValue();
return defAnnotation.getValues().get(name);
@@ -126,11 +130,11 @@ public class AnnotationGen {
return;
}
if (val instanceof String) {
code.add(StringUtils.unescapeString((String) val));
code.add(getStringUtils().unescapeString((String) val));
} else if (val instanceof Integer) {
code.add(TypeGen.formatInteger((Integer) val));
} else if (val instanceof Character) {
code.add(StringUtils.unescapeChar((Character) val));
code.add(getStringUtils().unescapeChar((Character) val));
} else if (val instanceof Boolean) {
code.add(Boolean.TRUE.equals(val) ? "true" : "false");
} else if (val instanceof Float) {
@@ -144,14 +148,15 @@ public class AnnotationGen {
} else if (val instanceof Byte) {
code.add(TypeGen.formatByte((Byte) val));
} else if (val instanceof ArgType) {
code.add(TypeGen.translate(classGen, (ArgType) val)).add(".class");
classGen.useType(code, (ArgType) val);
code.add(".class");
} else if (val instanceof FieldInfo) {
// must be a static field
FieldInfo field = (FieldInfo) val;
code.add(InsnGen.makeStaticFieldAccess(field, classGen));
} else if (val instanceof List) {
InsnGen.makeStaticFieldAccess(code, field, classGen);
} else if (val instanceof Iterable) {
code.add('{');
Iterator<?> it = ((List) val).iterator();
Iterator<?> it = ((Iterable) val).iterator();
while (it.hasNext()) {
Object obj = it.next();
encodeValue(code, obj);
@@ -167,4 +172,8 @@ public class AnnotationGen {
throw new JadxRuntimeException("Can't decode value: " + val + " (" + val.getClass() + ")");
}
}
private StringUtils getStringUtils() {
return cls.dex().root().getStringUtils();
}
}
@@ -1,27 +1,31 @@
package jadx.core.codegen;
import jadx.core.Consts;
import jadx.api.IJadxArgs;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.AttrNode;
import jadx.core.dex.attributes.AttributeFlag;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.EnumClassAttr;
import jadx.core.dex.attributes.EnumClassAttr.EnumField;
import jadx.core.dex.attributes.SourceFileAttr;
import jadx.core.dex.attributes.nodes.EnumClassAttr;
import jadx.core.dex.attributes.nodes.EnumClassAttr.EnumField;
import jadx.core.dex.attributes.nodes.SourceFileAttr;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.PrimitiveType;
import jadx.core.dex.instructions.mods.ConstructorInsn;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.parser.FieldValueAttr;
import jadx.core.dex.nodes.parser.FieldInitAttr;
import jadx.core.dex.nodes.parser.FieldInitAttr.InitType;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.CodegenException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
@@ -37,18 +41,35 @@ import com.android.dx.rop.code.AccessFlags;
public class ClassGen {
private static final Logger LOG = LoggerFactory.getLogger(ClassGen.class);
public static final Comparator<MethodNode> METHOD_LINE_COMPARATOR = new Comparator<MethodNode>() {
@Override
public int compare(MethodNode a, MethodNode b) {
return Utils.compare(a.getSourceLine(), b.getSourceLine());
}
};
private final ClassNode cls;
private final ClassGen parentGen;
private final AnnotationGen annotationGen;
private final boolean fallback;
private final boolean showInconsistentCode;
private final Set<ClassInfo> imports = new HashSet<ClassInfo>();
private int clsDeclLine = 0;
private int clsDeclLine;
public ClassGen(ClassNode cls, ClassGen parentClsGen, boolean fallback) {
public ClassGen(ClassNode cls, IJadxArgs jadxArgs) {
this(cls, null, jadxArgs.isFallbackMode(), jadxArgs.isShowInconsistentCode());
}
public ClassGen(ClassNode cls, ClassGen parentClsGen) {
this(cls, parentClsGen, parentClsGen.fallback, parentClsGen.showInconsistentCode);
}
public ClassGen(ClassNode cls, ClassGen parentClsGen, boolean fallback, boolean showBadCode) {
this.cls = cls;
this.parentGen = parentClsGen;
this.fallback = fallback;
this.showInconsistentCode = showBadCode;
this.annotationGen = new AnnotationGen(cls, this);
}
@@ -62,17 +83,15 @@ public class ClassGen {
addClassCode(clsBody);
CodeWriter clsCode = new CodeWriter();
if (!"".equals(cls.getPackage())) {
clsCode.add("package ").add(cls.getPackage()).add(';');
clsCode.newLine();
}
int importsCount = imports.size();
if (importsCount != 0) {
List<String> sortImports = new ArrayList<String>(importsCount);
for (ClassInfo ic : imports) {
sortImports.add(ic.getFullName());
sortImports.add(ic.getAlias().getFullName());
}
Collections.sort(sortImports);
@@ -84,16 +103,15 @@ public class ClassGen {
sortImports.clear();
imports.clear();
}
clsCode.add(clsBody);
return clsCode;
}
public void addClassCode(CodeWriter code) throws CodegenException {
if (cls.getAttributes().contains(AttributeFlag.DONT_GENERATE)) {
if (cls.contains(AFlag.DONT_GENERATE)) {
return;
}
if (cls.getAttributes().contains(AttributeFlag.INCONSISTENT_CODE)) {
if (cls.contains(AFlag.INCONSISTENT_CODE)) {
code.startLine("// jadx: inconsistent code");
}
addClassDeclaration(code);
@@ -103,13 +121,22 @@ public class ClassGen {
public void addClassDeclaration(CodeWriter clsCode) {
AccessInfo af = cls.getAccessFlags();
if (af.isInterface()) {
af = af.remove(AccessFlags.ACC_ABSTRACT);
af = af.remove(AccessFlags.ACC_ABSTRACT)
.remove(AccessFlags.ACC_STATIC);
} else if (af.isEnum()) {
af = af.remove(AccessFlags.ACC_FINAL).remove(AccessFlags.ACC_ABSTRACT);
af = af.remove(AccessFlags.ACC_FINAL)
.remove(AccessFlags.ACC_ABSTRACT)
.remove(AccessFlags.ACC_STATIC);
}
// 'static' and 'private' modifier not allowed for top classes (not inner)
if (!cls.getAlias().isInner()) {
af = af.remove(AccessFlags.ACC_STATIC).remove(AccessFlags.ACC_PRIVATE);
}
annotationGen.addForClass(clsCode);
insertSourceFileInfo(clsCode, cls);
insertRenameInfo(clsCode, cls);
clsCode.startLine(af.makeString());
if (af.isInterface()) {
if (af.isAnnotation()) {
@@ -121,27 +148,30 @@ public class ClassGen {
} else {
clsCode.add("class ");
}
clsCode.attachDefinition(cls);
clsCode.add(cls.getShortName());
addGenericMap(clsCode, cls.getGenericMap());
clsCode.add(' ');
ClassInfo sup = cls.getSuperClass();
ArgType sup = cls.getSuperClass();
if (sup != null
&& !sup.getFullName().equals(Consts.CLASS_OBJECT)
&& !sup.getFullName().equals(Consts.CLASS_ENUM)) {
clsCode.add("extends ").add(useClass(sup)).add(' ');
&& !sup.equals(ArgType.OBJECT)
&& !sup.getObject().equals(ArgType.ENUM.getObject())) {
clsCode.add("extends ");
useClass(clsCode, sup);
clsCode.add(' ');
}
if (cls.getInterfaces().size() > 0 && !af.isAnnotation()) {
if (!cls.getInterfaces().isEmpty() && !af.isAnnotation()) {
if (cls.getAccessFlags().isInterface()) {
clsCode.add("extends ");
} else {
clsCode.add("implements ");
}
for (Iterator<ClassInfo> it = cls.getInterfaces().iterator(); it.hasNext(); ) {
ClassInfo interf = it.next();
clsCode.add(useClass(interf));
for (Iterator<ArgType> it = cls.getInterfaces().iterator(); it.hasNext(); ) {
ArgType interf = it.next();
useClass(clsCode, interf);
if (it.hasNext()) {
clsCode.add(", ");
}
@@ -150,7 +180,6 @@ public class ClassGen {
clsCode.add(' ');
}
}
clsCode.attachDefinition(cls);
}
public boolean addGenericMap(CodeWriter code, Map<ArgType, List<ArgType>> gmap) {
@@ -165,12 +194,20 @@ public class ClassGen {
if (i != 0) {
code.add(", ");
}
code.add(useClass(type));
if (type.isGenericType()) {
code.add(type.getObject());
} else {
useClass(code, type);
}
if (list != null && !list.isEmpty()) {
code.add(" extends ");
for (Iterator<ArgType> it = list.iterator(); it.hasNext(); ) {
ArgType g = it.next();
code.add(useClass(g));
if (g.isGenericType()) {
code.add(g.getObject());
} else {
useClass(code, g);
}
if (it.hasNext()) {
code.add(" & ");
}
@@ -195,34 +232,64 @@ public class ClassGen {
private void addInnerClasses(CodeWriter code, ClassNode cls) throws CodegenException {
for (ClassNode innerCls : cls.getInnerClasses()) {
if (!innerCls.isAnonymous()) {
ClassGen inClGen = new ClassGen(innerCls, getParentGen(), fallback);
code.newLine();
inClGen.addClassCode(code);
imports.addAll(inClGen.getImports());
if (innerCls.contains(AFlag.DONT_GENERATE)
|| innerCls.contains(AFlag.ANONYMOUS_CLASS)) {
continue;
}
ClassGen inClGen = new ClassGen(innerCls, getParentGen());
code.newLine();
inClGen.addClassCode(code);
imports.addAll(inClGen.getImports());
}
}
private boolean isInnerClassesPresents() {
for (ClassNode innerCls : cls.getInnerClasses()) {
if (!innerCls.contains(AFlag.ANONYMOUS_CLASS)) {
return true;
}
}
return false;
}
private void addMethods(CodeWriter code) {
for (MethodNode mth : cls.getMethods()) {
if (!mth.getAttributes().contains(AttributeFlag.DONT_GENERATE)) {
try {
if (code.getLine() != clsDeclLine) {
code.newLine();
}
addMethod(code, mth);
} catch (Exception e) {
String msg = ErrorsCounter.methodError(mth, "Method generation error", e);
code.startLine("/* " + msg + CodeWriter.NL + Utils.getStackTrace(e) + " */");
}
List<MethodNode> methods = sortMethodsByLine(cls.getMethods());
for (MethodNode mth : methods) {
if (mth.contains(AFlag.DONT_GENERATE)) {
continue;
}
if (code.getLine() != clsDeclLine) {
code.newLine();
}
try {
addMethod(code, mth);
} catch (Exception e) {
code.newLine().add("/*");
code.newLine().add(ErrorsCounter.methodError(mth, "Method generation error", e));
code.newLine().add(Utils.getStackTrace(e));
code.newLine().add("*/");
}
}
}
private static List<MethodNode> sortMethodsByLine(List<MethodNode> methods) {
List<MethodNode> out = new ArrayList<MethodNode>(methods);
Collections.sort(out, METHOD_LINE_COMPARATOR);
return out;
}
private boolean isMethodsPresents() {
for (MethodNode mth : cls.getMethods()) {
if (!mth.contains(AFlag.DONT_GENERATE)) {
return true;
}
}
return false;
}
private void addMethod(CodeWriter code, MethodNode mth) throws CodegenException {
MethodGen mthGen = new MethodGen(this, mth);
if (mth.getAccessFlags().isAbstract() || mth.getAccessFlags().isNative()) {
MethodGen mthGen = new MethodGen(this, mth);
mthGen.addDefinition(code);
if (cls.getAccessFlags().isAnnotation()) {
Object def = annotationGen.getAnnotationDefaultValue(mth.getName());
@@ -233,11 +300,21 @@ public class ClassGen {
}
code.add(';');
} else {
boolean badCode = mth.getAttributes().contains(AttributeFlag.INCONSISTENT_CODE);
boolean badCode = mth.contains(AFlag.INCONSISTENT_CODE);
if (badCode) {
code.startLine("/* JADX WARNING: inconsistent code. */");
code.startLine("/* Code decompiled incorrectly, please refer to instructions dump. */");
LOG.error(ErrorsCounter.formatErrorMsg(mth, " Inconsistent code"));
ErrorsCounter.methodError(mth, "Inconsistent code");
if (showInconsistentCode) {
mth.remove(AFlag.INCONSISTENT_CODE);
badCode = false;
}
}
MethodGen mthGen;
if (badCode || mth.contains(AType.JADX_ERROR) || fallback) {
mthGen = MethodGen.getFallbackMethodGen(mth);
} else {
mthGen = new MethodGen(this, mth);
}
if (mthGen.addDefinition(code)) {
code.add(' ');
@@ -245,7 +322,11 @@ public class ClassGen {
code.add('{');
code.incIndent();
insertSourceFileInfo(code, mth);
mthGen.addInstructions(code);
if (fallback) {
mthGen.addFallbackMethodCode(code);
} else {
mthGen.addInstructions(code);
}
code.decIndent();
code.startLine('}');
}
@@ -254,153 +335,197 @@ public class ClassGen {
private void addFields(CodeWriter code) throws CodegenException {
addEnumFields(code);
for (FieldNode f : cls.getFields()) {
if (f.getAttributes().contains(AttributeFlag.DONT_GENERATE)) {
if (f.contains(AFlag.DONT_GENERATE)) {
continue;
}
annotationGen.addForField(code, f);
code.startLine(f.getAccessFlags().makeString());
code.add(TypeGen.translate(this, f.getType()));
useType(code, f.getType());
code.add(' ');
code.add(f.getName());
FieldValueAttr fv = (FieldValueAttr) f.getAttributes().get(AttributeType.FIELD_VALUE);
code.attachDefinition(f);
code.add(f.getAlias());
FieldInitAttr fv = f.get(AType.FIELD_INIT);
if (fv != null) {
code.add(" = ");
if (fv.getValue() == null) {
code.add(TypeGen.literalToString(0, f.getType()));
code.add(TypeGen.literalToString(0, f.getType(), cls));
} else {
annotationGen.encodeValue(code, fv.getValue());
if (fv.getValueType() == InitType.CONST) {
annotationGen.encodeValue(code, fv.getValue());
} else if (fv.getValueType() == InitType.INSN) {
InsnGen insnGen = makeInsnGen(fv.getInsnMth());
addInsnBody(insnGen, code, fv.getInsn());
}
}
}
code.add(';');
code.attachDefinition(f);
}
}
private void addEnumFields(CodeWriter code) throws CodegenException {
EnumClassAttr enumFields = (EnumClassAttr) cls.getAttributes().get(AttributeType.ENUM_CLASS);
if (enumFields != null) {
InsnGen igen = null;
for (Iterator<EnumField> it = enumFields.getFields().iterator(); it.hasNext(); ) {
EnumField f = it.next();
code.startLine(f.getName());
if (f.getArgs().size() != 0) {
code.add('(');
for (Iterator<InsnArg> aIt = f.getArgs().iterator(); aIt.hasNext(); ) {
InsnArg arg = aIt.next();
if (igen == null) {
// don't init mth gen if this is simple enum
MethodGen mthGen = new MethodGen(this, enumFields.getStaticMethod());
igen = new InsnGen(mthGen, false);
}
igen.addArg(code, arg);
if (aIt.hasNext()) {
code.add(", ");
}
}
code.add(')');
}
if (f.getCls() != null) {
new ClassGen(f.getCls(), this, fallback).addClassBody(code);
}
if (it.hasNext()) {
code.add(',');
}
private boolean isFieldsPresents() {
for (FieldNode field : cls.getFields()) {
if (!field.contains(AFlag.DONT_GENERATE)) {
return true;
}
}
return false;
}
private void addEnumFields(CodeWriter code) throws CodegenException {
EnumClassAttr enumFields = cls.get(AType.ENUM_CLASS);
if (enumFields == null) {
return;
}
InsnGen igen = null;
for (Iterator<EnumField> it = enumFields.getFields().iterator(); it.hasNext(); ) {
EnumField f = it.next();
code.startLine(f.getField().getAlias());
ConstructorInsn constrInsn = f.getConstrInsn();
if (constrInsn.getArgsCount() > f.getStartArg()) {
if (igen == null) {
igen = makeInsnGen(enumFields.getStaticMethod());
}
MethodNode callMth = cls.dex().resolveMethod(constrInsn.getCallMth());
igen.generateMethodArguments(code, constrInsn, f.getStartArg(), callMth);
}
if (f.getCls() != null) {
code.add(' ');
new ClassGen(f.getCls(), this).addClassBody(code);
}
if (it.hasNext()) {
code.add(',');
}
}
if (isMethodsPresents() || isFieldsPresents() || isInnerClassesPresents()) {
if (enumFields.getFields().isEmpty()) {
code.startLine();
}
code.add(';');
code.newLine();
}
}
public String useClass(ArgType clsType) {
if (clsType.isGenericType()) {
return clsType.getObject();
}
return useClass(ClassInfo.fromType(clsType));
}
public String useClass(ClassInfo classInfo) {
String baseClass = useClassInternal(cls.getClassInfo(), classInfo);
ArgType type = classInfo.getType();
ArgType[] generics = type.getGenericTypes();
if (generics == null) {
return baseClass;
}
StringBuilder sb = new StringBuilder();
sb.append(baseClass);
sb.append('<');
int len = generics.length;
for (int i = 0; i < len; i++) {
if (i != 0) {
sb.append(", ");
if (isFieldsPresents()) {
code.startLine();
}
ArgType gt = generics[i];
ArgType wt = gt.getWildcardType();
if (wt != null) {
sb.append('?');
int bounds = gt.getWildcardBounds();
if (bounds != 0) {
sb.append(bounds == -1 ? " super " : " extends ");
sb.append(TypeGen.translate(this, wt));
}
}
}
private InsnGen makeInsnGen(MethodNode mth) {
MethodGen mthGen = new MethodGen(this, mth);
return new InsnGen(mthGen, false);
}
private void addInsnBody(InsnGen insnGen, CodeWriter code, InsnNode insn) {
try {
insnGen.makeInsn(insn, code, InsnGen.Flags.BODY_ONLY_NOWRAP);
} catch (Exception e) {
ErrorsCounter.classError(cls, "Failed to generate init code", e);
}
}
public void useType(CodeWriter code, ArgType type) {
PrimitiveType stype = type.getPrimitiveType();
if (stype == null) {
code.add(type.toString());
} else if (stype == PrimitiveType.OBJECT) {
if (type.isGenericType()) {
code.add(type.getObject());
} else {
sb.append(TypeGen.translate(this, gt));
useClass(code, type);
}
} else if (stype == PrimitiveType.ARRAY) {
useType(code, type.getArrayElement());
code.add("[]");
} else {
code.add(stype.getLongName());
}
sb.append('>');
return sb.toString();
}
private String useClassInternal(ClassInfo useCls, ClassInfo classInfo) {
String fullName = classInfo.getFullName();
public void useClass(CodeWriter code, ArgType type) {
useClass(code, ClassInfo.extCls(cls.dex(), type));
ArgType[] generics = type.getGenericTypes();
if (generics != null) {
code.add('<');
int len = generics.length;
for (int i = 0; i < len; i++) {
if (i != 0) {
code.add(", ");
}
ArgType gt = generics[i];
ArgType wt = gt.getWildcardType();
if (wt != null) {
code.add('?');
int bounds = gt.getWildcardBounds();
if (bounds != 0) {
code.add(bounds == -1 ? " super " : " extends ");
useType(code, wt);
}
} else {
useType(code, gt);
}
}
code.add('>');
}
}
public void useClass(CodeWriter code, ClassInfo classInfo) {
ClassNode classNode = cls.dex().resolveClass(classInfo);
if (classNode != null) {
code.attachAnnotation(classNode);
}
String baseClass = useClassInternal(cls.getAlias(), classInfo.getAlias());
code.add(baseClass);
}
private String useClassInternal(ClassInfo useCls, ClassInfo extClsInfo) {
String fullName = extClsInfo.getFullName();
if (fallback) {
return fullName;
}
String shortName = classInfo.getShortName();
if (classInfo.getPackage().equals("java.lang") && classInfo.getParentClass() == null) {
return shortName;
} else {
// don't add import if this class inner for current class
if (isClassInnerFor(classInfo, useCls)) {
return shortName;
}
// don't add import if this class from same package
if (classInfo.getPackage().equals(useCls.getPackage()) && !classInfo.isInner()) {
return shortName;
}
// don't add import if class not public (must be accessed using inheritance)
ClassNode classNode = cls.dex().resolveClass(classInfo);
if (classNode != null && !classNode.getAccessFlags().isPublic()) {
return shortName;
}
if (searchCollision(cls.dex(), useCls, shortName)) {
return fullName;
}
if (classInfo.getPackage().equals(useCls.getPackage())) {
fullName = classInfo.getNameWithoutPackage();
}
for (ClassInfo importCls : getImports()) {
if (!importCls.equals(classInfo)
&& importCls.getShortName().equals(shortName)) {
if (classInfo.isInner()) {
String parent = useClassInternal(useCls, classInfo.getParentClass());
return parent + "." + shortName;
} else {
return fullName;
}
}
}
addImport(classInfo);
String shortName = extClsInfo.getShortName();
if (extClsInfo.getPackage().equals("java.lang") && extClsInfo.getParentClass() == null) {
return shortName;
}
if (isClassInnerFor(useCls, extClsInfo)) {
return shortName;
}
if (isBothClassesInOneTopClass(useCls, extClsInfo)) {
return shortName;
}
// don't add import if this class from same package
if (extClsInfo.getPackage().equals(useCls.getPackage()) && !extClsInfo.isInner()) {
return shortName;
}
// don't add import if class not public (must be accessed using inheritance)
ClassNode classNode = cls.dex().resolveClass(extClsInfo);
if (classNode != null && !classNode.getAccessFlags().isPublic()) {
return shortName;
}
if (searchCollision(cls.dex(), useCls, extClsInfo)) {
return fullName;
}
// ignore classes from default package
if (extClsInfo.isDefaultPackage()) {
return shortName;
}
if (extClsInfo.getPackage().equals(useCls.getPackage())) {
fullName = extClsInfo.getNameWithoutPackage();
}
for (ClassInfo importCls : getImports()) {
if (!importCls.equals(extClsInfo)
&& importCls.getShortName().equals(shortName)) {
if (extClsInfo.isInner()) {
String parent = useClassInternal(useCls, extClsInfo.getParentClass().getAlias());
return parent + "." + shortName;
} else {
return fullName;
}
}
}
addImport(extClsInfo);
return shortName;
}
private void addImport(ClassInfo classInfo) {
if (parentGen != null) {
parentGen.addImport(classInfo);
parentGen.addImport(classInfo.getAlias());
} else {
imports.add(classInfo);
}
@@ -414,6 +539,16 @@ public class ClassGen {
}
}
private static boolean isBothClassesInOneTopClass(ClassInfo useCls, ClassInfo extClsInfo) {
ClassInfo a = useCls.getTopParentClass();
ClassInfo b = extClsInfo.getTopParentClass();
if (a != null) {
return a.equals(b);
}
// useCls - is a top class
return useCls.equals(b);
}
private static boolean isClassInnerFor(ClassInfo inner, ClassInfo parent) {
if (inner.isInner()) {
ClassInfo p = inner.getParentClass();
@@ -422,28 +557,38 @@ public class ClassGen {
return false;
}
private static boolean searchCollision(DexNode dex, ClassInfo useCls, String shortName) {
private static boolean searchCollision(DexNode dex, ClassInfo useCls, ClassInfo searchCls) {
if (useCls == null) {
return false;
}
String shortName = searchCls.getShortName();
if (useCls.getShortName().equals(shortName)) {
return true;
}
ClassNode classNode = dex.resolveClass(useCls);
if (classNode != null) {
for (ClassNode inner : classNode.getInnerClasses()) {
if (inner.getShortName().equals(shortName)) {
if (inner.getShortName().equals(shortName)
&& !inner.getAlias().equals(searchCls)) {
return true;
}
}
}
return searchCollision(dex, useCls.getParentClass(), shortName);
return searchCollision(dex, useCls.getParentClass(), searchCls);
}
private void insertSourceFileInfo(CodeWriter code, AttrNode node) {
SourceFileAttr sourceFileAttr = (SourceFileAttr) node.getAttributes().get(AttributeType.SOURCE_FILE);
SourceFileAttr sourceFileAttr = node.get(AType.SOURCE_FILE);
if (sourceFileAttr != null) {
code.startLine("// compiled from: ").add(sourceFileAttr.getFileName());
code.startLine("/* compiled from: ").add(sourceFileAttr.getFileName()).add(" */");
}
}
private void insertRenameInfo(CodeWriter code, ClassNode cls) {
ClassInfo classInfo = cls.getClassInfo();
if (classInfo.isRenamed()
&& !cls.getShortName().equals(cls.getAlias().getShortName())) {
code.startLine("/* renamed from: ").add(classInfo.getFullName()).add(" */");
}
}
@@ -15,15 +15,11 @@ public class CodeGen extends AbstractVisitor {
@Override
public boolean visit(ClassNode cls) throws CodegenException {
ClassGen clsGen = new ClassGen(cls, null, isFallbackMode());
ClassGen clsGen = new ClassGen(cls, args);
CodeWriter clsCode = clsGen.makeClass();
clsCode.finish();
cls.setCode(clsCode);
return false;
}
public boolean isFallbackMode() {
return args.isFallbackMode();
}
}
@@ -1,8 +1,8 @@
package jadx.core.codegen;
import jadx.api.CodePosition;
import jadx.core.dex.attributes.LineAttrNode;
import jadx.core.utils.Utils;
import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.utils.files.FileUtils;
import java.io.File;
import java.io.PrintWriter;
@@ -10,17 +10,22 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static jadx.core.utils.files.FileUtils.close;
public class CodeWriter {
private static final Logger LOG = LoggerFactory.getLogger(CodeWriter.class);
private static final int MAX_FILENAME_LENGTH = 128;
public static final String NL = System.getProperty("line.separator");
public static final String INDENT = " ";
private static final boolean ADD_LINE_NUMBERS = false;
private static final String[] INDENT_CACHE = {
"",
INDENT,
@@ -30,22 +35,23 @@ public class CodeWriter {
INDENT + INDENT + INDENT + INDENT + INDENT,
};
private final StringBuilder buf = new StringBuilder();
private StringBuilder buf = new StringBuilder();
@Nullable
private String code;
private String indentStr;
private int indent;
private int line = 1;
private int offset = 0;
private Map<CodePosition, Object> annotations = Collections.emptyMap();
private Map<Integer, Integer> lineMap = Collections.emptyMap();
public CodeWriter() {
this.indent = 0;
this.indentStr = "";
}
public CodeWriter(int indent) {
this.indent = indent;
updateIndent();
if (ADD_LINE_NUMBERS) {
incIndent(2);
}
}
public CodeWriter startLine() {
@@ -68,8 +74,23 @@ public class CodeWriter {
return this;
}
public CodeWriter add(Object obj) {
add(obj.toString());
public CodeWriter startLineWithNum(int sourceLine) {
if (sourceLine == 0) {
startLine();
return this;
}
if (ADD_LINE_NUMBERS) {
newLine();
attachSourceLine(sourceLine);
String ln = "/* " + sourceLine + " */ ";
add(ln);
if (indentStr.length() > ln.length()) {
add(indentStr.substring(ln.length()));
}
} else {
startLine();
attachSourceLine(sourceLine);
}
return this;
}
@@ -91,9 +112,12 @@ public class CodeWriter {
CodePosition pos = entry.getKey();
attachAnnotation(entry.getValue(), new CodePosition(line + pos.getLine(), pos.getOffset()));
}
for (Map.Entry<Integer, Integer> entry : code.lineMap.entrySet()) {
attachSourceLine(line + entry.getKey(), entry.getValue());
}
line += code.line;
offset = code.offset;
buf.append(code);
buf.append(code.buf);
return this;
}
@@ -102,6 +126,11 @@ public class CodeWriter {
return this;
}
public CodeWriter addIndent() {
add(INDENT);
return this;
}
private void addLine() {
buf.append(NL);
line++;
@@ -114,11 +143,6 @@ public class CodeWriter {
return this;
}
public CodeWriter addIndent() {
add(INDENT);
return this;
}
private void updateIndent() {
int curIndent = indent;
if (curIndent < INDENT_CACHE.length) {
@@ -154,6 +178,10 @@ public class CodeWriter {
updateIndent();
}
public int getIndent() {
return indent;
}
public int getLine() {
return line;
}
@@ -170,12 +198,13 @@ public class CodeWriter {
}
}
public Object attachDefinition(LineAttrNode obj) {
return attachAnnotation(new DefinitionWrapper(obj), new CodePosition(line, offset));
public void attachDefinition(LineAttrNode obj) {
attachAnnotation(obj);
attachAnnotation(new DefinitionWrapper(obj), new CodePosition(line, offset));
}
public Object attachAnnotation(Object obj) {
return attachAnnotation(obj, new CodePosition(line, offset + 1));
public void attachAnnotation(Object obj) {
attachAnnotation(obj, new CodePosition(line, offset + 1));
}
private Object attachAnnotation(Object obj, CodePosition pos) {
@@ -189,8 +218,30 @@ public class CodeWriter {
return annotations;
}
public void attachSourceLine(int sourceLine) {
if (sourceLine == 0) {
return;
}
attachSourceLine(line, sourceLine);
}
private void attachSourceLine(int decompiledLine, int sourceLine) {
if (lineMap.isEmpty()) {
lineMap = new TreeMap<Integer, Integer>();
}
lineMap.put(decompiledLine, sourceLine);
}
public Map<Integer, Integer> getLineMapping() {
return lineMap;
}
public void finish() {
removeFirstEmptyLine();
buf.trimToSize();
code = buf.toString();
buf = null;
Iterator<Map.Entry<CodePosition, Object>> it = annotations.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<CodePosition, Object> entry = it.next();
@@ -203,29 +254,23 @@ public class CodeWriter {
}
}
private static String removeFirstEmptyLine(String str) {
if (str.startsWith(NL)) {
return str.substring(NL.length());
} else {
return str;
private void removeFirstEmptyLine() {
if (buf.indexOf(NL) == 0) {
buf.delete(0, NL.length());
}
}
public int length() {
public int bufLength() {
return buf.length();
}
public boolean isEmpty() {
return buf.length() == 0;
}
public boolean notEmpty() {
return buf.length() != 0;
public String getCodeStr() {
return code;
}
@Override
public String toString() {
return buf.toString();
return buf == null ? code : buf.toString();
}
public void save(File dir, String subDir, String fileName) {
@@ -237,48 +282,19 @@ public class CodeWriter {
}
public void save(File file) {
String name = file.getName();
if (name.length() > MAX_FILENAME_LENGTH) {
int dotIndex = name.indexOf('.');
int cutAt = MAX_FILENAME_LENGTH - name.length() + dotIndex - 1;
if (cutAt <= 0) {
name = name.substring(0, MAX_FILENAME_LENGTH - 1);
} else {
name = name.substring(0, cutAt) + name.substring(dotIndex);
}
file = new File(file.getParentFile(), name);
if (code == null) {
finish();
}
File outFile = FileUtils.prepareFile(file);
PrintWriter out = null;
try {
Utils.makeDirsForFile(file);
out = new PrintWriter(file, "UTF-8");
String code = buf.toString();
code = removeFirstEmptyLine(code);
out.print(code);
out = new PrintWriter(outFile, "UTF-8");
out.println(code);
} catch (Exception e) {
LOG.error("Save file error", e);
} finally {
if (out != null) {
out.close();
}
close(out);
}
}
@Override
public int hashCode() {
return buf.toString().hashCode();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof CodeWriter)) {
return false;
}
CodeWriter that = (CodeWriter) o;
return buf.toString().equals(that.buf.toString());
}
}
@@ -8,13 +8,16 @@ import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.InsnWrapArg;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.regions.Compare;
import jadx.core.dex.regions.IfCondition;
import jadx.core.dex.regions.conditions.Compare;
import jadx.core.dex.regions.conditions.IfCondition;
import jadx.core.dex.regions.conditions.IfCondition.Mode;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Queue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -22,42 +25,83 @@ import org.slf4j.LoggerFactory;
public class ConditionGen extends InsnGen {
private static final Logger LOG = LoggerFactory.getLogger(ConditionGen.class);
private static class CondStack {
private final Queue<IfCondition> stack = new LinkedList<IfCondition>();
public Queue<IfCondition> getStack() {
return stack;
}
public void push(IfCondition cond) {
stack.add(cond);
}
public IfCondition pop() {
return stack.poll();
}
}
public ConditionGen(InsnGen insnGen) {
super(insnGen.mgen, insnGen.fallback);
}
void add(CodeWriter code, IfCondition condition) throws CodegenException {
add(code, new CondStack(), condition);
}
void wrap(CodeWriter code, IfCondition condition) throws CodegenException {
wrap(code, new CondStack(), condition);
}
private void add(CodeWriter code, CondStack stack, IfCondition condition) throws CodegenException {
stack.push(condition);
switch (condition.getMode()) {
case COMPARE:
addCompare(code, condition.getCompare());
addCompare(code, stack, condition.getCompare());
break;
case TERNARY:
addTernary(code, stack, condition);
break;
case NOT:
addNot(code, condition);
addNot(code, stack, condition);
break;
case AND:
case OR:
addAndOr(code, condition);
addAndOr(code, stack, condition);
break;
default:
throw new JadxRuntimeException("Unknown condition mode: " + condition);
throw new JadxRuntimeException("Unknown condition mode: " + condition.getMode());
}
stack.pop();
}
void wrap(CodeWriter code, IfCondition cond) throws CodegenException {
private void wrap(CodeWriter code, CondStack stack, IfCondition cond) throws CodegenException {
boolean wrap = isWrapNeeded(cond);
if (wrap) {
code.add('(');
}
add(code, cond);
add(code, stack, cond);
if (wrap) {
code.add(')');
}
}
private void addCompare(CodeWriter code, Compare compare) throws CodegenException {
private void wrap(CodeWriter code, InsnArg firstArg) throws CodegenException {
boolean wrap = isArgWrapNeeded(firstArg);
if (wrap) {
code.add('(');
}
addArg(code, firstArg, false);
if (wrap) {
code.add(')');
}
}
private void addCompare(CodeWriter code, CondStack stack, Compare compare) throws CodegenException {
IfOp op = compare.getOp();
InsnArg firstArg = compare.getA();
InsnArg secondArg = compare.getB();
@@ -70,39 +114,44 @@ public class ConditionGen extends InsnGen {
}
if (op == IfOp.EQ) {
// == true
addArg(code, firstArg, false);
if (stack.getStack().size() == 1) {
addArg(code, firstArg, false);
} else {
wrap(code, firstArg);
}
return;
} else if (op == IfOp.NE) {
// != true
code.add('!');
boolean wrap = isWrapNeeded(firstArg);
if (wrap) {
code.add('(');
}
addArg(code, firstArg, false);
if (wrap) {
code.add(')');
}
wrap(code, firstArg);
return;
}
LOG.warn(ErrorsCounter.formatErrorMsg(mth, "Unsupported boolean condition " + op.getSymbol()));
}
addArg(code, firstArg, isWrapNeeded(firstArg));
addArg(code, firstArg, isArgWrapNeeded(firstArg));
code.add(' ').add(op.getSymbol()).add(' ');
addArg(code, secondArg, isWrapNeeded(secondArg));
addArg(code, secondArg, isArgWrapNeeded(secondArg));
}
private void addNot(CodeWriter code, IfCondition condition) throws CodegenException {
private void addTernary(CodeWriter code, CondStack stack, IfCondition condition) throws CodegenException {
add(code, stack, condition.first());
code.add(" ? ");
add(code, stack, condition.second());
code.add(" : ");
add(code, stack, condition.third());
}
private void addNot(CodeWriter code, CondStack stack, IfCondition condition) throws CodegenException {
code.add('!');
wrap(code, condition.getArgs().get(0));
wrap(code, stack, condition.getArgs().get(0));
}
private void addAndOr(CodeWriter code, IfCondition condition) throws CodegenException {
String mode = condition.getMode() == IfCondition.Mode.AND ? " && " : " || ";
private void addAndOr(CodeWriter code, CondStack stack, IfCondition condition) throws CodegenException {
String mode = condition.getMode() == Mode.AND ? " && " : " || ";
Iterator<IfCondition> it = condition.getArgs().iterator();
while (it.hasNext()) {
wrap(code, it.next());
wrap(code, stack, it.next());
if (it.hasNext()) {
code.add(mode);
}
@@ -110,15 +159,19 @@ public class ConditionGen extends InsnGen {
}
private boolean isWrapNeeded(IfCondition condition) {
return !condition.isCompare();
if (condition.isCompare()) {
return false;
}
return condition.getMode() != Mode.NOT;
}
private static boolean isWrapNeeded(InsnArg arg) {
private static boolean isArgWrapNeeded(InsnArg arg) {
if (!arg.isInsnWrap()) {
return false;
}
InsnNode insn = ((InsnWrapArg) arg).getWrapInsn();
if (insn.getType() == InsnType.ARITH) {
InsnType insnType = insn.getType();
if (insnType == InsnType.ARITH) {
switch (((ArithNode) insn).getOp()) {
case ADD:
case SUB:
@@ -127,8 +180,18 @@ public class ConditionGen extends InsnGen {
case REM:
return false;
}
} else if (insn.getType() == InsnType.INVOKE) {
return false;
} else {
switch (insnType) {
case INVOKE:
case SGET:
case IGET:
case AGET:
case CONST:
case ARRAY_LENGTH:
return false;
default:
return true;
}
}
return true;
}
@@ -1,10 +1,10 @@
package jadx.core.codegen;
import jadx.core.dex.attributes.AttributeFlag;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.FieldReplaceAttr;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.attributes.MethodInlineAttr;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
import jadx.core.dex.attributes.nodes.MethodInlineAttr;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
@@ -13,19 +13,21 @@ import jadx.core.dex.instructions.ArithOp;
import jadx.core.dex.instructions.ConstClassNode;
import jadx.core.dex.instructions.ConstStringNode;
import jadx.core.dex.instructions.FillArrayNode;
import jadx.core.dex.instructions.FilledNewArrayNode;
import jadx.core.dex.instructions.GotoNode;
import jadx.core.dex.instructions.IfNode;
import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.InvokeNode;
import jadx.core.dex.instructions.InvokeType;
import jadx.core.dex.instructions.NewArrayNode;
import jadx.core.dex.instructions.SwitchNode;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.FieldArg;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.InsnWrapArg;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.NamedArg;
import jadx.core.dex.instructions.args.Named;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.mods.ConstructorInsn;
import jadx.core.dex.instructions.mods.TernaryInsn;
@@ -35,22 +37,23 @@ import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.RegionUtils;
import jadx.core.utils.StringUtils;
import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static jadx.core.utils.android.AndroidResourcesUtils.handleAppResField;
public class InsnGen {
private static final Logger LOG = LoggerFactory.getLogger(InsnGen.class);
@@ -59,9 +62,10 @@ public class InsnGen {
protected final RootNode root;
protected final boolean fallback;
private static enum Flags {
protected enum Flags {
BODY_ONLY,
BODY_ONLY_NOWRAP,
INLINE
}
public InsnGen(MethodGen mgen, boolean fallback) {
@@ -76,9 +80,9 @@ public class InsnGen {
}
public void addArgDot(CodeWriter code, InsnArg arg) throws CodegenException {
int len = code.length();
int len = code.bufLength();
addArg(code, arg, true);
if (len != code.length()) {
if (len != code.bufLength()) {
code.add('.');
}
}
@@ -89,22 +93,20 @@ public class InsnGen {
public void addArg(CodeWriter code, InsnArg arg, boolean wrap) throws CodegenException {
if (arg.isRegister()) {
code.add(mgen.makeArgName((RegisterArg) arg));
code.add(mgen.getNameGen().useArg((RegisterArg) arg));
} else if (arg.isLiteral()) {
code.add(lit((LiteralArg) arg));
} else if (arg.isInsnWrap()) {
Flags flag = wrap ? Flags.BODY_ONLY : Flags.BODY_ONLY_NOWRAP;
makeInsn(((InsnWrapArg) arg).getWrapInsn(), code, flag);
} else if (arg.isNamed()) {
code.add(((NamedArg) arg).getName());
code.add(((Named) arg).getName());
} else if (arg.isField()) {
FieldArg f = (FieldArg) arg;
if (f.isStatic()) {
code.add(staticField(f.getField()));
staticField(code, f.getField());
} else {
RegisterArg regArg = new RegisterArg(f.getRegNum());
regArg.replaceTypedVar(f);
instanceField(code, f.getField(), regArg);
instanceField(code, f.getField(), f.getInstanceArg());
}
} else {
throw new CodegenException("Unknown arg type " + arg);
@@ -113,7 +115,7 @@ public class InsnGen {
public void assignVar(CodeWriter code, InsnNode insn) throws CodegenException {
RegisterArg arg = insn.getResult();
if (insn.getAttributes().contains(AttributeFlag.DECLARE_VAR)) {
if (insn.contains(AFlag.DECLARE_VAR)) {
declareVar(code, arg);
} else {
addArg(code, arg, false);
@@ -121,80 +123,104 @@ public class InsnGen {
}
public void declareVar(CodeWriter code, RegisterArg arg) {
code.add(useType(arg.getType()));
if (arg.getSVar().contains(AFlag.FINAL)) {
code.add("final ");
}
useType(code, arg.getType());
code.add(' ');
code.add(mgen.assignArg(arg));
code.add(mgen.getNameGen().assignArg(arg));
}
private static String lit(LiteralArg arg) {
return TypeGen.literalToString(arg.getLiteral(), arg.getType());
private String lit(LiteralArg arg) {
return TypeGen.literalToString(arg.getLiteral(), arg.getType(), mth);
}
private void instanceField(CodeWriter code, FieldInfo field, InsnArg arg) throws CodegenException {
FieldNode fieldNode = mth.getParentClass().searchField(field);
ClassNode pCls = mth.getParentClass();
FieldNode fieldNode = pCls.searchField(field);
while (fieldNode == null
&& pCls.getParentClass() != pCls
&& pCls.getParentClass() != null) {
pCls = pCls.getParentClass();
fieldNode = pCls.searchField(field);
}
if (fieldNode != null) {
FieldReplaceAttr replace = (FieldReplaceAttr) fieldNode.getAttributes().get(AttributeType.FIELD_REPLACE);
FieldReplaceAttr replace = fieldNode.get(AType.FIELD_REPLACE);
if (replace != null) {
FieldInfo info = replace.getFieldInfo();
if (replace.isOuterClass()) {
code.add(useClass(info.getDeclClass())).add(".this");
switch (replace.getReplaceType()) {
case CLASS_INSTANCE:
useClass(code, replace.getClsRef());
code.add(".this");
break;
case VAR:
addArg(code, replace.getVarRef());
break;
}
return;
}
}
addArgDot(code, arg);
code.add(field.getName());
if (fieldNode != null) {
code.attachAnnotation(fieldNode);
}
code.add(field.getAlias());
}
public static String makeStaticFieldAccess(FieldInfo field, ClassGen clsGen) {
public static void makeStaticFieldAccess(CodeWriter code, FieldInfo field, ClassGen clsGen) {
ClassInfo declClass = field.getDeclClass();
if (clsGen.getClassNode().getFullName().startsWith(declClass.getFullName())) {
return field.getName();
boolean fieldFromThisClass = clsGen.getClassNode().getClassInfo().equals(declClass);
if (!fieldFromThisClass) {
// Android specific resources class handler
if (!handleAppResField(code, clsGen, declClass)) {
clsGen.useClass(code, declClass);
}
code.add('.');
}
// Android specific resources class handler
ClassInfo parentClass = declClass.getParentClass();
if (parentClass != null && parentClass.getShortName().equals("R")) {
return clsGen.useClass(parentClass) + "." + declClass.getShortName() + "." + field.getName();
FieldNode fieldNode = clsGen.getClassNode().dex().resolveField(field);
if (fieldNode != null) {
code.attachAnnotation(fieldNode);
}
return clsGen.useClass(declClass) + '.' + field.getName();
code.add(field.getAlias());
}
protected String staticField(FieldInfo field) {
return makeStaticFieldAccess(field, mgen.getClassGen());
protected void staticField(CodeWriter code, FieldInfo field) {
makeStaticFieldAccess(code, field, mgen.getClassGen());
}
public String useClass(ClassInfo cls) {
return mgen.getClassGen().useClass(cls);
public void useClass(CodeWriter code, ArgType type) {
mgen.getClassGen().useClass(code, type);
}
private String useType(ArgType type) {
return TypeGen.translate(mgen.getClassGen(), type);
public void useClass(CodeWriter code, ClassInfo cls) {
mgen.getClassGen().useClass(code, cls);
}
protected void useType(CodeWriter code, ArgType type) {
mgen.getClassGen().useType(code, type);
}
public boolean makeInsn(InsnNode insn, CodeWriter code) throws CodegenException {
return makeInsn(insn, code, null);
}
private boolean makeInsn(InsnNode insn, CodeWriter code, Flags flag) throws CodegenException {
protected boolean makeInsn(InsnNode insn, CodeWriter code, Flags flag) throws CodegenException {
try {
if (insn.getType() == InsnType.NOP) {
return false;
}
EnumSet<Flags> state = EnumSet.noneOf(Flags.class);
Set<Flags> state = EnumSet.noneOf(Flags.class);
if (flag == Flags.BODY_ONLY || flag == Flags.BODY_ONLY_NOWRAP) {
state.add(flag);
makeInsnBody(code, insn, state);
} else {
code.startLine();
if (insn.getSourceLine() != 0) {
code.attachAnnotation(insn.getSourceLine());
if (flag != Flags.INLINE) {
code.startLineWithNum(insn.getSourceLine());
}
if (insn.getResult() != null && insn.getType() != InsnType.ARITH_ONEARG) {
if (insn.getResult() != null && !insn.contains(AFlag.ARITH_ONEARG)) {
assignVar(code, insn);
code.add(" = ");
}
makeInsnBody(code, insn, state);
code.add(';');
if (flag != Flags.INLINE) {
code.add(';');
}
}
} catch (Throwable th) {
throw new CodegenException(mth, "Error generate insn: " + insn, th);
@@ -202,16 +228,17 @@ public class InsnGen {
return true;
}
private void makeInsnBody(CodeWriter code, InsnNode insn, EnumSet<Flags> state) throws CodegenException {
private void makeInsnBody(CodeWriter code, InsnNode insn, Set<Flags> state) throws CodegenException {
switch (insn.getType()) {
case CONST_STR:
String str = ((ConstStringNode) insn).getString();
code.add(StringUtils.unescapeString(str));
code.add(mth.dex().root().getStringUtils().unescapeString(str));
break;
case CONST_CLASS:
ArgType clsType = ((ConstClassNode) insn).getClsType();
code.add(useType(clsType)).add(".class");
useType(code, clsType);
code.add(".class");
break;
case CONST:
@@ -230,7 +257,7 @@ public class InsnGen {
code.add('(');
}
code.add('(');
code.add(useType((ArgType) ((IndexInsnNode) insn).getIndex()));
useType(code, (ArgType) ((IndexInsnNode) insn).getIndex());
code.add(") ");
addArg(code, insn.getArg(0), true);
if (wrap) {
@@ -243,10 +270,6 @@ public class InsnGen {
makeArith((ArithNode) insn, code, state);
break;
case ARITH_ONEARG:
makeArithOneArg((ArithNode) insn, code, state);
break;
case NEG: {
boolean wrap = state.contains(Flags.BODY_ONLY);
if (wrap) {
@@ -271,6 +294,10 @@ public class InsnGen {
case BREAK:
code.add("break");
LoopLabelAttr labelAttr = insn.get(AType.LOOP_LABEL);
if (labelAttr != null) {
code.add(' ').add(mgen.getNameGen().getLoopLabel(labelAttr));
}
break;
case CONTINUE:
@@ -292,7 +319,7 @@ public class InsnGen {
addArg(code, insn.getArg(0));
code.add(" == ");
addArg(code, insn.getArg(1));
code.add("? 0 : -1))");
code.add(" ? 0 : -1))");
break;
case INSTANCE_OF: {
@@ -302,14 +329,14 @@ public class InsnGen {
}
addArg(code, insn.getArg(0));
code.add(" instanceof ");
code.add(useType((ArgType) ((IndexInsnNode) insn).getIndex()));
useType(code, (ArgType) ((IndexInsnNode) insn).getIndex());
if (wrap) {
code.add(')');
}
break;
}
case CONSTRUCTOR:
makeConstructor((ConstructorInsn) insn, code, state);
makeConstructor((ConstructorInsn) insn, code);
break;
case INVOKE:
@@ -317,8 +344,9 @@ public class InsnGen {
break;
case NEW_ARRAY: {
ArgType arrayType = insn.getResult().getType();
code.add("new ").add(useType(arrayType.getArrayRootElement()));
ArgType arrayType = ((NewArrayNode) insn).getArrayType();
code.add("new ");
useType(code, arrayType.getArrayRootElement());
code.add('[');
addArg(code, insn.getArg(0));
code.add(']');
@@ -334,12 +362,8 @@ public class InsnGen {
code.add(".length");
break;
case FILL_ARRAY:
fillArray((FillArrayNode) insn, code);
break;
case FILLED_NEW_ARRAY:
filledNewArray(insn, code);
filledNewArray((FilledNewArrayNode) insn, code);
break;
case AGET:
@@ -371,11 +395,12 @@ public class InsnGen {
}
case SGET:
code.add(staticField((FieldInfo) ((IndexInsnNode) insn).getIndex()));
staticField(code, (FieldInfo) ((IndexInsnNode) insn).getIndex());
break;
case SPUT:
FieldInfo field = (FieldInfo) ((IndexInsnNode) insn).getIndex();
code.add(staticField(field)).add(" = ");
staticField(code, field);
code.add(" = ");
addArg(code, insn.getArg(0), false);
break;
@@ -411,25 +436,17 @@ public class InsnGen {
}
break;
case MOVE_EXCEPTION:
if (isFallback()) {
code.add("move-exception");
} else {
addArg(code, insn.getArg(0));
}
break;
case TERNARY:
makeTernary((TernaryInsn) insn, code, state);
break;
case ARGS:
case ONE_ARG:
addArg(code, insn.getArg(0));
break;
/* fallback mode instructions */
case IF:
assert isFallback() : "if insn in not fallback mode";
fallbackOnlyInsn(insn);
IfNode ifInsn = (IfNode) insn;
code.add("if (");
addArg(code, insn.getArg(0));
@@ -440,19 +457,25 @@ public class InsnGen {
break;
case GOTO:
assert isFallback();
fallbackOnlyInsn(insn);
code.add("goto ").add(MethodGen.getLabelName(((GotoNode) insn).getTarget()));
break;
case MOVE_EXCEPTION:
fallbackOnlyInsn(insn);
code.add("move-exception");
break;
case SWITCH:
assert isFallback();
fallbackOnlyInsn(insn);
SwitchNode sw = (SwitchNode) insn;
code.add("switch(");
addArg(code, insn.getArg(0));
code.add(") {");
code.incIndent();
for (int i = 0; i < sw.getCasesCount(); i++) {
code.startLine("case ").add(sw.getKeys()[i]).add(": goto ");
String key = sw.getKeys()[i].toString();
code.startLine("case ").add(key).add(": goto ");
code.add(MethodGen.getLabelName(sw.getTargets()[i])).add(';');
}
code.startLine("default: goto ");
@@ -461,10 +484,40 @@ public class InsnGen {
code.startLine('}');
break;
case FILL_ARRAY:
fallbackOnlyInsn(insn);
FillArrayNode arrayNode = (FillArrayNode) insn;
Object data = arrayNode.getData();
String arrStr;
if (data instanceof int[]) {
arrStr = Arrays.toString((int[]) data);
} else if (data instanceof short[]) {
arrStr = Arrays.toString((short[]) data);
} else if (data instanceof byte[]) {
arrStr = Arrays.toString((byte[]) data);
} else if (data instanceof long[]) {
arrStr = Arrays.toString((long[]) data);
} else {
arrStr = "?";
}
code.add('{').add(arrStr.substring(1, arrStr.length() - 1)).add('}');
break;
case NEW_INSTANCE:
// only fallback - make new instance in constructor invoke
assert isFallback();
code.add("new " + insn.getResult().getType());
fallbackOnlyInsn(insn);
code.add("new ").add(insn.getResult().getType().toString());
break;
case PHI:
case MERGE:
fallbackOnlyInsn(insn);
code.add(insn.getType().toString()).add("(");
for (InsnArg insnArg : insn.getArguments()) {
addArg(code, insnArg);
code.add(' ');
}
code.add(")");
break;
default:
@@ -472,12 +525,19 @@ public class InsnGen {
}
}
private void filledNewArray(InsnNode insn, CodeWriter code) throws CodegenException {
int c = insn.getArgsCount();
code.add("new ").add(useType(insn.getResult().getType()));
private void fallbackOnlyInsn(InsnNode insn) throws CodegenException {
if (!fallback) {
throw new CodegenException(insn.getType() + " can be used only in fallback mode");
}
}
private void filledNewArray(FilledNewArrayNode insn, CodeWriter code) throws CodegenException {
code.add("new ");
useType(code, insn.getArrayType());
code.add('{');
int c = insn.getArgsCount();
for (int i = 0; i < c; i++) {
addArg(code, insn.getArg(i));
addArg(code, insn.getArg(i), false);
if (i + 1 < c) {
code.add(", ");
}
@@ -485,83 +545,11 @@ public class InsnGen {
code.add('}');
}
private void fillArray(FillArrayNode insn, CodeWriter code) throws CodegenException {
ArgType insnArrayType = insn.getResult().getType();
ArgType insnElementType = insnArrayType.getArrayElement();
ArgType elType = insn.getElementType();
if (!elType.equals(insnElementType) && !insnArrayType.equals(ArgType.OBJECT)) {
ErrorsCounter.methodError(mth,
"Incorrect type for fill-array insn " + InsnUtils.formatOffset(insn.getOffset())
+ ", element type: " + elType + ", insn element type: " + insnElementType
);
if (!elType.isTypeKnown()) {
elType = insnElementType.isTypeKnown() ? insnElementType : elType.selectFirst();
}
}
StringBuilder str = new StringBuilder();
Object data = insn.getData();
switch (elType.getPrimitiveType()) {
case BOOLEAN:
case BYTE:
byte[] array = (byte[]) data;
for (byte b : array) {
str.append(TypeGen.literalToString(b, elType));
str.append(", ");
}
break;
case SHORT:
case CHAR:
short[] sarray = (short[]) data;
for (short b : sarray) {
str.append(TypeGen.literalToString(b, elType));
str.append(", ");
}
break;
case INT:
case FLOAT:
int[] iarray = (int[]) data;
for (int b : iarray) {
str.append(TypeGen.literalToString(b, elType));
str.append(", ");
}
break;
case LONG:
case DOUBLE:
long[] larray = (long[]) data;
for (long b : larray) {
str.append(TypeGen.literalToString(b, elType));
str.append(", ");
}
break;
default:
throw new CodegenException(mth, "Unknown type: " + elType);
}
int len = str.length();
str.delete(len - 2, len);
code.add("new ").add(useType(elType)).add("[]{").add(str.toString()).add('}');
}
private void makeConstructor(ConstructorInsn insn, CodeWriter code, EnumSet<Flags> state)
private void makeConstructor(ConstructorInsn insn, CodeWriter code)
throws CodegenException {
ClassNode cls = mth.dex().resolveClass(insn.getClassType());
if (cls != null && cls.isAnonymous()) {
// anonymous class construction
ClassInfo parent;
if (cls.getInterfaces().size() == 1) {
parent = cls.getInterfaces().get(0);
} else {
parent = cls.getSuperClass();
}
cls.getAttributes().add(AttributeFlag.DONT_GENERATE);
MethodNode defCtr = cls.getDefaultConstructor();
if (RegionUtils.notEmpty(defCtr.getRegion())) {
defCtr.getAttributes().add(AttributeFlag.ANONYMOUS_CONSTRUCTOR);
} else {
defCtr.getAttributes().add(AttributeFlag.DONT_GENERATE);
}
code.add("new ").add(parent == null ? "Object" : useClass(parent)).add("() ");
new ClassGen(cls, mgen.getClassGen().getParentGen(), fallback).addClassBody(code);
if (cls != null && cls.contains(AFlag.ANONYMOUS_CLASS) && !fallback) {
inlineAnonymousConstr(code, cls, insn);
return;
}
if (insn.isSelf()) {
@@ -572,20 +560,57 @@ public class InsnGen {
} else if (insn.isThis()) {
code.add("this");
} else {
code.add("new ").add(useClass(insn.getClassType()));
code.add("new ");
useClass(code, insn.getClassType());
}
generateArguments(code, insn, 0, mth.dex().resolveMethod(insn.getCallMth()));
MethodNode callMth = mth.dex().resolveMethod(insn.getCallMth());
generateMethodArguments(code, insn, 0, callMth);
}
private void inlineAnonymousConstr(CodeWriter code, ClassNode cls, ConstructorInsn insn) throws CodegenException {
// anonymous class construction
if (cls.contains(AFlag.DONT_GENERATE)) {
code.add("/* anonymous class already generated */");
ErrorsCounter.methodError(mth, "Anonymous class already generated: " + cls);
return;
}
ArgType parent;
if (cls.getInterfaces().size() == 1) {
parent = cls.getInterfaces().get(0);
} else {
parent = cls.getSuperClass();
}
cls.add(AFlag.DONT_GENERATE);
MethodNode defCtr = cls.getDefaultConstructor();
if (defCtr != null) {
if (RegionUtils.notEmpty(defCtr.getRegion())) {
defCtr.add(AFlag.ANONYMOUS_CONSTRUCTOR);
} else {
defCtr.add(AFlag.DONT_GENERATE);
}
}
code.add("new ");
if (parent == null) {
code.add("Object");
} else {
useClass(code, parent);
}
MethodNode callMth = mth.dex().resolveMethod(insn.getCallMth());
generateMethodArguments(code, insn, 0, callMth);
code.add(' ');
new ClassGen(cls, mgen.getClassGen().getParentGen()).addClassBody(code);
}
private void makeInvoke(InvokeNode insn, CodeWriter code) throws CodegenException {
MethodInfo callMth = insn.getCallMth();
// inline method
MethodNode callMthNode = mth.dex().resolveMethod(callMth);
if (callMthNode != null
&& callMthNode.getAttributes().contains(AttributeType.METHOD_INLINE)) {
inlineMethod(callMthNode, insn, code);
return;
MethodNode callMthNode = mth.dex().deepResolveMethod(callMth);
if (callMthNode != null) {
if (inlineMethod(callMthNode, insn, code)) {
return;
}
callMth = callMthNode.getMethodInfo();
}
int k = 0;
@@ -609,61 +634,111 @@ public class InsnGen {
break;
case STATIC:
ClassInfo insnCls = mth.getParentClass().getClassInfo();
ClassInfo insnCls = mth.getParentClass().getAlias();
ClassInfo declClass = callMth.getDeclClass();
if (!insnCls.equals(declClass)) {
code.add(useClass(declClass)).add('.');
useClass(code, declClass);
code.add('.');
}
break;
}
if (callMthNode != null) {
code.attachAnnotation(callMthNode);
}
code.add(callMth.getName());
generateArguments(code, insn, k, callMthNode);
code.add(callMth.getAlias());
generateMethodArguments(code, insn, k, callMthNode);
}
private void generateArguments(CodeWriter code, InsnNode insn, int k, MethodNode callMth) throws CodegenException {
if (callMth != null && callMth.getAttributes().contains(AttributeFlag.SKIP_FIRST_ARG)) {
void generateMethodArguments(CodeWriter code, InsnNode insn, int startArgNum,
@Nullable MethodNode callMth) throws CodegenException {
int k = startArgNum;
if (callMth != null && callMth.contains(AFlag.SKIP_FIRST_ARG)) {
k++;
}
int argsCount = insn.getArgsCount();
if (callMth != null && callMth.isArgsOverload()) {
// add additional argument casts for overloaded methods
List<ArgType> originalType = callMth.getMethodInfo().getArgumentsTypes();
int origPos = 0;
code.add('(');
code.add('(');
boolean firstArg = true;
if (k < argsCount) {
boolean overloaded = callMth != null && callMth.isArgsOverload();
for (int i = k; i < argsCount; i++) {
InsnArg arg = insn.getArg(i);
ArgType origType = originalType.get(origPos);
if (!arg.getType().equals(origType)) {
code.add('(').add(useType(origType)).add(')');
addArg(code, arg, true);
} else {
addArg(code, arg, false);
if (arg.contains(AFlag.SKIP_ARG)) {
continue;
}
if (i < argsCount - 1) {
RegisterArg callArg = getCallMthArg(callMth, i - startArgNum);
if (callArg != null && callArg.contains(AFlag.SKIP_ARG)) {
continue;
}
if (!firstArg) {
code.add(", ");
}
origPos++;
}
code.add(')');
} else {
code.add('(');
if (k < argsCount) {
addArg(code, insn.getArg(k), false);
for (int i = k + 1; i < argsCount; i++) {
code.add(", ");
addArg(code, insn.getArg(i), false);
boolean cast = overloaded && processOverloadedArg(code, callMth, arg, i - startArgNum);
if (!cast && i == argsCount - 1 && processVarArg(code, callMth, arg)) {
continue;
}
addArg(code, arg, false);
firstArg = false;
}
code.add(')');
}
code.add(')');
}
private void inlineMethod(MethodNode callMthNode, InvokeNode insn, CodeWriter code) throws CodegenException {
IAttribute mia = callMthNode.getAttributes().get(AttributeType.METHOD_INLINE);
InsnNode inl = ((MethodInlineAttr) mia).getInsn();
private static RegisterArg getCallMthArg(@Nullable MethodNode callMth, int num) {
if (callMth == null) {
return null;
}
List<RegisterArg> args = callMth.getArguments(false);
if (args != null && num < args.size()) {
return args.get(num);
}
return null;
}
/**
* Add additional cast for overloaded method argument.
*/
private boolean processOverloadedArg(CodeWriter code, MethodNode callMth, InsnArg arg, int origPos) {
ArgType origType = callMth.getMethodInfo().getArgumentsTypes().get(origPos);
if (!arg.getType().equals(origType)) {
code.add('(');
useType(code, origType);
code.add(") ");
return true;
}
return false;
}
/**
* Expand varArgs from filled array.
*/
private boolean processVarArg(CodeWriter code, MethodNode callMth, InsnArg lastArg) throws CodegenException {
if (callMth == null || !callMth.getAccessFlags().isVarArgs()) {
return false;
}
if (!lastArg.getType().isArray() || !lastArg.isInsnWrap()) {
return false;
}
InsnNode insn = ((InsnWrapArg) lastArg).getWrapInsn();
if (insn.getType() == InsnType.FILLED_NEW_ARRAY) {
int count = insn.getArgsCount();
for (int i = 0; i < count; i++) {
InsnArg elemArg = insn.getArg(i);
addArg(code, elemArg, false);
if (i < count - 1) {
code.add(", ");
}
}
return true;
}
return false;
}
private boolean inlineMethod(MethodNode callMthNode, InvokeNode insn, CodeWriter code) throws CodegenException {
MethodInlineAttr mia = callMthNode.get(AType.METHOD_INLINE);
if (mia == null) {
return false;
}
InsnNode inl = mia.getInsn();
if (callMthNode.getMethodInfo().getArgumentsTypes().isEmpty()) {
makeInsn(inl, code, Flags.BODY_ONLY);
} else {
@@ -676,31 +751,28 @@ public class InsnGen {
regs[callArg.getRegNum()] = arg;
}
// replace args
InsnNode inlCopy = inl.copy();
List<RegisterArg> inlArgs = new ArrayList<RegisterArg>();
inl.getRegisterArgs(inlArgs);
Map<RegisterArg, InsnArg> toRevert = new HashMap<RegisterArg, InsnArg>();
inlCopy.getRegisterArgs(inlArgs);
for (RegisterArg r : inlArgs) {
if (r.getRegNum() >= regs.length) {
LOG.warn("Unknown register number {} in method call: {}, {}", r, callMthNode, mth);
int regNum = r.getRegNum();
if (regNum >= regs.length) {
LOG.warn("Unknown register number {} in method call: {} from {}", r, callMthNode, mth);
} else {
InsnArg repl = regs[r.getRegNum()];
InsnArg repl = regs[regNum];
if (repl == null) {
LOG.warn("Not passed register {} in method call: {}, {}", r, callMthNode, mth);
LOG.warn("Not passed register {} in method call: {} from {}", r, callMthNode, mth);
} else {
inl.replaceArg(r, repl);
toRevert.put(r, repl);
inlCopy.replaceArg(r, repl);
}
}
}
makeInsn(inl, code, Flags.BODY_ONLY);
// revert changes
for (Map.Entry<RegisterArg, InsnArg> e : toRevert.entrySet()) {
inl.replaceArg(e.getValue(), e.getKey());
}
makeInsn(inlCopy, code, Flags.BODY_ONLY);
}
return true;
}
private void makeTernary(TernaryInsn insn, CodeWriter code, EnumSet<Flags> state) throws CodegenException {
private void makeTernary(TernaryInsn insn, CodeWriter code, Set<Flags> state) throws CodegenException {
boolean wrap = state.contains(Flags.BODY_ONLY);
if (wrap) {
code.add('(');
@@ -722,10 +794,13 @@ public class InsnGen {
}
}
private void makeArith(ArithNode insn, CodeWriter code, EnumSet<Flags> state) throws CodegenException {
private void makeArith(ArithNode insn, CodeWriter code, Set<Flags> state) throws CodegenException {
if (insn.contains(AFlag.ARITH_ONEARG)) {
makeArithOneArg(insn, code);
return;
}
// wrap insn in brackets for save correct operation order
boolean wrap = state.contains(Flags.BODY_ONLY)
&& !insn.getAttributes().contains(AttributeFlag.DONT_WRAP);
boolean wrap = state.contains(Flags.BODY_ONLY) && !insn.contains(AFlag.DONT_WRAP);
if (wrap) {
code.add('(');
}
@@ -739,9 +814,9 @@ public class InsnGen {
}
}
private void makeArithOneArg(ArithNode insn, CodeWriter code, EnumSet<Flags> state) throws CodegenException {
private void makeArithOneArg(ArithNode insn, CodeWriter code) throws CodegenException {
ArithOp op = insn.getOp();
InsnArg arg = insn.getArg(0);
InsnArg arg = insn.getArg(1);
// "++" or "--"
if (arg.isLiteral() && (op == ArithOp.ADD || op == ArithOp.SUB)) {
LiteralArg lit = (LiteralArg) arg;
@@ -1,18 +1,16 @@
package jadx.core.codegen;
import jadx.core.Consts;
import jadx.core.dex.attributes.AttributeFlag;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.AttributesList;
import jadx.core.dex.attributes.JadxErrorAttr;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.annotations.MethodParameters;
import jadx.core.dex.attributes.nodes.JadxErrorAttr;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.NamedArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.regions.Region;
import jadx.core.dex.trycatch.CatchAttr;
import jadx.core.dex.visitors.DepthTraversal;
import jadx.core.dex.visitors.FallbackModeVisitor;
@@ -22,10 +20,8 @@ import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.DecodeException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -37,38 +33,35 @@ public class MethodGen {
private final MethodNode mth;
private final ClassGen classGen;
private final boolean fallback;
private final AnnotationGen annotationGen;
private final Set<String> varNames = new HashSet<String>();
private final NameGen nameGen;
public MethodGen(ClassGen classGen, MethodNode mth) {
this.mth = mth;
this.classGen = classGen;
this.fallback = classGen.isFallbackMode();
this.annotationGen = classGen.getAnnotationGen();
List<RegisterArg> args = mth.getArguments(true);
for (RegisterArg arg : args) {
varNames.add(makeArgName(arg));
}
this.nameGen = new NameGen(mth, classGen.isFallbackMode());
}
public ClassGen getClassGen() {
return classGen;
}
public NameGen getNameGen() {
return nameGen;
}
public MethodNode getMethodNode() {
return mth;
}
public boolean addDefinition(CodeWriter code) {
if (mth.getMethodInfo().isClassInit()) {
code.startLine("static");
code.attachDefinition(mth);
code.startLine("static");
return true;
}
if (mth.getAttributes().contains(AttributeFlag.ANONYMOUS_CONSTRUCTOR)) {
if (mth.contains(AFlag.ANONYMOUS_CONSTRUCTOR)) {
// don't add method name and arguments
code.startLine();
code.attachDefinition(mth);
@@ -78,31 +71,35 @@ public class MethodGen {
AccessInfo clsAccFlags = mth.getParentClass().getAccessFlags();
AccessInfo ai = mth.getAccessFlags();
// don't add 'abstract' to methods in interface
// don't add 'abstract' and 'public' to methods in interface
if (clsAccFlags.isInterface()) {
ai = ai.remove(AccessFlags.ACC_ABSTRACT);
ai = ai.remove(AccessFlags.ACC_PUBLIC);
}
// don't add 'public' for annotations
if (clsAccFlags.isAnnotation()) {
ai = ai.remove(AccessFlags.ACC_PUBLIC);
}
code.startLine(ai.makeString());
code.startLineWithNum(mth.getSourceLine());
code.add(ai.makeString());
if (classGen.addGenericMap(code, mth.getGenericMap())) {
code.add(' ');
}
if (mth.getAccessFlags().isConstructor()) {
code.attachDefinition(mth);
code.add(classGen.getClassNode().getShortName()); // constructor
} else {
code.add(TypeGen.translate(classGen, mth.getReturnType()));
classGen.useType(code, mth.getReturnType());
code.add(' ');
code.add(mth.getName());
code.attachDefinition(mth);
code.add(mth.getAlias());
}
code.add('(');
List<RegisterArg> args = mth.getArguments(false);
if (mth.getMethodInfo().isConstructor()
&& mth.getParentClass().getAttributes().contains(AttributeType.ENUM_CLASS)) {
&& mth.getParentClass().contains(AType.ENUM_CLASS)) {
if (args.size() == 2) {
args.clear();
} else if (args.size() > 2) {
@@ -118,14 +115,11 @@ public class MethodGen {
code.add(')');
annotationGen.addThrows(mth, code);
code.attachDefinition(mth);
return true;
}
private void addMethodArguments(CodeWriter argsCode, List<RegisterArg> args) {
MethodParameters paramsAnnotation =
(MethodParameters) mth.getAttributes().get(AttributeType.ANNOTATION_MTH_PARAMETERS);
MethodParameters paramsAnnotation = mth.get(AType.ANNOTATION_MTH_PARAMETERS);
int i = 0;
for (Iterator<RegisterArg> it = args.iterator(); it.hasNext(); ) {
RegisterArg arg = it.next();
@@ -134,22 +128,26 @@ public class MethodGen {
if (paramsAnnotation != null) {
annotationGen.addForParameter(argsCode, paramsAnnotation, i);
}
SSAVar argSVar = arg.getSVar();
if (argSVar!= null && argSVar.contains(AFlag.FINAL)) {
argsCode.add("final ");
}
if (!it.hasNext() && mth.getAccessFlags().isVarArgs()) {
// change last array argument to varargs
ArgType type = arg.getType();
if (type.isArray()) {
ArgType elType = type.getArrayElement();
argsCode.add(TypeGen.translate(classGen, elType));
argsCode.add(" ...");
classGen.useType(argsCode, elType);
argsCode.add("...");
} else {
LOG.warn(ErrorsCounter.formatErrorMsg(mth, "Last argument in varargs method not array"));
argsCode.add(TypeGen.translate(classGen, arg.getType()));
classGen.useType(argsCode, arg.getType());
}
} else {
argsCode.add(TypeGen.translate(classGen, arg.getType()));
classGen.useType(argsCode, arg.getType());
}
argsCode.add(' ');
argsCode.add(makeArgName(arg));
argsCode.add(nameGen.assignArg(arg));
i++;
if (it.hasNext()) {
@@ -158,160 +156,82 @@ public class MethodGen {
}
}
/**
* Make variable name for register,
* Name contains register number and
* variable type or name (if debug info available)
*/
public String makeArgName(RegisterArg arg) {
String name = arg.getTypedVar().getName();
String base = "r" + arg.getRegNum();
if (fallback) {
if (name != null) {
return base + "_" + name;
}
return base;
} else {
if (name != null) {
if (Consts.DEBUG) {
return base + "_" + name;
}
return name;
} else {
ArgType type = arg.getType();
if (type.isPrimitive()) {
return base + type.getPrimitiveType().getShortName().toLowerCase();
} else {
return base + "_" + Utils.escape(TypeGen.translate(classGen, arg.getType()));
}
}
}
}
/**
* Put variable declaration and return variable name (used for assignments)
*
* @param arg register variable
* @return variable name
*/
public String assignArg(RegisterArg arg) {
String name = makeArgName(arg);
if (varNames.add(name) || fallback) {
return name;
}
name = getUniqVarName(name);
arg.getTypedVar().setName(name);
return name;
}
public String assignNamedArg(NamedArg arg) {
String name = arg.getName();
if (varNames.add(name) || fallback) {
return name;
}
name = getUniqVarName(name);
arg.setName(name);
return name;
}
private String getUniqVarName(String name) {
String r;
int i = 2;
do {
r = name + "_" + i;
i++;
} while (varNames.contains(r));
varNames.add(r);
return r;
}
public void addInstructions(CodeWriter code) throws CodegenException {
if (mth.getAttributes().contains(AttributeType.JADX_ERROR)) {
code.startLine("throw new UnsupportedOperationException(\"Method not decompiled: ");
code.add(mth.toString());
code.add("\");");
JadxErrorAttr err = (JadxErrorAttr) mth.getAttributes().get(AttributeType.JADX_ERROR);
code.startLine("/* JADX: method processing error */");
Throwable cause = err.getCause();
if (cause != null) {
code.newLine();
code.add("/*");
code.startLine("Error: ").add(Utils.getStackTrace(cause));
code.add("*/");
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("*/");
}
}
makeMethodDump(code);
} else if (mth.getAttributes().contains(AttributeFlag.INCONSISTENT_CODE)) {
code.startLine("/*");
addFallbackMethodCode(code);
code.startLine("*/");
code.newLine();
code.startLine("throw new UnsupportedOperationException(\"Method not decompiled: ")
.add(mth.toString())
.add("\");");
} else {
Region startRegion = mth.getRegion();
if (startRegion != null) {
(new RegionGen(this)).makeRegion(code, startRegion);
} else {
addFallbackMethodCode(code);
}
RegionGen regionGen = new RegionGen(this);
regionGen.makeRegion(code, mth.getRegion());
}
}
private void makeMethodDump(CodeWriter code) {
code.startLine("/*");
getFallbackMethodGen(mth).addDefinition(code);
code.add(" {");
code.incIndent();
addFallbackMethodCode(code);
code.decIndent();
code.startLine('}');
code.startLine("*/");
}
public void addFallbackMethodCode(CodeWriter code) {
if (mth.getInstructions() == null) {
// load original instructions
try {
mth.load();
DepthTraversal.visit(new FallbackModeVisitor(), mth);
} catch (DecodeException e) {
LOG.error("Error reload instructions in fallback mode:", e);
code.startLine("// Can't loadFile method instructions: " + e.getMessage());
return;
JadxErrorAttr errorAttr = mth.get(AType.JADX_ERROR);
if (errorAttr == null
|| errorAttr.getCause() == null
|| !errorAttr.getCause().getClass().equals(DecodeException.class)) {
// load original instructions
try {
mth.load();
DepthTraversal.visit(new FallbackModeVisitor(), mth);
} catch (DecodeException e) {
LOG.error("Error reload instructions in fallback mode:", e);
code.startLine("// Can't load method instructions: " + e.getMessage());
return;
}
}
}
List<InsnNode> insns = mth.getInstructions();
if (insns == null) {
InsnNode[] insnArr = mth.getInstructions();
if (insnArr == null) {
code.startLine("// Can't load method instructions.");
return;
}
if (mth.getThisArg() != null) {
code.startLine(getFallbackMethodGen(mth).makeArgName(mth.getThisArg())).add(" = this;");
code.startLine(nameGen.useArg(mth.getThisArg())).add(" = this;");
}
addFallbackInsns(code, mth, insns, true);
addFallbackInsns(code, mth, insnArr, true);
}
public static void addFallbackInsns(CodeWriter code, MethodNode mth, List<InsnNode> insns, boolean addLabels) {
public static void addFallbackInsns(CodeWriter code, MethodNode mth, InsnNode[] insnArr, boolean addLabels) {
InsnGen insnGen = new InsnGen(getFallbackMethodGen(mth), true);
for (InsnNode insn : insns) {
AttributesList attrs = insn.getAttributes();
if (addLabels) {
if (attrs.contains(AttributeType.JUMP)
|| attrs.contains(AttributeType.EXC_HANDLER)) {
code.decIndent();
code.startLine(getLabelName(insn.getOffset()) + ":");
code.incIndent();
}
for (InsnNode insn : insnArr) {
if (insn == null || insn.getType() == InsnType.NOP) {
continue;
}
if (addLabels && (insn.contains(AType.JUMP) || insn.contains(AType.EXC_HANDLER))) {
code.decIndent();
code.startLine(getLabelName(insn.getOffset()) + ":");
code.incIndent();
}
try {
if (insnGen.makeInsn(insn, code)) {
CatchAttr catchAttr = (CatchAttr) attrs.get(AttributeType.CATCH_BLOCK);
CatchAttr catchAttr = insn.get(AType.CATCH_BLOCK);
if (catchAttr != null) {
code.add("\t //" + catchAttr);
code.add("\t " + catchAttr);
}
}
} catch (CodegenException e) {
LOG.debug("Error generate fallback instruction: ", e.getCause());
code.startLine("// error: " + insn);
}
}
@@ -320,8 +240,8 @@ public class MethodGen {
/**
* Return fallback variant of method codegen
*/
private static MethodGen getFallbackMethodGen(MethodNode mth) {
ClassGen clsGen = new ClassGen(mth.getParentClass(), null, true);
public static MethodGen getFallbackMethodGen(MethodNode mth) {
ClassGen clsGen = new ClassGen(mth.getParentClass(), null, true, true);
return new MethodGen(clsGen, mth);
}
@@ -0,0 +1,243 @@
package jadx.core.codegen;
import jadx.core.Consts;
import jadx.core.deobf.NameMapper;
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.InvokeNode;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.InsnWrapArg;
import jadx.core.dex.instructions.args.NamedArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.instructions.mods.ConstructorInsn;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.StringUtils;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class NameGen {
private static final Map<String, String> OBJ_ALIAS;
private final Set<String> varNames = new HashSet<String>();
private final MethodNode mth;
private final boolean fallback;
static {
OBJ_ALIAS = new HashMap<String, String>();
OBJ_ALIAS.put(Consts.CLASS_STRING, "str");
OBJ_ALIAS.put(Consts.CLASS_CLASS, "cls");
OBJ_ALIAS.put(Consts.CLASS_THROWABLE, "th");
OBJ_ALIAS.put(Consts.CLASS_OBJECT, "obj");
OBJ_ALIAS.put("java.util.Iterator", "it");
OBJ_ALIAS.put("java.lang.Boolean", "bool");
OBJ_ALIAS.put("java.lang.Short", "sh");
OBJ_ALIAS.put("java.lang.Integer", "num");
OBJ_ALIAS.put("java.lang.Character", "ch");
OBJ_ALIAS.put("java.lang.Byte", "b");
OBJ_ALIAS.put("java.lang.Float", "f");
OBJ_ALIAS.put("java.lang.Long", "l");
OBJ_ALIAS.put("java.lang.Double", "d");
}
public NameGen(MethodNode mth, boolean fallback) {
this.mth = mth;
this.fallback = fallback;
}
public String assignArg(RegisterArg arg) {
String name = makeArgName(arg);
if (fallback) {
return name;
}
name = getUniqueVarName(name);
arg.setName(name);
return name;
}
public String assignNamedArg(NamedArg arg) {
String name = arg.getName();
if (fallback) {
return name;
}
name = getUniqueVarName(name);
arg.setName(name);
return name;
}
public String useArg(RegisterArg arg) {
String name = arg.getName();
if (name == null || fallback) {
return getFallbackName(arg);
}
return name;
}
// TODO: avoid name collision with variables names
public String getLoopLabel(LoopLabelAttr attr) {
String name = "loop" + attr.getLoop().getId();
varNames.add(name);
return name;
}
private String getUniqueVarName(String name) {
String r = name;
int i = 2;
while (varNames.contains(r)) {
r = name + i;
i++;
}
varNames.add(r);
return r;
}
private String makeArgName(RegisterArg arg) {
if (fallback) {
return getFallbackName(arg);
}
String name = arg.getName();
String varName;
if (name != null) {
if ("this".equals(name)) {
return name;
}
varName = name;
} else {
varName = guessName(arg);
}
if (NameMapper.isReserved(varName)) {
return varName + "R";
}
return varName;
}
private String getFallbackName(RegisterArg arg) {
return "r" + arg.getRegNum();
}
private String guessName(RegisterArg arg) {
SSAVar sVar = arg.getSVar();
if (sVar != null && sVar.getName() == null) {
RegisterArg assignArg = sVar.getAssign();
InsnNode assignInsn = assignArg.getParentInsn();
if (assignInsn != null) {
String name = makeNameFromInsn(assignInsn);
if (name != null && !NameMapper.isReserved(name)) {
assignArg.setName(name);
return name;
}
}
}
return makeNameForType(arg.getType());
}
private String makeNameForType(ArgType type) {
if (type.isPrimitive()) {
return makeNameForPrimitive(type);
} else if (type.isArray()) {
return makeNameForType(type.getArrayRootElement()) + "Arr";
} else {
return makeNameForObject(type);
}
}
private static String makeNameForPrimitive(ArgType type) {
return type.getPrimitiveType().getShortName().toLowerCase();
}
private String makeNameForObject(ArgType type) {
if (type.isObject()) {
String alias = getAliasForObject(type.getObject());
if (alias != null) {
return alias;
}
ClassInfo extClsInfo = ClassInfo.extCls(mth.dex(), type);
String shortName = extClsInfo.getShortName();
String vName = fromName(shortName);
if (vName != null) {
return vName;
}
}
return StringUtils.escape(type.toString());
}
private static String fromName(String name) {
if (name == null || name.isEmpty()) {
return null;
}
if (name.toUpperCase().equals(name)) {
// all characters are upper case
return name.toLowerCase();
}
String v1 = Character.toLowerCase(name.charAt(0)) + name.substring(1);
if (!v1.equals(name)) {
return v1;
}
if (name.length() < 3) {
return name + "Var";
}
return null;
}
private static String getAliasForObject(String name) {
return OBJ_ALIAS.get(name);
}
private String makeNameFromInsn(InsnNode insn) {
switch (insn.getType()) {
case INVOKE:
InvokeNode inv = (InvokeNode) insn;
return makeNameFromInvoke(inv.getCallMth());
case CONSTRUCTOR:
ConstructorInsn co = (ConstructorInsn) insn;
return makeNameForObject(co.getClassType().getType());
case ARRAY_LENGTH:
return "length";
case ARITH:
case TERNARY:
case CAST:
for (InsnArg arg : insn.getArguments()) {
if (arg.isInsnWrap()) {
InsnNode wrapInsn = ((InsnWrapArg) arg).getWrapInsn();
String wName = makeNameFromInsn(wrapInsn);
if (wName != null) {
return wName;
}
}
}
break;
default:
break;
}
return null;
}
private String makeNameFromInvoke(MethodInfo callMth) {
String name = callMth.getName();
if (name.startsWith("get") || name.startsWith("set")) {
return fromName(name.substring(3));
}
ArgType declType = callMth.getDeclClass().getAlias().getType();
if ("iterator".equals(name)) {
return "it";
}
if ("toString".equals(name)) {
return makeNameForType(declType);
}
if ("forName".equals(name) && declType.equals(ArgType.CLASS)) {
return OBJ_ALIAS.get(Consts.CLASS_CLASS);
}
return name;
}
}
@@ -1,33 +1,39 @@
package jadx.core.codegen;
import jadx.core.dex.attributes.AttributeFlag;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.DeclareVariablesAttr;
import jadx.core.dex.attributes.ForceReturnAttr;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
import jadx.core.dex.attributes.nodes.ForceReturnAttr;
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
import jadx.core.dex.instructions.SwitchNode;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.NamedArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.IBlock;
import jadx.core.dex.nodes.IContainer;
import jadx.core.dex.nodes.IRegion;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.regions.IfCondition;
import jadx.core.dex.regions.IfRegion;
import jadx.core.dex.regions.LoopRegion;
import jadx.core.dex.nodes.parser.FieldInitAttr;
import jadx.core.dex.regions.Region;
import jadx.core.dex.regions.SwitchRegion;
import jadx.core.dex.regions.SynchronizedRegion;
import jadx.core.dex.trycatch.CatchAttr;
import jadx.core.dex.regions.TryCatchRegion;
import jadx.core.dex.regions.conditions.IfCondition;
import jadx.core.dex.regions.conditions.IfRegion;
import jadx.core.dex.regions.loops.ForEachLoop;
import jadx.core.dex.regions.loops.ForLoop;
import jadx.core.dex.regions.loops.LoopRegion;
import jadx.core.dex.regions.loops.LoopType;
import jadx.core.dex.trycatch.ExceptionHandler;
import jadx.core.dex.trycatch.TryCatchBlock;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.RegionUtils;
import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -43,17 +49,21 @@ public class RegionGen extends InsnGen {
if (cont instanceof IBlock) {
makeSimpleBlock((IBlock) cont, code);
} else if (cont instanceof IRegion) {
declareVars(code, cont);
if (cont instanceof Region) {
makeSimpleRegion(code, (Region) cont);
} else if (cont instanceof IfRegion) {
makeIf((IfRegion) cont, code, true);
} else if (cont instanceof SwitchRegion) {
makeSwitch((SwitchRegion) cont, code);
} else if (cont instanceof LoopRegion) {
makeLoop((LoopRegion) cont, code);
} else if (cont instanceof SynchronizedRegion) {
makeSynchronizedRegion((SynchronizedRegion) cont, code);
} else {
declareVars(code, cont);
if (cont instanceof IfRegion) {
makeIf((IfRegion) cont, code, true);
} else if (cont instanceof SwitchRegion) {
makeSwitch((SwitchRegion) cont, code);
} else if (cont instanceof LoopRegion) {
makeLoop((LoopRegion) cont, code);
} else if (cont instanceof TryCatchRegion) {
makeTryCatch((TryCatchRegion) cont, code);
} else if (cont instanceof SynchronizedRegion) {
makeSynchronizedRegion((SynchronizedRegion) cont, code);
}
}
} else {
throw new CodegenException("Not processed container: " + cont);
@@ -61,8 +71,7 @@ public class RegionGen extends InsnGen {
}
private void declareVars(CodeWriter code, IContainer cont) {
DeclareVariablesAttr declVars =
(DeclareVariablesAttr) cont.getAttributes().get(AttributeType.DECLARE_VARIABLES);
DeclareVariablesAttr declVars = cont.get(AType.DECLARE_VARIABLES);
if (declVars != null) {
for (RegisterArg v : declVars.getVars()) {
code.startLine();
@@ -73,13 +82,9 @@ public class RegionGen extends InsnGen {
}
private void makeSimpleRegion(CodeWriter code, Region region) throws CodegenException {
CatchAttr tc = (CatchAttr) region.getAttributes().get(AttributeType.CATCH_BLOCK);
if (tc != null) {
makeTryCatch(region, tc.getTryBlock(), code);
} else {
for (IContainer c : region.getSubBlocks()) {
makeRegion(code, c);
}
declareVars(code, region);
for (IContainer c : region.getSubBlocks()) {
makeRegion(code, c);
}
}
@@ -91,22 +96,21 @@ public class RegionGen extends InsnGen {
private void makeSimpleBlock(IBlock block, CodeWriter code) throws CodegenException {
for (InsnNode insn : block.getInstructions()) {
makeInsn(insn, code);
if (!insn.contains(AFlag.SKIP)) {
makeInsn(insn, code);
}
}
IAttribute attr;
if ((attr = block.getAttributes().get(AttributeType.FORCE_RETURN)) != null) {
ForceReturnAttr retAttr = (ForceReturnAttr) attr;
ForceReturnAttr retAttr = block.get(AType.FORCE_RETURN);
if (retAttr != null) {
makeInsn(retAttr.getReturnInsn(), code);
}
}
private void makeIf(IfRegion region, CodeWriter code, boolean newLine) throws CodegenException {
if (region.getTernRegion() != null) {
makeSimpleBlock(region.getTernRegion().getBlock(), code);
return;
}
if (newLine) {
code.startLine();
code.startLineWithNum(region.getSourceLine());
} else {
code.attachSourceLine(region.getSourceLine());
}
code.add("if (");
new ConditionGen(this).add(code, region.getCondition());
@@ -130,16 +134,17 @@ public class RegionGen extends InsnGen {
* Connect if-else-if block
*/
private boolean connectElseIf(CodeWriter code, IContainer els) throws CodegenException {
if (els instanceof Region) {
Region re = (Region) els;
List<IContainer> subBlocks = re.getSubBlocks();
if (subBlocks.size() == 1 && subBlocks.get(0) instanceof IfRegion) {
IfRegion ifRegion = (IfRegion) subBlocks.get(0);
if (ifRegion.getAttributes().contains(AttributeFlag.ELSE_IF_CHAIN)) {
makeIf(ifRegion, code, false);
return true;
}
}
if (!els.contains(AFlag.ELSE_IF_CHAIN)) {
return false;
}
if (!(els instanceof Region)) {
return false;
}
List<IContainer> subBlocks = ((Region) els).getSubBlocks();
if (subBlocks.size() == 1
&& subBlocks.get(0) instanceof IfRegion) {
makeIf((IfRegion) subBlocks.get(0), code, false);
return true;
}
return false;
}
@@ -149,8 +154,7 @@ public class RegionGen extends InsnGen {
if (header != null) {
List<InsnNode> headerInsns = header.getInstructions();
if (headerInsns.size() > 1) {
// write not inlined instructions from header
mth.getAttributes().add(AttributeFlag.INCONSISTENT_CODE);
ErrorsCounter.methodError(mth, "Found not inlined instructions from loop header");
int last = headerInsns.size() - 1;
for (int i = 0; i < last; i++) {
InsnNode insn = headerInsns.get(i);
@@ -158,6 +162,10 @@ public class RegionGen extends InsnGen {
}
}
}
LoopLabelAttr labelAttr = region.getInfo().getStart().get(AType.LOOP_LABEL);
if (labelAttr != null) {
code.startLine(mgen.getNameGen().getLoopLabel(labelAttr)).add(':');
}
IfCondition condition = region.getCondition();
if (condition == null) {
@@ -167,8 +175,35 @@ public class RegionGen extends InsnGen {
code.startLine('}');
return code;
}
ConditionGen conditionGen = new ConditionGen(this);
LoopType type = region.getType();
if (type != null) {
if (type instanceof ForLoop) {
ForLoop forLoop = (ForLoop) type;
code.startLine("for (");
makeInsn(forLoop.getInitInsn(), code, Flags.INLINE);
code.add("; ");
conditionGen.add(code, condition);
code.add("; ");
makeInsn(forLoop.getIncrInsn(), code, Flags.INLINE);
code.add(") {");
makeRegionIndent(code, region.getBody());
code.startLine('}');
return code;
}
if (type instanceof ForEachLoop) {
ForEachLoop forEachLoop = (ForEachLoop) type;
code.startLine("for (");
declareVar(code, forEachLoop.getVarArg());
code.add(" : ");
addArg(code, forEachLoop.getIterableArg(), false);
code.add(") {");
makeRegionIndent(code, region.getBody());
code.startLine('}');
return code;
}
throw new JadxRuntimeException("Unknown loop type: " + type.getClass());
}
if (region.isConditionAtEnd()) {
code.startLine("do {");
makeRegionIndent(code, region.getBody());
@@ -197,7 +232,7 @@ public class RegionGen extends InsnGen {
SwitchNode insn = (SwitchNode) sw.getHeader().getInstructions().get(0);
InsnArg arg = insn.getArg(0);
code.startLine("switch (");
addArg(code, arg);
addArg(code, arg, false);
code.add(") {");
code.incIndent();
@@ -207,73 +242,82 @@ public class RegionGen extends InsnGen {
IContainer c = sw.getCases().get(i);
for (Object k : keys) {
code.startLine("case ");
if (k instanceof IndexInsnNode) {
code.add(staticField((FieldInfo) ((IndexInsnNode) k).getIndex()));
if (k instanceof FieldNode) {
FieldNode fn = (FieldNode) k;
if (fn.getParentClass().isEnum()) {
code.add(fn.getAlias());
} else {
staticField(code, fn.getFieldInfo());
// print original value, sometimes replace with incorrect field
FieldInitAttr valueAttr = fn.get(AType.FIELD_INIT);
if (valueAttr != null && valueAttr.getValue() != null) {
code.add(" /*").add(valueAttr.getValue().toString()).add("*/");
}
}
} else if (k instanceof Integer) {
code.add(TypeGen.literalToString((Integer) k, arg.getType(), mth));
} else {
code.add(TypeGen.literalToString((Integer) k, arg.getType()));
throw new JadxRuntimeException("Unexpected key in switch: " + (k != null ? k.getClass() : null));
}
code.add(':');
}
makeCaseBlock(c, code);
makeRegionIndent(code, c);
}
if (sw.getDefaultCase() != null) {
code.startLine("default:");
makeCaseBlock(sw.getDefaultCase(), code);
makeRegionIndent(code, sw.getDefaultCase());
}
code.decIndent();
code.startLine('}');
return code;
}
private void makeCaseBlock(IContainer c, CodeWriter code) throws CodegenException {
boolean addBreak = true;
if (RegionUtils.notEmpty(c)) {
makeRegionIndent(code, c);
if (!RegionUtils.hasExitEdge(c)) {
addBreak = false;
}
}
if (addBreak) {
code.startLine().addIndent().add("break;");
}
}
private void makeTryCatch(IContainer region, TryCatchBlock tryCatchBlock, CodeWriter code)
throws CodegenException {
private void makeTryCatch(TryCatchRegion region, CodeWriter code) throws CodegenException {
code.startLine("try {");
region.getAttributes().remove(AttributeType.CATCH_BLOCK);
makeRegionIndent(code, region);
makeRegionIndent(code, region.getTryRegion());
// TODO: move search of 'allHandler' to 'TryCatchRegion'
ExceptionHandler allHandler = null;
for (ExceptionHandler handler : tryCatchBlock.getHandlers()) {
if (!handler.isCatchAll()) {
makeCatchBlock(code, handler);
} else {
for (Map.Entry<ExceptionHandler, IContainer> entry : region.getCatchRegions().entrySet()) {
ExceptionHandler handler = entry.getKey();
if (handler.isCatchAll()) {
if (allHandler != null) {
LOG.warn("Several 'all' handlers in try/catch block in " + mth);
LOG.warn("Several 'all' handlers in try/catch block in {}", mth);
}
allHandler = handler;
} else {
makeCatchBlock(code, handler);
}
}
if (allHandler != null) {
makeCatchBlock(code, allHandler);
}
if (tryCatchBlock.getFinalBlock() != null) {
IContainer finallyRegion = region.getFinallyRegion();
if (finallyRegion != null) {
code.startLine("} finally {");
makeRegionIndent(code, tryCatchBlock.getFinalBlock());
makeRegionIndent(code, finallyRegion);
}
code.startLine('}');
}
private void makeCatchBlock(CodeWriter code, ExceptionHandler handler)
throws CodegenException {
private void makeCatchBlock(CodeWriter code, ExceptionHandler handler) throws CodegenException {
IContainer region = handler.getHandlerRegion();
if (region != null) {
code.startLine("} catch (");
code.add(handler.isCatchAll() ? "Throwable" : useClass(handler.getCatchType()));
code.add(' ');
code.add(mgen.assignNamedArg(handler.getArg()));
code.add(") {");
makeRegionIndent(code, region);
if (region == null) {
return;
}
code.startLine("} catch (");
InsnArg arg = handler.getArg();
if (arg instanceof RegisterArg) {
declareVar(code, (RegisterArg) arg);
} else if (arg instanceof NamedArg) {
if (handler.isCatchAll()) {
code.add("Throwable");
} else {
useClass(code, handler.getCatchType());
}
code.add(' ');
code.add(mgen.getNameGen().assignNamedArg((NamedArg) arg));
}
code.add(") {");
makeRegionIndent(code, region);
}
}
@@ -1,29 +1,24 @@
package jadx.core.codegen;
import jadx.api.JadxArgs;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.PrimitiveType;
import jadx.core.dex.nodes.IDexNode;
import jadx.core.utils.StringUtils;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
public class TypeGen {
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public static String translate(ClassGen clsGen, ArgType type) {
final PrimitiveType stype = type.getPrimitiveType();
if (stype == null) {
return type.toString();
}
if (stype == PrimitiveType.OBJECT) {
return clsGen.useClass(type);
}
if (stype == PrimitiveType.ARRAY) {
return translate(clsGen, type.getArrayElement()) + "[]";
}
return stype.getLongName();
public class TypeGen {
private static final Logger LOG = LoggerFactory.getLogger(TypeGen.class);
private TypeGen() {
}
public static String signature(ArgType type) {
final PrimitiveType stype = type.getPrimitiveType();
PrimitiveType stype = type.getPrimitiveType();
if (stype == PrimitiveType.OBJECT) {
return Utils.makeQualifiedObjectName(type.getObject());
}
@@ -38,7 +33,16 @@ public class TypeGen {
*
* @throws JadxRuntimeException for incorrect type or literal value
*/
public static String literalToString(long lit, ArgType type, IDexNode dexNode) {
return literalToString(lit, type, dexNode.root().getStringUtils());
}
@Deprecated
public static String literalToString(long lit, ArgType type) {
return literalToString(lit, type, new StringUtils(new JadxArgs()));
}
private static String literalToString(long lit, ArgType type, StringUtils stringUtils) {
if (type == null || !type.isTypeKnown()) {
String n = Long.toString(lit);
if (Math.abs(lit) > 100) {
@@ -53,7 +57,7 @@ public class TypeGen {
case BOOLEAN:
return lit == 0 ? "false" : "true";
case CHAR:
return StringUtils.unescapeChar((char) lit);
return stringUtils.unescapeChar((char) lit);
case BYTE:
return formatByte((byte) lit);
case SHORT:
@@ -70,7 +74,8 @@ public class TypeGen {
case OBJECT:
case ARRAY:
if (lit != 0) {
throw new JadxRuntimeException("Wrong object literal: " + type + " = " + lit);
LOG.warn("Wrong object literal: {} for type: {}", lit, type);
return Long.toString(lit);
}
return "null";
@@ -80,37 +85,91 @@ public class TypeGen {
}
public static String formatShort(short s) {
return "(short) " + wrapNegNum(s < 0, Short.toString(s));
if (s == Short.MAX_VALUE) {
return "Short.MAX_VALUE";
}
if (s == Short.MIN_VALUE) {
return "Short.MIN_VALUE";
}
return "(short) " + Short.toString(s);
}
public static String formatByte(byte b) {
return "(byte) " + wrapNegNum(b < 0, Byte.toString(b));
if (b == Byte.MAX_VALUE) {
return "Byte.MAX_VALUE";
}
if (b == Byte.MIN_VALUE) {
return "Byte.MIN_VALUE";
}
return "(byte) " + Byte.toString(b);
}
public static String formatInteger(int i) {
return wrapNegNum(i < 0, Integer.toString(i));
if (i == Integer.MAX_VALUE) {
return "Integer.MAX_VALUE";
}
if (i == Integer.MIN_VALUE) {
return "Integer.MIN_VALUE";
}
return Integer.toString(i);
}
public static String formatLong(long l) {
if (l == Long.MAX_VALUE) {
return "Long.MAX_VALUE";
}
if (l == Long.MIN_VALUE) {
return "Long.MIN_VALUE";
}
String str = Long.toString(l);
if (Math.abs(l) >= Integer.MAX_VALUE) {
str += "L";
}
return str;
}
public static String formatDouble(double d) {
return wrapNegNum(d < 0, Double.toString(d) + "d");
if (Double.isNaN(d)) {
return "Double.NaN";
}
if (d == Double.NEGATIVE_INFINITY) {
return "Double.NEGATIVE_INFINITY";
}
if (d == Double.POSITIVE_INFINITY) {
return "Double.POSITIVE_INFINITY";
}
if (d == Double.MIN_VALUE) {
return "Double.MIN_VALUE";
}
if (d == Double.MAX_VALUE) {
return "Double.MAX_VALUE";
}
if (d == Double.MIN_NORMAL) {
return "Double.MIN_NORMAL";
}
return Double.toString(d) + "d";
}
public static String formatFloat(float f) {
return wrapNegNum(f < 0, Float.toString(f) + "f");
}
public static String formatLong(long lit) {
String l = Long.toString(lit);
if (lit == Long.MIN_VALUE || Math.abs(lit) >= Integer.MAX_VALUE) {
l += "L";
if (Float.isNaN(f)) {
return "Float.NaN";
}
return wrapNegNum(lit < 0, l);
if (f == Float.NEGATIVE_INFINITY) {
return "Float.NEGATIVE_INFINITY";
}
if (f == Float.POSITIVE_INFINITY) {
return "Float.POSITIVE_INFINITY";
}
if (f == Float.MIN_VALUE) {
return "Float.MIN_VALUE";
}
if (f == Float.MAX_VALUE) {
return "Float.MAX_VALUE";
}
if (f == Float.MIN_NORMAL) {
return "Float.MIN_NORMAL";
}
return Float.toString(f) + "f";
}
private static String wrapNegNum(boolean lz, String str) {
// if (lz)
// return "(" + str + ")";
// else
return str;
}
}
@@ -0,0 +1,50 @@
package jadx.core.deobf;
import jadx.core.dex.nodes.ClassNode;
class DeobfClsInfo {
private final Deobfuscator deobfuscator;
private final ClassNode cls;
private final PackageNode pkg;
private final String alias;
public DeobfClsInfo(Deobfuscator deobfuscator, ClassNode cls, PackageNode pkg, String alias) {
this.deobfuscator = deobfuscator;
this.cls = cls;
this.pkg = pkg;
this.alias = alias;
}
public String makeNameWithoutPkg() {
String prefix;
ClassNode parentClass = cls.getParentClass();
if (parentClass != cls) {
DeobfClsInfo parentDeobfClsInfo = deobfuscator.getClsMap().get(parentClass.getClassInfo());
if (parentDeobfClsInfo != null) {
prefix = parentDeobfClsInfo.makeNameWithoutPkg();
} else {
prefix = deobfuscator.getNameWithoutPackage(parentClass.getClassInfo());
}
prefix += Deobfuscator.INNER_CLASS_SEPARATOR;
} else {
prefix = "";
}
return prefix + (this.alias != null ? this.alias : this.cls.getShortName());
}
public String getFullName() {
return pkg.getFullAlias() + Deobfuscator.CLASS_NAME_SEPARATOR + makeNameWithoutPkg();
}
public ClassNode getCls() {
return cls;
}
public PackageNode getPkg() {
return pkg;
}
public String getAlias() {
return alias;
}
}
@@ -0,0 +1,167 @@
package jadx.core.deobf;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
class DeobfPresets {
private static final Logger LOG = LoggerFactory.getLogger(DeobfPresets.class);
private static final String MAP_FILE_CHARSET = "UTF-8";
private final Deobfuscator deobfuscator;
private final File deobfMapFile;
private final Map<String, String> clsPresetMap = new HashMap<String, String>();
private final Map<String, String> fldPresetMap = new HashMap<String, String>();
private final Map<String, String> mthPresetMap = new HashMap<String, String>();
public DeobfPresets(Deobfuscator deobfuscator, File deobfMapFile) {
this.deobfuscator = deobfuscator;
this.deobfMapFile = deobfMapFile;
}
/**
* Loads deobfuscator presets
*/
public void load() {
if (!deobfMapFile.exists()) {
return;
}
LOG.info("Loading obfuscation map from: {}", deobfMapFile.getAbsoluteFile());
try {
List<String> lines = FileUtils.readLines(deobfMapFile, MAP_FILE_CHARSET);
for (String l : lines) {
l = l.trim();
if (l.isEmpty() || l.startsWith("#")) {
continue;
}
String[] va = splitAndTrim(l);
if (va.length != 2) {
continue;
}
String origName = va[0];
String alias = va[1];
if (l.startsWith("p ")) {
deobfuscator.addPackagePreset(origName, alias);
} else if (l.startsWith("c ")) {
clsPresetMap.put(origName, alias);
} else if (l.startsWith("f ")) {
fldPresetMap.put(origName, alias);
} else if (l.startsWith("m ")) {
mthPresetMap.put(origName, alias);
}
}
} catch (IOException e) {
LOG.error("Failed to load deobfuscation map file '{}'", deobfMapFile.getAbsolutePath(), e);
}
}
private static String[] splitAndTrim(String str) {
String[] v = str.substring(2).split("=");
for (int i = 0; i < v.length; i++) {
v[i] = v[i].trim();
}
return v;
}
public void save(boolean forceSave) {
try {
if (deobfMapFile.exists()) {
if (forceSave) {
dumpMapping();
} else {
LOG.warn("Deobfuscation map file '{}' exists. Use command line option '--deobf-rewrite-cfg' to rewrite it",
deobfMapFile.getAbsolutePath());
}
} else {
dumpMapping();
}
} catch (IOException e) {
LOG.error("Failed to load deobfuscation map file '{}'", deobfMapFile.getAbsolutePath(), e);
}
}
/**
* Saves DefaultDeobfuscator presets
*/
private void dumpMapping() throws IOException {
List<String> list = new ArrayList<String>();
// packages
for (PackageNode p : deobfuscator.getRootPackage().getInnerPackages()) {
for (PackageNode pp : p.getInnerPackages()) {
dfsPackageName(list, p.getName(), pp);
}
if (p.hasAlias()) {
list.add(String.format("p %s = %s", p.getName(), p.getAlias()));
}
}
// classes
for (DeobfClsInfo deobfClsInfo : deobfuscator.getClsMap().values()) {
if (deobfClsInfo.getAlias() != null) {
list.add(String.format("c %s = %s",
deobfClsInfo.getCls().getClassInfo().getFullName(), deobfClsInfo.getAlias()));
}
}
for (FieldInfo fld : deobfuscator.getFldMap().keySet()) {
list.add(String.format("f %s = %s", fld.getFullId(), fld.getAlias()));
}
for (MethodInfo mth : deobfuscator.getMthMap().keySet()) {
list.add(String.format("m %s = %s", mth.getFullId(), mth.getAlias()));
}
Collections.sort(list);
FileUtils.writeLines(deobfMapFile, MAP_FILE_CHARSET, list);
list.clear();
}
private static void dfsPackageName(List<String> list, String prefix, PackageNode node) {
for (PackageNode pp : node.getInnerPackages()) {
dfsPackageName(list, prefix + '.' + node.getName(), pp);
}
if (node.hasAlias()) {
list.add(String.format("p %s.%s = %s", prefix, node.getName(), node.getAlias()));
}
}
public String getForCls(ClassInfo cls) {
return clsPresetMap.get(cls.getFullName());
}
public String getForFld(FieldInfo fld) {
return fldPresetMap.get(fld.getFullId());
}
public String getForMth(MethodInfo mth) {
return mthPresetMap.get(mth.getFullId());
}
public void clear() {
clsPresetMap.clear();
fldPresetMap.clear();
mthPresetMap.clear();
}
public Map<String, String> getClsPresetMap() {
return clsPresetMap;
}
public Map<String, String> getFldPresetMap() {
return fldPresetMap;
}
public Map<String, String> getMthPresetMap() {
return mthPresetMap;
}
}
@@ -0,0 +1,546 @@
package jadx.core.deobf;
import jadx.api.IJadxArgs;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.SourceFileAttr;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Deobfuscator {
private static final Logger LOG = LoggerFactory.getLogger(Deobfuscator.class);
private static final boolean DEBUG = false;
public static final String CLASS_NAME_SEPARATOR = ".";
public static final String INNER_CLASS_SEPARATOR = "$";
private final IJadxArgs args;
@NotNull
private final List<DexNode> dexNodes;
private final DeobfPresets deobfPresets;
private final Map<ClassInfo, DeobfClsInfo> clsMap = new HashMap<ClassInfo, DeobfClsInfo>();
private final Map<FieldInfo, String> fldMap = new HashMap<FieldInfo, String>();
private final Map<MethodInfo, String> mthMap = new HashMap<MethodInfo, String>();
private final Map<MethodInfo, OverridedMethodsNode> ovrdMap = new HashMap<MethodInfo, OverridedMethodsNode>();
private final List<OverridedMethodsNode> ovrd = new ArrayList<OverridedMethodsNode>();
private final PackageNode rootPackage = new PackageNode("");
private final Set<String> pkgSet = new TreeSet<String>();
private final int maxLength;
private final int minLength;
private final boolean useSourceNameAsAlias;
private int pkgIndex = 0;
private int clsIndex = 0;
private int fldIndex = 0;
private int mthIndex = 0;
public Deobfuscator(IJadxArgs args, @NotNull List<DexNode> dexNodes, File deobfMapFile) {
this.args = args;
this.dexNodes = dexNodes;
this.minLength = args.getDeobfuscationMinLength();
this.maxLength = args.getDeobfuscationMaxLength();
this.useSourceNameAsAlias = args.useSourceNameAsClassAlias();
this.deobfPresets = new DeobfPresets(this, deobfMapFile);
}
public void execute() {
if (!args.isDeobfuscationForceSave()) {
deobfPresets.load();
initIndexes();
}
process();
deobfPresets.save(args.isDeobfuscationForceSave());
clear();
}
private void initIndexes() {
pkgIndex = pkgSet.size();
clsIndex = deobfPresets.getClsPresetMap().size();
fldIndex = deobfPresets.getFldPresetMap().size();
mthIndex = deobfPresets.getMthPresetMap().size();
}
private void preProcess() {
for (DexNode dexNode : dexNodes) {
for (ClassNode cls : dexNode.getClasses()) {
doClass(cls);
}
}
}
private void process() {
preProcess();
if (DEBUG) {
dumpAlias();
}
for (DexNode dexNode : dexNodes) {
for (ClassNode cls : dexNode.getClasses()) {
processClass(dexNode, cls);
}
}
postProcess();
}
private void postProcess() {
int id = 1;
for (OverridedMethodsNode o : ovrd) {
Iterator<MethodInfo> it = o.getMethods().iterator();
if (it.hasNext()) {
MethodInfo mth = it.next();
if (mth.isRenamed() && !mth.isAliasFromPreset()) {
mth.setAlias(String.format("mo%d%s", id, makeName(mth.getName())));
}
String firstMethodAlias = mth.getAlias();
while (it.hasNext()) {
mth = it.next();
if (!mth.getAlias().equals(firstMethodAlias)) {
mth.setAlias(firstMethodAlias);
}
}
}
id++;
}
}
void clear() {
deobfPresets.clear();
clsMap.clear();
fldMap.clear();
mthMap.clear();
ovrd.clear();
ovrdMap.clear();
}
@Nullable
private static ClassNode resolveOverridingInternal(DexNode dex, ClassNode cls, String signature,
Set<MethodInfo> overrideSet, ClassNode rootClass) {
ClassNode result = null;
for (MethodNode m : cls.getMethods()) {
if (m.getMethodInfo().getShortId().startsWith(signature)) {
result = cls;
if (!overrideSet.contains(m.getMethodInfo())) {
overrideSet.add(m.getMethodInfo());
}
break;
}
}
ArgType superClass = cls.getSuperClass();
if (superClass != null) {
ClassNode superNode = dex.resolveClass(superClass);
if (superNode != null) {
ClassNode clsWithMth = resolveOverridingInternal(dex, superNode, signature, overrideSet, rootClass);
if (clsWithMth != null) {
if ((result != null) && (result != cls)) {
if (clsWithMth != result) {
LOG.warn(String.format("Multiple overriding '%s' from '%s' and '%s' in '%s'",
signature,
result.getFullName(), clsWithMth.getFullName(),
rootClass.getFullName()));
}
} else {
result = clsWithMth;
}
}
}
}
for (ArgType iFaceType : cls.getInterfaces()) {
ClassNode iFaceNode = dex.resolveClass(iFaceType);
if (iFaceNode != null) {
ClassNode clsWithMth = resolveOverridingInternal(dex, iFaceNode, signature, overrideSet, rootClass);
if (clsWithMth != null) {
if ((result != null) && (result != cls)) {
if (clsWithMth != result) {
LOG.warn(String.format("Multiple overriding '%s' from '%s' and '%s' in '%s'",
signature,
result.getFullName(), clsWithMth.getFullName(),
rootClass.getFullName()));
}
} else {
result = clsWithMth;
}
}
}
}
return result;
}
private void resolveOverriding(DexNode dex, ClassNode cls, MethodNode mth) {
Set<MethodInfo> overrideSet = new HashSet<MethodInfo>();
resolveOverridingInternal(dex, cls, mth.getMethodInfo().makeSignature(false), overrideSet, cls);
if (overrideSet.size() > 1) {
OverridedMethodsNode overrideNode = null;
for (MethodInfo _mth : overrideSet) {
if (ovrdMap.containsKey(_mth)) {
overrideNode = ovrdMap.get(_mth);
break;
}
}
if (overrideNode == null) {
overrideNode = new OverridedMethodsNode(overrideSet);
ovrd.add(overrideNode);
}
for (MethodInfo _mth : overrideSet) {
if (!ovrdMap.containsKey(_mth)) {
ovrdMap.put(_mth, overrideNode);
if (!overrideNode.contains(_mth)) {
overrideNode.add(_mth);
}
}
}
} else {
overrideSet.clear();
overrideSet = null;
}
}
private void processClass(DexNode dex, ClassNode cls) {
ClassInfo clsInfo = cls.getClassInfo();
String fullName = getClassFullName(clsInfo);
if (!fullName.equals(clsInfo.getFullName())) {
clsInfo.rename(dex, fullName);
}
for (FieldNode field : cls.getFields()) {
FieldInfo fieldInfo = field.getFieldInfo();
String alias = getFieldAlias(field);
if (alias != null) {
fieldInfo.setAlias(alias);
}
}
for (MethodNode mth : cls.getMethods()) {
MethodInfo methodInfo = mth.getMethodInfo();
String alias = getMethodAlias(mth);
if (alias != null) {
methodInfo.setAlias(alias);
}
if (mth.isVirtual()) {
resolveOverriding(dex, cls, mth);
}
}
}
public void addPackagePreset(String origPkgName, String pkgAlias) {
PackageNode pkg = getPackageNode(origPkgName, true);
pkg.setAlias(pkgAlias);
}
/**
* Gets package node for full package name
*
* @param fullPkgName full package name
* @param create if {@code true} then will create all absent objects
* @return package node object or {@code null} if no package found and <b>create</b> set to {@code false}
*/
private PackageNode getPackageNode(String fullPkgName, boolean create) {
if (fullPkgName.isEmpty() || fullPkgName.equals(CLASS_NAME_SEPARATOR)) {
return rootPackage;
}
PackageNode result = rootPackage;
PackageNode parentNode;
do {
String pkgName;
int idx = fullPkgName.indexOf(CLASS_NAME_SEPARATOR);
if (idx > -1) {
pkgName = fullPkgName.substring(0, idx);
fullPkgName = fullPkgName.substring(idx + 1);
} else {
pkgName = fullPkgName;
fullPkgName = "";
}
parentNode = result;
result = result.getInnerPackageByName(pkgName);
if (result == null && create) {
result = new PackageNode(pkgName);
parentNode.addInnerPackage(result);
}
} while (!fullPkgName.isEmpty() && result != null);
return result;
}
String getNameWithoutPackage(ClassInfo clsInfo) {
String prefix;
ClassInfo parentClsInfo = clsInfo.getParentClass();
if (parentClsInfo != null) {
DeobfClsInfo parentDeobfClsInfo = clsMap.get(parentClsInfo);
if (parentDeobfClsInfo != null) {
prefix = parentDeobfClsInfo.makeNameWithoutPkg();
} else {
prefix = getNameWithoutPackage(parentClsInfo);
}
prefix += INNER_CLASS_SEPARATOR;
} else {
prefix = "";
}
return prefix + clsInfo.getShortName();
}
private void doClass(ClassNode cls) {
ClassInfo classInfo = cls.getClassInfo();
String pkgFullName = classInfo.getPackage();
PackageNode pkg = getPackageNode(pkgFullName, true);
doPkg(pkg, pkgFullName);
String alias = deobfPresets.getForCls(classInfo);
if (alias != null) {
clsMap.put(classInfo, new DeobfClsInfo(this, cls, pkg, alias));
return;
}
if (clsMap.containsKey(classInfo)) {
return;
}
if (shouldRename(classInfo.getShortName())) {
makeClsAlias(cls);
}
}
public String getClsAlias(ClassNode cls) {
DeobfClsInfo deobfClsInfo = clsMap.get(cls.getClassInfo());
if (deobfClsInfo != null) {
return deobfClsInfo.getAlias();
}
return makeClsAlias(cls);
}
private String makeClsAlias(ClassNode cls) {
ClassInfo classInfo = cls.getClassInfo();
String alias = null;
if (this.useSourceNameAsAlias) {
alias = getAliasFromSourceFile(cls);
}
if (alias == null) {
String clsName = classInfo.getShortName();
alias = String.format("C%04d%s", clsIndex++, makeName(clsName));
}
PackageNode pkg = getPackageNode(classInfo.getPackage(), true);
clsMap.put(classInfo, new DeobfClsInfo(this, cls, pkg, alias));
return alias;
}
@Nullable
private String getAliasFromSourceFile(ClassNode cls) {
SourceFileAttr sourceFileAttr = cls.get(AType.SOURCE_FILE);
if (sourceFileAttr == null) {
return null;
}
String name = sourceFileAttr.getFileName();
if (name.endsWith(".java")) {
name = name.substring(0, name.length() - ".java".length());
}
if (NameMapper.isValidIdentifier(name)
&& !NameMapper.isReserved(name)) {
// TODO: check if no class with this name exists or already renamed
cls.remove(AType.SOURCE_FILE);
return name;
}
return null;
}
@Nullable
public String getFieldAlias(FieldNode field) {
FieldInfo fieldInfo = field.getFieldInfo();
String alias = fldMap.get(fieldInfo);
if (alias != null) {
return alias;
}
alias = deobfPresets.getForFld(fieldInfo);
if (alias != null) {
fldMap.put(fieldInfo, alias);
return alias;
}
if (shouldRename(field.getName())) {
return makeFieldAlias(field);
}
return null;
}
@Nullable
public String getMethodAlias(MethodNode mth) {
MethodInfo methodInfo = mth.getMethodInfo();
String alias = mthMap.get(methodInfo);
if (alias != null) {
return alias;
}
alias = deobfPresets.getForMth(methodInfo);
if (alias != null) {
mthMap.put(methodInfo, alias);
methodInfo.setAliasFromPreset(true);
return alias;
}
if (shouldRename(mth.getName())) {
return makeMethodAlias(mth);
}
return null;
}
public String makeFieldAlias(FieldNode field) {
String alias = String.format("f%d%s", fldIndex++, makeName(field.getName()));
fldMap.put(field.getFieldInfo(), alias);
return alias;
}
public String makeMethodAlias(MethodNode mth) {
String alias = String.format("m%d%s", mthIndex++, makeName(mth.getName()));
mthMap.put(mth.getMethodInfo(), alias);
return alias;
}
private void doPkg(PackageNode pkg, String fullName) {
if (pkgSet.contains(fullName)) {
return;
}
pkgSet.add(fullName);
// doPkg for all parent packages except root that not hasAliases
PackageNode parentPkg = pkg.getParentPackage();
while (!parentPkg.getName().isEmpty()) {
if (!parentPkg.hasAlias()) {
doPkg(parentPkg, parentPkg.getFullName());
}
parentPkg = parentPkg.getParentPackage();
}
final String pkgName = pkg.getName();
if (!pkg.hasAlias() && shouldRename(pkgName)) {
final String pkgAlias = String.format("p%03d%s", pkgIndex++, makeName(pkgName));
pkg.setAlias(pkgAlias);
}
}
private boolean shouldRename(String s) {
return s.length() > maxLength
|| s.length() < minLength
|| NameMapper.isReserved(s)
|| !NameMapper.isAllCharsPrintable(s);
}
private String makeName(String name) {
if (name.length() > maxLength) {
return "x" + Integer.toHexString(name.hashCode());
}
if (NameMapper.isReserved(name)) {
return name;
}
if (!NameMapper.isAllCharsPrintable(name)) {
return removeInvalidChars(name);
}
return name;
}
private String removeInvalidChars(String name) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < name.length(); i++) {
int ch = name.charAt(i);
if (NameMapper.isPrintableChar(ch)) {
sb.append((char) ch);
}
}
return sb.toString();
}
private void dumpClassAlias(ClassNode cls) {
PackageNode pkg = getPackageNode(cls.getPackage(), false);
if (pkg != null) {
if (!cls.getFullName().equals(getClassFullName(cls))) {
LOG.info("Alias name for class '{}' is '{}'", cls.getFullName(), getClassFullName(cls));
}
} else {
LOG.error("Can't find package node for '{}'", cls.getPackage());
}
}
private void dumpAlias() {
for (DexNode dexNode : dexNodes) {
for (ClassNode cls : dexNode.getClasses()) {
dumpClassAlias(cls);
}
}
}
private String getPackageName(String packageName) {
final PackageNode pkg = getPackageNode(packageName, false);
if (pkg != null) {
return pkg.getFullAlias();
}
return packageName;
}
private String getClassName(ClassInfo clsInfo) {
final DeobfClsInfo deobfClsInfo = clsMap.get(clsInfo);
if (deobfClsInfo != null) {
return deobfClsInfo.makeNameWithoutPkg();
}
return getNameWithoutPackage(clsInfo);
}
private String getClassFullName(ClassNode cls) {
return getClassFullName(cls.getClassInfo());
}
private String getClassFullName(ClassInfo clsInfo) {
final DeobfClsInfo deobfClsInfo = clsMap.get(clsInfo);
if (deobfClsInfo != null) {
return deobfClsInfo.getFullName();
}
return getPackageName(clsInfo.getPackage()) + CLASS_NAME_SEPARATOR + getClassName(clsInfo);
}
public Map<ClassInfo, DeobfClsInfo> getClsMap() {
return clsMap;
}
public Map<FieldInfo, String> getFldMap() {
return fldMap;
}
public Map<MethodInfo, String> getMthMap() {
return mthMap;
}
public PackageNode getRootPackage() {
return rootPackage;
}
}
@@ -3,9 +3,16 @@ package jadx.core.deobf;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Pattern;
public class NameMapper {
private static final Pattern VALID_JAVA_IDENTIFIER = Pattern.compile(
"\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*");
private static final Pattern VALID_JAVA_FULL_IDENTIFIER = Pattern.compile(
"(" + VALID_JAVA_IDENTIFIER + "\\.)*" + VALID_JAVA_IDENTIFIER);
private static final Set<String> RESERVED_NAMES = new HashSet<String>(
Arrays.asList(new String[]{
"abstract",
@@ -68,4 +75,25 @@ public class NameMapper {
return RESERVED_NAMES.contains(str);
}
public static boolean isValidIdentifier(String str) {
return VALID_JAVA_IDENTIFIER.matcher(str).matches() && isAllCharsPrintable(str);
}
public static boolean isValidFullIdentifier(String str) {
return VALID_JAVA_FULL_IDENTIFIER.matcher(str).matches() && isAllCharsPrintable(str);
}
public static boolean isPrintableChar(int c) {
return 32 <= c && c <= 126;
}
public static boolean isAllCharsPrintable(String str) {
int len = str.length();
for (int i = 0; i < len; i++) {
if (!isPrintableChar(str.charAt(i))) {
return false;
}
}
return true;
}
}
@@ -0,0 +1,26 @@
package jadx.core.deobf;
import jadx.core.dex.info.MethodInfo;
import java.util.Set;
/* package */ class OverridedMethodsNode {
private Set<MethodInfo> methods;
public OverridedMethodsNode(Set<MethodInfo> methodsSet) {
methods = methodsSet;
}
public boolean contains(MethodInfo mth) {
return methods.contains(mth);
}
public void add(MethodInfo mth) {
methods.add(mth);
}
public Set<MethodInfo> getMethods() {
return methods;
}
}
@@ -0,0 +1,130 @@
package jadx.core.deobf;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Stack;
public class PackageNode {
private static final char SEPARATOR_CHAR = '.';
private PackageNode parentPackage;
private List<PackageNode> innerPackages = Collections.emptyList();
private final String packageName;
private String packageAlias;
private String cachedPackageFullName;
private String cachedPackageFullAlias;
public PackageNode(String packageName) {
this.packageName = packageName;
this.parentPackage = this;
}
public String getName() {
return packageName;
}
public String getFullName() {
if (cachedPackageFullName == null) {
Stack<PackageNode> pp = getParentPackages();
StringBuilder result = new StringBuilder();
result.append(pp.pop().getName());
while (pp.size() > 0) {
result.append(SEPARATOR_CHAR);
result.append(pp.pop().getName());
}
cachedPackageFullName = result.toString();
}
return cachedPackageFullName;
}
public String getAlias() {
if (packageAlias != null) {
return packageAlias;
}
return packageName;
}
public void setAlias(String alias) {
packageAlias = alias;
}
public boolean hasAlias() {
return packageAlias != null;
}
public String getFullAlias() {
if (cachedPackageFullAlias == null) {
Stack<PackageNode> pp = getParentPackages();
StringBuilder result = new StringBuilder();
if (pp.size() > 0) {
result.append(pp.pop().getAlias());
while (pp.size() > 0) {
result.append(SEPARATOR_CHAR);
result.append(pp.pop().getAlias());
}
} else {
result.append(this.getAlias());
}
cachedPackageFullAlias = result.toString();
}
return cachedPackageFullAlias;
}
public PackageNode getParentPackage() {
return parentPackage;
}
public List<PackageNode> getInnerPackages() {
return innerPackages;
}
public void addInnerPackage(PackageNode pkg) {
if (innerPackages.isEmpty()) {
innerPackages = new ArrayList<PackageNode>();
}
innerPackages.add(pkg);
pkg.parentPackage = this;
}
/**
* Gets inner package node by name
*
* @param name inner package name
* @return package node or {@code null}
*/
public PackageNode getInnerPackageByName(String name) {
PackageNode result = null;
for (PackageNode p : innerPackages) {
if (p.getName().equals(name)) {
result = p;
break;
}
}
return result;
}
/**
* Fills stack with parent packages exclude root node
*
* @return stack with parent packages
*/
private Stack<PackageNode> getParentPackages() {
Stack<PackageNode> pp = new Stack<PackageNode>();
PackageNode currentP = this;
PackageNode parentP = currentP.getParentPackage();
while (currentP != parentP) {
pp.push(currentP);
currentP = parentP;
parentP = currentP.getParentPackage();
}
return pp;
}
}
@@ -1,6 +1,6 @@
package jadx.core.dex.attributes;
public enum AttributeFlag {
public enum AFlag {
TRY_ENTER,
TRY_LEAVE,
@@ -8,20 +8,31 @@ public enum AttributeFlag {
LOOP_END,
SYNTHETIC,
FINAL, // SSAVar attribute for make var final
RETURN, // block contains only return instruction
ORIG_RETURN,
DECLARE_VAR,
DONT_WRAP,
DONT_SHRINK,
DONT_INLINE,
DONT_GENERATE,
SKIP,
REMOVE,
SKIP_FIRST_ARG,
SKIP_ARG, // skip argument in invoke call
ANONYMOUS_CONSTRUCTOR,
ANONYMOUS_CLASS,
ELSE_IF_CHAIN,
WRAPPED,
ARITH_ONEARG,
FALL_THROUGH,
INCONSISTENT_CODE, // warning about incorrect decompilation
}
@@ -0,0 +1,53 @@
package jadx.core.dex.attributes;
import jadx.core.dex.attributes.annotations.AnnotationsList;
import jadx.core.dex.attributes.annotations.MethodParameters;
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
import jadx.core.dex.attributes.nodes.EdgeInsnAttr;
import jadx.core.dex.attributes.nodes.EnumClassAttr;
import jadx.core.dex.attributes.nodes.EnumMapAttr;
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
import jadx.core.dex.attributes.nodes.ForceReturnAttr;
import jadx.core.dex.attributes.nodes.IgnoreEdgeAttr;
import jadx.core.dex.attributes.nodes.JadxErrorAttr;
import jadx.core.dex.attributes.nodes.JumpInfo;
import jadx.core.dex.attributes.nodes.LoopInfo;
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
import jadx.core.dex.attributes.nodes.MethodInlineAttr;
import jadx.core.dex.attributes.nodes.PhiListAttr;
import jadx.core.dex.attributes.nodes.SourceFileAttr;
import jadx.core.dex.nodes.parser.FieldInitAttr;
import jadx.core.dex.trycatch.CatchAttr;
import jadx.core.dex.trycatch.ExcHandlerAttr;
import jadx.core.dex.trycatch.SplitterBlockAttr;
/**
* Attribute types enumeration,
* uses generic type for omit cast after 'AttributeStorage.get' method
*
* @param <T> attribute class implementation
*/
public class AType<T extends IAttribute> {
public static final AType<AttrList<JumpInfo>> JUMP = new AType<AttrList<JumpInfo>>();
public static final AType<AttrList<LoopInfo>> LOOP = new AType<AttrList<LoopInfo>>();
public static final AType<AttrList<EdgeInsnAttr>> EDGE_INSN = new AType<AttrList<EdgeInsnAttr>>();
public static final AType<ExcHandlerAttr> EXC_HANDLER = new AType<ExcHandlerAttr>();
public static final AType<CatchAttr> CATCH_BLOCK = new AType<CatchAttr>();
public static final AType<SplitterBlockAttr> SPLITTER_BLOCK = new AType<SplitterBlockAttr>();
public static final AType<ForceReturnAttr> FORCE_RETURN = new AType<ForceReturnAttr>();
public static final AType<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>();
}
@@ -0,0 +1,30 @@
package jadx.core.dex.attributes;
import jadx.core.utils.Utils;
import java.util.LinkedList;
import java.util.List;
public class AttrList<T> implements IAttribute {
private final AType<AttrList<T>> type;
private final List<T> list = new LinkedList<T>();
public AttrList(AType<AttrList<T>> type) {
this.type = type;
}
public List<T> getList() {
return list;
}
@Override
public AType<AttrList<T>> getType() {
return type;
}
@Override
public String toString() {
return Utils.listToString(list);
}
}
@@ -1,15 +1,99 @@
package jadx.core.dex.attributes;
import jadx.core.dex.attributes.annotations.Annotation;
import java.util.List;
public abstract class AttrNode implements IAttributeNode {
private AttributesList attributesList;
private static final AttributeStorage EMPTY_ATTR_STORAGE = new EmptyAttrStorage();
private AttributeStorage storage = EMPTY_ATTR_STORAGE;
@Override
public AttributesList getAttributes() {
if (attributesList == null) {
attributesList = new AttributesList();
}
return attributesList;
public void add(AFlag flag) {
initStorage().add(flag);
}
@Override
public void addAttr(IAttribute attr) {
initStorage().add(attr);
}
@Override
public <T> void addAttr(AType<AttrList<T>> type, T obj) {
initStorage().add(type, obj);
}
@Override
public void copyAttributesFrom(AttrNode attrNode) {
AttributeStorage copyFrom = attrNode.storage;
if (!copyFrom.isEmpty()) {
initStorage().addAll(copyFrom);
}
}
private AttributeStorage initStorage() {
AttributeStorage store = storage;
if (store == EMPTY_ATTR_STORAGE) {
store = new AttributeStorage();
storage = store;
}
return store;
}
@Override
public boolean contains(AFlag flag) {
return storage.contains(flag);
}
@Override
public <T extends IAttribute> boolean contains(AType<T> type) {
return storage.contains(type);
}
@Override
public <T extends IAttribute> T get(AType<T> type) {
return storage.get(type);
}
@Override
public Annotation getAnnotation(String cls) {
return storage.getAnnotation(cls);
}
@Override
public <T> List<T> getAll(AType<AttrList<T>> type) {
return storage.getAll(type);
}
@Override
public void remove(AFlag flag) {
storage.remove(flag);
}
@Override
public <T extends IAttribute> void remove(AType<T> type) {
storage.remove(type);
}
@Override
public void removeAttr(IAttribute attr) {
storage.remove(attr);
}
@Override
public void clearAttributes() {
storage.clear();
}
@Override
public List<String> getAttributesStringsList() {
return storage.getAttributeStrings();
}
@Override
public String getAttributesString() {
return storage.toString();
}
}
@@ -0,0 +1,126 @@
package jadx.core.dex.attributes;
import jadx.core.dex.attributes.annotations.Annotation;
import jadx.core.dex.attributes.annotations.AnnotationsList;
import jadx.core.utils.Utils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Storage for different attribute types:
* 1. flags - boolean attribute (set or not)
* 2. attribute - class instance associated with attribute type.
*/
public class AttributeStorage {
private final Set<AFlag> flags;
private final Map<AType<?>, IAttribute> attributes;
public AttributeStorage() {
flags = EnumSet.noneOf(AFlag.class);
attributes = new IdentityHashMap<AType<?>, IAttribute>();
}
public void add(AFlag flag) {
flags.add(flag);
}
public void add(IAttribute attr) {
attributes.put(attr.getType(), attr);
}
public <T> void add(AType<AttrList<T>> type, T obj) {
AttrList<T> list = get(type);
if (list == null) {
list = new AttrList<T>(type);
add(list);
}
list.getList().add(obj);
}
public void addAll(AttributeStorage otherList) {
flags.addAll(otherList.flags);
attributes.putAll(otherList.attributes);
}
public boolean contains(AFlag flag) {
return flags.contains(flag);
}
public <T extends IAttribute> boolean contains(AType<T> type) {
return attributes.containsKey(type);
}
@SuppressWarnings("unchecked")
public <T extends IAttribute> T get(AType<T> type) {
return (T) attributes.get(type);
}
public Annotation getAnnotation(String cls) {
AnnotationsList aList = get(AType.ANNOTATION_LIST);
return aList == null ? null : aList.get(cls);
}
public <T> List<T> getAll(AType<AttrList<T>> type) {
AttrList<T> attrList = get(type);
if (attrList == null) {
return Collections.emptyList();
}
return Collections.unmodifiableList(attrList.getList());
}
public void remove(AFlag flag) {
flags.remove(flag);
}
public <T extends IAttribute> void remove(AType<T> type) {
attributes.remove(type);
}
public void remove(IAttribute attr) {
AType<?> type = attr.getType();
IAttribute a = attributes.get(type);
if (a == attr) {
attributes.remove(type);
}
}
public void clear() {
flags.clear();
attributes.clear();
}
public List<String> getAttributeStrings() {
int size = flags.size() + attributes.size() + attributes.size();
if (size == 0) {
return Collections.emptyList();
}
List<String> list = new ArrayList<String>(size);
for (AFlag a : flags) {
list.add(a.toString());
}
for (IAttribute a : attributes.values()) {
list.add(a.toString());
}
return list;
}
public boolean isEmpty() {
return flags.isEmpty() && attributes.isEmpty();
}
@Override
public String toString() {
List<String> list = getAttributeStrings();
if (list.isEmpty()) {
return "";
}
return "A:{" + Utils.listToString(list) + "}";
}
}
@@ -1,65 +0,0 @@
package jadx.core.dex.attributes;
public enum AttributeType {
/* Multi attributes */
JUMP(false),
LOOP(false),
CATCH_BLOCK(false),
/* Uniq attributes */
EXC_HANDLER(true),
SPLITTER_BLOCK(true),
FORCE_RETURN(true),
FIELD_VALUE(true),
JADX_ERROR(true),
METHOD_INLINE(true),
FIELD_REPLACE(true),
ENUM_CLASS(true),
ANNOTATION_LIST(true),
ANNOTATION_MTH_PARAMETERS(true),
SOURCE_FILE(true),
// for regions
DECLARE_VARIABLES(true);
private static final int NOT_UNIQ_COUNT;
private final boolean uniq;
private AttributeType(boolean isUniq) {
this.uniq = isUniq;
}
static {
// place all not unique attributes at first
int last = -1;
AttributeType[] vals = AttributeType.values();
for (int i = 0; i < vals.length; i++) {
AttributeType type = vals[i];
if (type.notUniq()) {
last = i;
}
}
NOT_UNIQ_COUNT = last + 1;
}
public static int getNotUniqCount() {
return NOT_UNIQ_COUNT;
}
public boolean isUniq() {
return uniq;
}
public boolean notUniq() {
return !uniq;
}
}
@@ -1,202 +0,0 @@
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.Arrays;
import java.util.Collections;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Storage for different attribute types:
* 1. flags - boolean attribute (set or not)
* 2. attribute - class instance associated for attribute type,
* only one attached to node for unique attributes, multiple for others
*/
public final class AttributesList {
private final Set<AttributeFlag> flags;
private final Map<AttributeType, IAttribute> uniqAttr;
private final List<IAttribute> attributes;
private final int[] attrCount;
public AttributesList() {
flags = EnumSet.noneOf(AttributeFlag.class);
uniqAttr = new EnumMap<AttributeType, IAttribute>(AttributeType.class);
attributes = new LinkedList<IAttribute>();
attrCount = new int[AttributeType.getNotUniqCount()];
}
// Flags
public void add(AttributeFlag flag) {
flags.add(flag);
}
public boolean contains(AttributeFlag flag) {
return flags.contains(flag);
}
public void remove(AttributeFlag flag) {
flags.remove(flag);
}
// Attributes
public void add(IAttribute attr) {
if (attr.getType().isUniq()) {
uniqAttr.put(attr.getType(), attr);
} else {
addMultiAttribute(attr);
}
}
private void addMultiAttribute(IAttribute attr) {
attributes.add(attr);
attrCount[attr.getType().ordinal()]++;
}
private int getMultiCountInternal(AttributeType type) {
return attrCount[type.ordinal()];
}
public void addAll(AttributesList otherList) {
flags.addAll(otherList.flags);
uniqAttr.putAll(otherList.uniqAttr);
for (IAttribute attr : otherList.attributes) {
addMultiAttribute(attr);
}
}
public boolean contains(AttributeType type) {
if (type.isUniq()) {
return uniqAttr.containsKey(type);
} else {
return getMultiCountInternal(type) != 0;
}
}
public IAttribute get(AttributeType type) {
if (type.isUniq()) {
return uniqAttr.get(type);
} else {
if (getMultiCountInternal(type) != 0) {
for (IAttribute attr : attributes) {
if (attr.getType() == type) {
return attr;
}
}
}
return null;
}
}
public int getCount(AttributeType type) {
if (type.isUniq()) {
return uniqAttr.containsKey(type) ? 1 : 0;
} else {
return getMultiCountInternal(type);
}
}
public Annotation getAnnotation(String cls) {
AnnotationsList aList = (AnnotationsList) get(AttributeType.ANNOTATION_LIST);
return aList == null ? null : aList.get(cls);
}
public List<IAttribute> getAll(AttributeType type) {
assert type.notUniq();
int count = getMultiCountInternal(type);
if (count == 0) {
return Collections.emptyList();
} else {
List<IAttribute> attrs = new ArrayList<IAttribute>(count);
for (IAttribute attr : attributes) {
if (attr.getType() == type) {
attrs.add(attr);
}
}
return attrs;
}
}
public void remove(AttributeType type) {
if (type.isUniq()) {
uniqAttr.remove(type);
} else {
for (Iterator<IAttribute> it = attributes.iterator(); it.hasNext(); ) {
IAttribute attr = it.next();
if (attr.getType() == type) {
it.remove();
}
}
attrCount[type.ordinal()] = 0;
}
}
public void remove(IAttribute attr) {
AttributeType type = attr.getType();
if (type.isUniq()) {
IAttribute a = uniqAttr.get(type);
if (a == attr) {
uniqAttr.remove(type);
}
} else {
if (getMultiCountInternal(type) == 0) {
return;
}
for (Iterator<IAttribute> it = attributes.iterator(); it.hasNext(); ) {
IAttribute a = it.next();
if (a == attr) {
it.remove();
attrCount[type.ordinal()]--;
}
}
}
}
public void clear() {
flags.clear();
uniqAttr.clear();
attributes.clear();
Arrays.fill(attrCount, 0);
}
public List<String> getAttributeStrings() {
int size = flags.size() + uniqAttr.size() + attributes.size();
if (size == 0) {
return Collections.emptyList();
}
List<String> list = new ArrayList<String>(size);
for (AttributeFlag a : flags) {
list.add(a.toString());
}
for (IAttribute a : uniqAttr.values()) {
list.add(a.toString());
}
for (IAttribute a : attributes) {
list.add(a.toString());
}
return list;
}
@Override
public String toString() {
List<String> list = getAttributeStrings();
if (list.isEmpty()) {
return "";
}
return "A:{" + Utils.listToString(list) + "}";
}
}
@@ -1,56 +0,0 @@
package jadx.core.dex.attributes;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.TypedVar;
import jadx.core.dex.nodes.MethodNode;
public final class BlockRegState {
private final RegisterArg[] regs;
public BlockRegState(MethodNode mth) {
this.regs = new RegisterArg[mth.getRegsCount()];
for (int i = 0; i < regs.length; i++) {
regs[i] = new RegisterArg(i);
}
}
public BlockRegState(BlockRegState state) {
this.regs = new RegisterArg[state.regs.length];
System.arraycopy(state.regs, 0, regs, 0, state.regs.length);
}
public void assignReg(RegisterArg arg) {
regs[arg.getRegNum()] = arg;
arg.getTypedVar().getUseList().add(arg);
}
public void use(RegisterArg arg) {
RegisterArg reg = regs[arg.getRegNum()];
TypedVar regType = reg.getTypedVar();
if (regType == null) {
regType = new TypedVar(arg.getType());
reg.forceSetTypedVar(regType);
}
arg.replaceTypedVar(reg);
reg.getTypedVar().getUseList().add(arg);
}
public RegisterArg getRegister(int r) {
return regs[r];
}
@Override
public String toString() {
StringBuilder str = new StringBuilder();
for (RegisterArg reg : regs) {
if (reg.getTypedVar() != null) {
if (str.length() != 0) {
str.append(", ");
}
str.append(reg);
}
}
return str.toString();
}
}
@@ -0,0 +1,65 @@
package jadx.core.dex.attributes;
import jadx.core.dex.attributes.annotations.Annotation;
import java.util.Collections;
import java.util.List;
public final class EmptyAttrStorage extends AttributeStorage {
@Override
public boolean contains(AFlag flag) {
return false;
}
@Override
public <T extends IAttribute> boolean contains(AType<T> type) {
return false;
}
@Override
public <T extends IAttribute> T get(AType<T> type) {
return null;
}
@Override
public Annotation getAnnotation(String cls) {
return null;
}
@Override
public <T> List<T> getAll(AType<AttrList<T>> type) {
return Collections.emptyList();
}
@Override
public void clear() {
}
@Override
public void remove(AFlag flag) {
}
@Override
public <T extends IAttribute> void remove(AType<T> type) {
}
@Override
public void remove(IAttribute attr) {
}
@Override
public List<String> getAttributeStrings() {
return Collections.emptyList();
}
@Override
public boolean isEmpty() {
return true;
}
@Override
public String toString() {
return "";
}
}
@@ -1,32 +0,0 @@
package jadx.core.dex.attributes;
import jadx.core.dex.info.FieldInfo;
public class FieldReplaceAttr implements IAttribute {
private final FieldInfo fieldInfo;
private final boolean isOuterClass;
public FieldReplaceAttr(FieldInfo fieldInfo, boolean isOuterClass) {
this.fieldInfo = fieldInfo;
this.isOuterClass = isOuterClass;
}
public FieldInfo getFieldInfo() {
return fieldInfo;
}
public boolean isOuterClass() {
return isOuterClass;
}
@Override
public AttributeType getType() {
return AttributeType.FIELD_REPLACE;
}
@Override
public String toString() {
return "REPLACE: " + fieldInfo;
}
}
@@ -2,6 +2,6 @@ package jadx.core.dex.attributes;
public interface IAttribute {
AttributeType getType();
AType<?> getType();
}
@@ -1,7 +1,38 @@
package jadx.core.dex.attributes;
import jadx.core.dex.attributes.annotations.Annotation;
import java.util.List;
public interface IAttributeNode {
AttributesList getAttributes();
void add(AFlag flag);
void addAttr(IAttribute attr);
<T> void addAttr(AType<AttrList<T>> type, T obj);
void copyAttributesFrom(AttrNode attrNode);
boolean contains(AFlag flag);
<T extends IAttribute> boolean contains(AType<T> type);
<T extends IAttribute> T get(AType<T> type);
Annotation getAnnotation(String cls);
<T> List<T> getAll(AType<AttrList<T>> type);
void remove(AFlag flag);
<T extends IAttribute> void remove(AType<T> type);
void removeAttr(IAttribute attr);
void clearAttributes();
List<String> getAttributesStringsList();
String getAttributesString();
}
@@ -6,7 +6,7 @@ import java.util.Map;
public class Annotation {
public static enum Visibility {
public enum Visibility {
BUILD, RUNTIME, SYSTEM
}
@@ -1,16 +1,19 @@
package jadx.core.dex.attributes.annotations;
import jadx.core.dex.attributes.AttributeType;
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;
public class AnnotationsList implements IAttribute {
public static final AnnotationsList EMPTY = new AnnotationsList(Collections.<Annotation>emptyList());
private final Map<String, Annotation> map;
public AnnotationsList(List<Annotation> anList) {
@@ -32,9 +35,13 @@ public class AnnotationsList implements IAttribute {
return map.size();
}
public boolean isEmpty() {
return map.isEmpty();
}
@Override
public AttributeType getType() {
return AttributeType.ANNOTATION_LIST;
public AType<AnnotationsList> getType() {
return AType.ANNOTATION_LIST;
}
@Override
@@ -1,6 +1,6 @@
package jadx.core.dex.attributes.annotations;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.utils.Utils;
@@ -20,8 +20,8 @@ public class MethodParameters implements IAttribute {
}
@Override
public AttributeType getType() {
return AttributeType.ANNOTATION_MTH_PARAMETERS;
public AType<MethodParameters> getType() {
return AType.ANNOTATION_MTH_PARAMETERS;
}
@Override
@@ -1,5 +1,7 @@
package jadx.core.dex.attributes;
package jadx.core.dex.attributes.nodes;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.utils.Utils;
@@ -22,8 +24,8 @@ public class DeclareVariablesAttr implements IAttribute {
}
@Override
public AttributeType getType() {
return AttributeType.DECLARE_VARIABLES;
public AType<DeclareVariablesAttr> getType() {
return AType.DECLARE_VARIABLES;
}
@Override
@@ -0,0 +1,48 @@
package jadx.core.dex.attributes.nodes;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.AttrList;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.InsnNode;
public class EdgeInsnAttr implements IAttribute {
private final BlockNode start;
private final BlockNode end;
private final InsnNode insn;
public static void addEdgeInsn(BlockNode start, BlockNode end, InsnNode insn) {
EdgeInsnAttr edgeInsnAttr = new EdgeInsnAttr(start, end, insn);
start.addAttr(AType.EDGE_INSN, edgeInsnAttr);
end.addAttr(AType.EDGE_INSN, edgeInsnAttr);
}
public EdgeInsnAttr(BlockNode start, BlockNode end, InsnNode insn) {
this.start = start;
this.end = end;
this.insn = insn;
}
@Override
public AType<AttrList<EdgeInsnAttr>> getType() {
return AType.EDGE_INSN;
}
public BlockNode getStart() {
return start;
}
public BlockNode getEnd() {
return end;
}
public InsnNode getInsn() {
return insn;
}
@Override
public String toString() {
return "EDGE_INSN: " + start + "->" + end + " " + insn;
}
}
@@ -1,36 +1,39 @@
package jadx.core.dex.attributes;
package jadx.core.dex.attributes.nodes;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.mods.ConstructorInsn;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.Utils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class EnumClassAttr implements IAttribute {
public static class EnumField {
private final String name;
private final List<InsnArg> args;
private final FieldInfo field;
private final ConstructorInsn constrInsn;
private final int startArg;
private ClassNode cls;
public EnumField(String name, int argsCount) {
this.name = name;
if (argsCount != 0) {
this.args = new ArrayList<InsnArg>(argsCount);
} else {
this.args = Collections.emptyList();
}
public EnumField(FieldInfo field, ConstructorInsn co, int startArg) {
this.field = field;
this.constrInsn = co;
this.startArg = startArg;
}
public String getName() {
return name;
public FieldInfo getField() {
return field;
}
public List<InsnArg> getArgs() {
return args;
public ConstructorInsn getConstrInsn() {
return constrInsn;
}
public int getStartArg() {
return startArg;
}
public ClassNode getCls() {
@@ -43,7 +46,7 @@ public class EnumClassAttr implements IAttribute {
@Override
public String toString() {
return name + "(" + Utils.listToString(args) + ") " + cls;
return field + "(" + constrInsn + ") " + cls;
}
}
@@ -51,7 +54,7 @@ public class EnumClassAttr implements IAttribute {
private MethodNode staticMethod;
public EnumClassAttr(int fieldsCount) {
this.fields = new ArrayList<EnumClassAttr.EnumField>(fieldsCount);
this.fields = new ArrayList<EnumField>(fieldsCount);
}
public List<EnumField> getFields() {
@@ -67,8 +70,8 @@ public class EnumClassAttr implements IAttribute {
}
@Override
public AttributeType getType() {
return AttributeType.ENUM_CLASS;
public AType<EnumClassAttr> getType() {
return AType.ENUM_CLASS;
}
@Override
@@ -0,0 +1,49 @@
package jadx.core.dex.attributes.nodes;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.nodes.FieldNode;
import java.util.HashMap;
import java.util.Map;
public class EnumMapAttr implements IAttribute {
public static class KeyValueMap {
private final Map<Object, Object> map = new HashMap<Object, Object>();
public Object get(Object key) {
return map.get(key);
}
void put(Object key, Object value) {
map.put(key, value);
}
}
private final Map<FieldNode, KeyValueMap> fieldsMap = new HashMap<FieldNode, KeyValueMap>();
public KeyValueMap getMap(FieldNode field) {
return fieldsMap.get(field);
}
public void add(FieldNode field, Object key, Object value) {
KeyValueMap map = getMap(field);
if (map == null) {
map = new KeyValueMap();
fieldsMap.put(field, map);
}
map.put(key, value);
}
@Override
public AType<EnumMapAttr> getType() {
return AType.ENUM_MAP;
}
@Override
public String toString() {
return "Enum fields map: " + fieldsMap;
}
}
@@ -0,0 +1,49 @@
package jadx.core.dex.attributes.nodes;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.instructions.args.InsnArg;
public class FieldReplaceAttr implements IAttribute {
public enum ReplaceWith {
CLASS_INSTANCE,
VAR
}
private final ReplaceWith replaceType;
private final Object replaceObj;
public FieldReplaceAttr(ClassInfo cls) {
this.replaceType = ReplaceWith.CLASS_INSTANCE;
this.replaceObj = cls;
}
public FieldReplaceAttr(InsnArg reg) {
this.replaceType = ReplaceWith.VAR;
this.replaceObj = reg;
}
public ReplaceWith getReplaceType() {
return replaceType;
}
public ClassInfo getClsRef() {
return (ClassInfo) replaceObj;
}
public InsnArg getVarRef() {
return (InsnArg) replaceObj;
}
@Override
public AType<FieldReplaceAttr> getType() {
return AType.FIELD_REPLACE;
}
@Override
public String toString() {
return "REPLACE: " + replaceType + " " + replaceObj;
}
}
@@ -1,5 +1,7 @@
package jadx.core.dex.attributes;
package jadx.core.dex.attributes.nodes;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.Utils;
@@ -16,8 +18,8 @@ public class ForceReturnAttr implements IAttribute {
}
@Override
public AttributeType getType() {
return AttributeType.FORCE_RETURN;
public AType<ForceReturnAttr> getType() {
return AType.FORCE_RETURN;
}
@Override
@@ -0,0 +1,32 @@
package jadx.core.dex.attributes.nodes;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.utils.Utils;
import java.util.HashSet;
import java.util.Set;
public class IgnoreEdgeAttr implements IAttribute {
private final Set<BlockNode> blocks = new HashSet<BlockNode>(3);
public Set<BlockNode> getBlocks() {
return blocks;
}
public boolean contains(BlockNode block) {
return blocks.contains(block);
}
@Override
public AType<IgnoreEdgeAttr> getType() {
return AType.IGNORE_EDGE;
}
@Override
public String toString() {
return "IGNORE_EDGES: " + Utils.listToString(blocks);
}
}
@@ -1,5 +1,7 @@
package jadx.core.dex.attributes;
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 {
@@ -15,8 +17,8 @@ public class JadxErrorAttr implements IAttribute {
}
@Override
public AttributeType getType() {
return AttributeType.JADX_ERROR;
public AType<JadxErrorAttr> getType() {
return AType.JADX_ERROR;
}
@Override
@@ -1,22 +1,17 @@
package jadx.core.dex.attributes;
package jadx.core.dex.attributes.nodes;
import jadx.core.utils.InsnUtils;
public class JumpAttribute implements IAttribute {
public class JumpInfo {
private final int src;
private final int dest;
public JumpAttribute(int src, int dest) {
public JumpInfo(int src, int dest) {
this.src = src;
this.dest = dest;
}
@Override
public AttributeType getType() {
return AttributeType.JUMP;
}
public int getSrc() {
return src;
}
@@ -25,11 +20,6 @@ public class JumpAttribute implements IAttribute {
return dest;
}
@Override
public String toString() {
return "JUMP: " + InsnUtils.formatOffset(src) + " -> " + InsnUtils.formatOffset(dest);
}
@Override
public int hashCode() {
return 31 * dest + src;
@@ -46,7 +36,12 @@ public class JumpAttribute implements IAttribute {
if (getClass() != obj.getClass()) {
return false;
}
JumpAttribute other = (JumpAttribute) obj;
JumpInfo other = (JumpInfo) obj;
return dest == other.dest && src == other.src;
}
@Override
public String toString() {
return "JUMP: " + InsnUtils.formatOffset(src) + " -> " + InsnUtils.formatOffset(dest);
}
}
@@ -1,4 +1,6 @@
package jadx.core.dex.attributes;
package jadx.core.dex.attributes.nodes;
import jadx.core.dex.attributes.AttrNode;
public abstract class LineAttrNode extends AttrNode {
@@ -21,4 +23,9 @@ public abstract class LineAttrNode extends AttrNode {
public void setDecompiledLine(int decompiledLine) {
this.decompiledLine = decompiledLine;
}
public void copyLines(LineAttrNode lineAttrNode) {
setSourceLine(lineAttrNode.getSourceLine());
setDecompiledLine(lineAttrNode.getDecompiledLine());
}
}
@@ -1,5 +1,6 @@
package jadx.core.dex.attributes;
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;
@@ -10,13 +11,16 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Set;
public class LoopAttr implements IAttribute {
public class LoopInfo {
private final BlockNode start;
private final BlockNode end;
private final Set<BlockNode> loopBlocks;
public LoopAttr(BlockNode start, BlockNode end) {
private int id;
private LoopInfo parentLoop;
public LoopInfo(BlockNode start, BlockNode end) {
this.start = start;
this.end = end;
this.loopBlocks = Collections.unmodifiableSet(BlockUtils.getAllPathsBlocks(start, end));
@@ -30,11 +34,6 @@ public class LoopAttr implements IAttribute {
return end;
}
@Override
public AttributeType getType() {
return AttributeType.LOOP;
}
public Set<BlockNode> getLoopBlocks() {
return loopBlocks;
}
@@ -49,7 +48,7 @@ public class LoopAttr implements IAttribute {
for (BlockNode block : blocks) {
// exit: successor node not from this loop, (don't change to getCleanSuccessors)
for (BlockNode s : block.getSuccessors()) {
if (!blocks.contains(s) && !s.getAttributes().contains(AttributeType.EXC_HANDLER)) {
if (!blocks.contains(s) && !s.contains(AType.EXC_HANDLER)) {
nodes.add(block);
}
}
@@ -65,7 +64,7 @@ public class LoopAttr implements IAttribute {
Set<BlockNode> blocks = getLoopBlocks();
for (BlockNode block : blocks) {
for (BlockNode s : block.getSuccessors()) {
if (!blocks.contains(s) && !s.getAttributes().contains(AttributeType.EXC_HANDLER)) {
if (!blocks.contains(s) && !s.contains(AType.EXC_HANDLER)) {
edges.add(new Edge(block, s));
}
}
@@ -73,8 +72,24 @@ public class LoopAttr implements IAttribute {
return edges;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public LoopInfo getParentLoop() {
return parentLoop;
}
public void setParentLoop(LoopInfo parentLoop) {
this.parentLoop = parentLoop;
}
@Override
public String toString() {
return "LOOP: " + start + "->" + end;
return "LOOP:" + id + ": " + start + "->" + end;
}
}
@@ -0,0 +1,27 @@
package jadx.core.dex.attributes.nodes;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
public class LoopLabelAttr implements IAttribute {
private final LoopInfo loop;
public LoopLabelAttr(LoopInfo loop) {
this.loop = loop;
}
public LoopInfo getLoop() {
return loop;
}
@Override
public AType<LoopLabelAttr> getType() {
return AType.LOOP_LABEL;
}
@Override
public String toString() {
return "LOOP_LABEL: " + loop;
}
}
@@ -1,5 +1,7 @@
package jadx.core.dex.attributes;
package jadx.core.dex.attributes.nodes;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.nodes.InsnNode;
public class MethodInlineAttr implements IAttribute {
@@ -15,8 +17,8 @@ public class MethodInlineAttr implements IAttribute {
}
@Override
public AttributeType getType() {
return AttributeType.METHOD_INLINE;
public AType<MethodInlineAttr> getType() {
return AType.METHOD_INLINE;
}
@Override
@@ -0,0 +1,32 @@
package jadx.core.dex.attributes.nodes;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.instructions.PhiInsn;
import java.util.LinkedList;
import java.util.List;
public class PhiListAttr implements IAttribute {
private final List<PhiInsn> list = new LinkedList<PhiInsn>();
@Override
public AType<PhiListAttr> getType() {
return AType.PHI_LIST;
}
public List<PhiInsn> getList() {
return list;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("PHI: ");
for (PhiInsn phiInsn : list) {
sb.append('r').append(phiInsn.getResult().getRegNum()).append(" ");
}
return sb.toString();
}
}
@@ -1,4 +1,7 @@
package jadx.core.dex.attributes;
package jadx.core.dex.attributes.nodes;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
public class SourceFileAttr implements IAttribute {
@@ -13,8 +16,8 @@ public class SourceFileAttr implements IAttribute {
}
@Override
public AttributeType getType() {
return AttributeType.SOURCE_FILE;
public AType<SourceFileAttr> getType() {
return AType.SOURCE_FILE;
}
@Override
@@ -8,7 +8,7 @@ public class AccessInfo {
private final int accFlags;
public static enum AFType {
public enum AFType {
CLASS, FIELD, METHOD
}
@@ -25,16 +25,15 @@ public class AccessInfo {
public AccessInfo remove(int flag) {
if (containsFlag(flag)) {
return new AccessInfo(accFlags - flag, type);
} else {
return this;
return new AccessInfo(accFlags & ~flag, type);
}
return this;
}
public AccessInfo getVisibility() {
int f = (accFlags & AccessFlags.ACC_PUBLIC)
| (accFlags & AccessFlags.ACC_PROTECTED)
| (accFlags & AccessFlags.ACC_PRIVATE);
int f = accFlags & AccessFlags.ACC_PUBLIC
| accFlags & AccessFlags.ACC_PROTECTED
| accFlags & AccessFlags.ACC_PRIVATE;
return new AccessInfo(f, type);
}
@@ -1,69 +1,85 @@
package jadx.core.dex.info;
import jadx.core.Consts;
import jadx.core.deobf.NameMapper;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.DexNode;
import jadx.core.utils.exceptions.JadxRuntimeException;
import java.io.File;
import java.util.Map;
import java.util.WeakHashMap;
public final class ClassInfo {
private static final Map<ArgType, ClassInfo> CLASSINFO_CACHE = new WeakHashMap<ArgType, ClassInfo>();
private final ArgType type;
private String pkg;
private String name;
private String fullName;
// for inner class not equals null
private ClassInfo parentClass;
// class info after rename (deobfuscation)
private ClassInfo alias;
private ClassInfo(ArgType type) {
assert type.isObject() : "Not class type: " + type;
private ClassInfo(DexNode dex, ArgType type) {
this(dex, type, true);
}
private ClassInfo(DexNode dex, ArgType type, boolean inner) {
if (!type.isObject() || type.isGeneric()) {
throw new JadxRuntimeException("Not class type: " + type);
}
this.type = type;
this.alias = this;
splitNames(true);
splitNames(dex, inner);
}
public static ClassInfo fromType(DexNode dex, ArgType type) {
if (type.isArray()) {
type = ArgType.OBJECT;
}
ClassInfo cls = dex.getInfoStorage().getCls(type);
if (cls != null) {
return cls;
}
cls = new ClassInfo(dex, type);
return dex.getInfoStorage().putCls(cls);
}
public static ClassInfo fromDex(DexNode dex, int clsIndex) {
if (clsIndex == DexNode.NO_INDEX) {
return null;
}
ArgType type = dex.getType(clsIndex);
if (type.isArray()) {
type = ArgType.OBJECT;
return fromType(dex, dex.getType(clsIndex));
}
public static ClassInfo fromName(DexNode dex, String clsName) {
return fromType(dex, ArgType.object(clsName));
}
public static ClassInfo extCls(DexNode dex, ArgType type) {
ClassInfo classInfo = fromName(dex, type.getObject());
return classInfo.alias;
}
public void rename(DexNode dex, String fullName) {
ClassInfo newAlias = new ClassInfo(dex, ArgType.object(fullName), isInner());
if (!alias.getFullName().equals(newAlias.getFullName())) {
this.alias = newAlias;
}
return fromType(type);
}
public static ClassInfo fromName(String clsName) {
return fromType(ArgType.object(clsName));
public boolean isRenamed() {
return alias != this;
}
public static ClassInfo fromType(ArgType type) {
ClassInfo cls = CLASSINFO_CACHE.get(type);
if (cls == null) {
cls = new ClassInfo(type);
CLASSINFO_CACHE.put(type, cls);
}
return cls;
public ClassInfo getAlias() {
return alias;
}
public static void clearCache() {
CLASSINFO_CACHE.clear();
}
private void splitNames(boolean canBeInner) {
private void splitNames(DexNode dex, boolean canBeInner) {
String fullObjectName = type.getObject();
assert fullObjectName.indexOf('/') == -1 : "Raw type: " + type;
String clsName;
int dot = fullObjectName.lastIndexOf('.');
if (dot == -1) {
// rename default package if it used from class with package (often for obfuscated apps),
pkg = Consts.DEFAULT_PACKAGE_NAME;
pkg = "";
clsName = fullObjectName;
} else {
pkg = fullObjectName.substring(0, dot);
@@ -73,69 +89,75 @@ public final class ClassInfo {
int sep = clsName.lastIndexOf('$');
if (canBeInner && sep > 0 && sep != clsName.length() - 1) {
String parClsName = pkg + "." + clsName.substring(0, sep);
parentClass = fromName(parClsName);
parentClass = fromName(dex, parClsName);
clsName = clsName.substring(sep + 1);
} else {
parentClass = null;
}
char firstChar = clsName.charAt(0);
if (Character.isDigit(firstChar)) {
clsName = Consts.ANONYMOUS_CLASS_PREFIX + clsName;
} else if (firstChar == '$') {
clsName = "_" + clsName;
}
if (NameMapper.isReserved(clsName)) {
clsName += "_";
}
this.fullName = (parentClass != null ? parentClass.getFullName() : pkg) + "." + clsName;
this.name = clsName;
this.fullName = makeFullClsName(clsName, false);
}
public String makeFullClsName(String shortName, boolean raw) {
if (parentClass != null) {
String innerSep = raw ? "$" : ".";
return parentClass.makeFullClsName(parentClass.getShortName(), raw) + innerSep + shortName;
}
return pkg.isEmpty() ? shortName : pkg + "." + shortName;
}
public String getFullPath() {
return pkg.replace('.', File.separatorChar)
ClassInfo alias = getAlias();
return alias.getPackage().replace('.', File.separatorChar)
+ File.separatorChar
+ getNameWithoutPackage().replace('.', '_');
+ alias.getNameWithoutPackage().replace('.', '_');
}
public String getFullName() {
return fullName;
}
public boolean isObject() {
return fullName.equals(Consts.CLASS_OBJECT);
}
public String getShortName() {
return name;
}
public String getRawName() {
return type.getObject();
}
public String getPackage() {
return pkg;
}
public boolean isPackageDefault() {
return pkg.isEmpty() || pkg.equals(Consts.DEFAULT_PACKAGE_NAME);
public boolean isDefaultPackage() {
return pkg.isEmpty();
}
public String getRawName() {
return type.getObject();
}
public String getNameWithoutPackage() {
return (parentClass != null ? parentClass.getNameWithoutPackage() + "." : "") + name;
if (parentClass == null) {
return name;
}
return parentClass.getNameWithoutPackage() + "." + name;
}
public ClassInfo getParentClass() {
return parentClass;
}
public ClassInfo getTopParentClass() {
if (parentClass != null) {
ClassInfo topCls = parentClass.getTopParentClass();
return topCls != null ? topCls : parentClass;
}
return null;
}
public boolean isInner() {
return parentClass != null;
}
public void notInner() {
splitNames(false);
public void notInner(DexNode dex) {
splitNames(dex, false);
}
public ArgType getType() {
@@ -149,7 +171,7 @@ public final class ClassInfo {
@Override
public int hashCode() {
return fullName.hashCode();
return type.hashCode();
}
@Override
@@ -159,7 +181,7 @@ public final class ClassInfo {
}
if (obj instanceof ClassInfo) {
ClassInfo other = (ClassInfo) obj;
return this.getFullName().equals(other.getFullName());
return this.type.equals(other.type);
}
return false;
}
@@ -0,0 +1,187 @@
package jadx.core.dex.info;
import jadx.api.IJadxArgs;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.PrimitiveType;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.ResRefField;
import jadx.core.dex.nodes.parser.FieldInitAttr;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jetbrains.annotations.Nullable;
public class ConstStorage {
private static final class Values {
private final Map<Object, FieldNode> values = new HashMap<Object, FieldNode>();
private final Set<Object> duplicates = new HashSet<Object>();
public Map<Object, FieldNode> getValues() {
return values;
}
public FieldNode get(Object key) {
return values.get(key);
}
/**
* @return true if this value is duplicated
*/
public boolean put(Object value, FieldNode fld) {
FieldNode prev = values.put(value, fld);
if (prev != null) {
values.remove(value);
duplicates.add(value);
return true;
}
if (duplicates.contains(value)) {
values.remove(value);
return true;
}
return false;
}
public boolean contains(Object value) {
return duplicates.contains(value) || values.containsKey(value);
}
}
private final boolean replaceEnabled;
private final Values globalValues = new Values();
private final Map<ClassNode, Values> classes = new HashMap<ClassNode, Values>();
private Map<Integer, String> resourcesNames = new HashMap<Integer, String>();
public ConstStorage(IJadxArgs args) {
this.replaceEnabled = args.isReplaceConsts();
}
public void processConstFields(ClassNode cls, List<FieldNode> staticFields) {
if (!replaceEnabled || staticFields.isEmpty()) {
return;
}
for (FieldNode f : staticFields) {
AccessInfo accFlags = f.getAccessFlags();
if (accFlags.isStatic() && accFlags.isFinal()) {
FieldInitAttr fv = f.get(AType.FIELD_INIT);
if (fv != null
&& fv.getValue() != null
&& fv.getValueType() == FieldInitAttr.InitType.CONST
&& fv != FieldInitAttr.NULL_VALUE) {
addConstField(cls, f, fv.getValue(), accFlags.isPublic());
}
}
}
}
private void addConstField(ClassNode cls, FieldNode fld, Object value, boolean isPublic) {
if (isPublic) {
globalValues.put(value, fld);
} else {
getClsValues(cls).put(value, fld);
}
}
private Values getClsValues(ClassNode cls) {
Values classValues = classes.get(cls);
if (classValues == null) {
classValues = new Values();
classes.put(cls, classValues);
}
return classValues;
}
@Nullable
public FieldNode getConstField(ClassNode cls, Object value, boolean searchGlobal) {
DexNode dex = cls.dex();
if (value instanceof Integer) {
String str = resourcesNames.get(value);
if (str != null) {
return new ResRefField(dex, str.replace('/', '.'));
}
}
if (!replaceEnabled) {
return null;
}
boolean foundInGlobal = globalValues.contains(value);
if (foundInGlobal && !searchGlobal) {
return null;
}
ClassNode current = cls;
while (current != null) {
Values classValues = classes.get(current);
if (classValues != null) {
FieldNode field = classValues.get(value);
if (field != null) {
if (foundInGlobal) {
return null;
}
return field;
}
}
ClassInfo parentClass = current.getClassInfo().getParentClass();
if (parentClass == null) {
break;
}
current = dex.resolveClass(parentClass);
}
if (searchGlobal) {
return globalValues.get(value);
}
return null;
}
@Nullable
public FieldNode getConstFieldByLiteralArg(ClassNode cls, LiteralArg arg) {
PrimitiveType type = arg.getType().getPrimitiveType();
if (type == null) {
return null;
}
long literal = arg.getLiteral();
switch (type) {
case BOOLEAN:
return getConstField(cls, literal == 1, false);
case CHAR:
return getConstField(cls, (char) literal, Math.abs(literal) > 10);
case BYTE:
return getConstField(cls, (byte) literal, Math.abs(literal) > 10);
case SHORT:
return getConstField(cls, (short) literal, Math.abs(literal) > 100);
case INT:
return getConstField(cls, (int) literal, Math.abs(literal) > 100);
case LONG:
return getConstField(cls, literal, Math.abs(literal) > 1000);
case FLOAT:
float f = Float.intBitsToFloat((int) literal);
return getConstField(cls, f, f != 0.0);
case DOUBLE:
double d = Double.longBitsToDouble(literal);
return getConstField(cls, d, d != 0);
}
return null;
}
public void setResourcesNames(Map<Integer, String> resourcesNames) {
this.resourcesNames = resourcesNames;
}
public Map<Integer, String> getResourcesNames() {
return resourcesNames;
}
public Map<Object, FieldNode> getGlobalConstFields() {
return globalValues.getValues();
}
public boolean isReplaceEnabled() {
return replaceEnabled;
}
}
@@ -1,34 +1,38 @@
package jadx.core.dex.info;
import jadx.core.codegen.TypeGen;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.DexNode;
import com.android.dx.io.FieldId;
import com.android.dex.FieldId;
public class FieldInfo {
public final class FieldInfo {
private final ClassInfo declClass;
private final String name;
private final ArgType type;
private String alias;
public FieldInfo(ClassInfo declClass, String name, ArgType type) {
private FieldInfo(ClassInfo declClass, String name, ArgType type) {
this.declClass = declClass;
this.name = name;
this.type = type;
this.alias = name;
}
public static FieldInfo from(DexNode dex, ClassInfo declClass, String name, ArgType type) {
FieldInfo field = new FieldInfo(declClass, name, type);
return dex.getInfoStorage().getField(field);
}
public static FieldInfo fromDex(DexNode dex, int index) {
FieldId field = dex.getFieldId(index);
return new FieldInfo(
return from(dex,
ClassInfo.fromDex(dex, field.getDeclaringClassIndex()),
dex.getString(field.getNameIndex()),
dex.getType(field.getTypeIndex()));
}
public static String getNameById(DexNode dex, int ind) {
return dex.getString(dex.getFieldId(ind).getNameIndex());
}
public String getName() {
return name;
}
@@ -41,6 +45,22 @@ public class FieldInfo {
return declClass;
}
public String getAlias() {
return alias;
}
public void setAlias(String alias) {
this.alias = alias;
}
public String getFullId() {
return declClass.getFullName() + "." + name + ":" + TypeGen.signature(type);
}
public boolean isRenamed() {
return !name.equals(alias);
}
@Override
public boolean equals(Object o) {
if (this == o) {
@@ -50,16 +70,9 @@ public class FieldInfo {
return false;
}
FieldInfo fieldInfo = (FieldInfo) o;
if (!name.equals(fieldInfo.name)) {
return false;
}
if (!type.equals(fieldInfo.type)) {
return false;
}
if (!declClass.equals(fieldInfo.declClass)) {
return false;
}
return true;
return name.equals(fieldInfo.name)
&& type.equals(fieldInfo.type)
&& declClass.equals(fieldInfo.declClass);
}
@Override
@@ -0,0 +1,46 @@
package jadx.core.dex.info;
import jadx.core.dex.instructions.args.ArgType;
import java.util.HashMap;
import java.util.Map;
public class InfoStorage {
private final Map<ArgType, ClassInfo> classes = new HashMap<ArgType, ClassInfo>();
private final Map<Integer, MethodInfo> methods = new HashMap<Integer, MethodInfo>();
private final Map<FieldInfo, FieldInfo> fields = new HashMap<FieldInfo, FieldInfo>();
public ClassInfo getCls(ArgType type) {
return classes.get(type);
}
public ClassInfo putCls(ClassInfo cls) {
synchronized (classes) {
ClassInfo prev = classes.put(cls.getType(), cls);
return prev == null ? cls : prev;
}
}
public MethodInfo getMethod(int mtdId) {
return methods.get(mtdId);
}
public MethodInfo putMethod(int mthId, MethodInfo mth) {
synchronized (methods) {
MethodInfo prev = methods.put(mthId, mth);
return prev == null ? mth : prev;
}
}
public FieldInfo getField(FieldInfo field) {
synchronized (fields) {
FieldInfo f = fields.get(field);
if (f != null) {
return f;
}
fields.put(field, field);
return field;
}
}
}
@@ -7,8 +7,8 @@ import jadx.core.utils.Utils;
import java.util.List;
import com.android.dx.io.MethodId;
import com.android.dx.io.ProtoId;
import com.android.dex.MethodId;
import com.android.dex.ProtoId;
public final class MethodInfo {
@@ -17,16 +17,32 @@ public final class MethodInfo {
private final List<ArgType> args;
private final ClassInfo declClass;
private final String shortId;
private String alias;
private boolean aliasFromPreset;
private MethodInfo(DexNode dex, int mthIndex) {
MethodId mthId = dex.getMethodId(mthIndex);
name = dex.getString(mthId.getNameIndex());
alias = name;
aliasFromPreset = false;
declClass = ClassInfo.fromDex(dex, mthId.getDeclaringClassIndex());
ProtoId proto = dex.getProtoId(mthId.getProtoIndex());
retType = dex.getType(proto.getReturnTypeIndex());
args = dex.readParamList(proto.getParametersOffset());
shortId = makeSignature(true);
}
public static MethodInfo fromDex(DexNode dex, int mthIndex) {
MethodInfo mth = dex.getInfoStorage().getMethod(mthIndex);
if (mth != null) {
return mth;
}
mth = new MethodInfo(dex, mthIndex);
return dex.getInfoStorage().putMethod(mthIndex, mth);
}
public String makeSignature(boolean includeRetType) {
StringBuilder signature = new StringBuilder();
signature.append(name);
signature.append('(');
@@ -34,13 +50,10 @@ public final class MethodInfo {
signature.append(TypeGen.signature(arg));
}
signature.append(')');
signature.append(TypeGen.signature(retType));
shortId = signature.toString();
}
public static MethodInfo fromDex(DexNode dex, int mthIndex) {
return new MethodInfo(dex, mthIndex);
if (includeRetType) {
signature.append(TypeGen.signature(retType));
}
return signature.toString();
}
public String getName() {
@@ -86,6 +99,26 @@ public final class MethodInfo {
return name.equals("<clinit>");
}
public String getAlias() {
return alias;
}
public void setAlias(String alias) {
this.alias = alias;
}
public boolean isRenamed() {
return !name.equals(alias);
}
public boolean isAliasFromPreset() {
return aliasFromPreset;
}
public void setAliasFromPreset(boolean value) {
aliasFromPreset = value;
}
@Override
public int hashCode() {
int result = declClass.hashCode();
@@ -99,23 +132,13 @@ public final class MethodInfo {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
if (!(obj instanceof MethodInfo)) {
return false;
}
MethodInfo other = (MethodInfo) obj;
if (!shortId.equals(other.shortId)) {
return false;
}
if (!retType.equals(other.retType)) {
return false;
}
if (!declClass.equals(other.declClass)) {
return false;
}
return true;
return shortId.equals(other.shortId)
&& retType.equals(other.retType)
&& declClass.equals(other.declClass);
}
@Override
@@ -1,5 +1,6 @@
package jadx.core.dex.instructions;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.RegisterArg;
@@ -39,7 +40,6 @@ public class ArithNode extends InsnNode {
addReg(insn, 2, type);
}
}
assert getArgsCount() == 2;
}
public ArithNode(ArithOp op, RegisterArg res, InsnArg a, InsnArg b) {
@@ -51,16 +51,26 @@ public class ArithNode extends InsnNode {
}
public ArithNode(ArithOp op, RegisterArg res, InsnArg a) {
super(InsnType.ARITH_ONEARG, 1);
this.op = op;
setResult(res);
addArg(a);
this(op, res, res, a);
add(AFlag.ARITH_ONEARG);
}
public ArithOp getOp() {
return op;
}
@Override
public boolean isSame(InsnNode obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof ArithNode) || !super.isSame(obj)) {
return false;
}
ArithNode other = (ArithNode) obj;
return op == other.op;
}
@Override
public String toString() {
return InsnUtils.formatOffset(offset) + ": "
@@ -68,7 +78,7 @@ public class ArithNode extends InsnNode {
+ getResult() + " = "
+ getArg(0) + " "
+ op.getSymbol() + " "
+ (getArgsCount() == 2 ? getArg(1) : "");
+ getArg(1);
}
}
@@ -17,7 +17,7 @@ public enum ArithOp {
private final String symbol;
private ArithOp(String symbol) {
ArithOp(String symbol) {
this.symbol = symbol;
}
@@ -3,7 +3,7 @@ package jadx.core.dex.instructions;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.InsnNode;
public class ConstClassNode extends InsnNode {
public final class ConstClassNode extends InsnNode {
private final ArgType clsType;
@@ -16,6 +16,23 @@ public class ConstClassNode extends InsnNode {
return clsType;
}
@Override
public InsnNode copy() {
return copyCommonParams(new ConstClassNode(clsType));
}
@Override
public boolean isSame(InsnNode obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof ConstClassNode) || !super.isSame(obj)) {
return false;
}
ConstClassNode other = (ConstClassNode) obj;
return clsType.equals(other.clsType);
}
@Override
public String toString() {
return super.toString() + " " + clsType;
@@ -2,7 +2,7 @@ package jadx.core.dex.instructions;
import jadx.core.dex.nodes.InsnNode;
public class ConstStringNode extends InsnNode {
public final class ConstStringNode extends InsnNode {
private final String str;
@@ -15,6 +15,23 @@ public class ConstStringNode extends InsnNode {
return str;
}
@Override
public InsnNode copy() {
return copyCommonParams(new ConstStringNode(str));
}
@Override
public boolean isSame(InsnNode obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof ConstStringNode) || !super.isSame(obj)) {
return false;
}
ConstStringNode other = (ConstStringNode) obj;
return str.equals(other.str);
}
@Override
public String toString() {
return super.toString() + " \"" + str + "\"";
@@ -2,15 +2,21 @@ package jadx.core.dex.instructions;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.PrimitiveType;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.exceptions.JadxRuntimeException;
import java.util.ArrayList;
import java.util.List;
import com.android.dx.io.instructions.FillArrayDataPayloadDecodedInstruction;
public class FillArrayNode extends InsnNode {
public final class FillArrayNode extends InsnNode {
private final Object data;
private final int size;
private ArgType elemType;
public FillArrayNode(int resReg, FillArrayDataPayloadDecodedInstruction payload) {
@@ -36,6 +42,7 @@ public class FillArrayNode extends InsnNode {
setResult(InsnArg.reg(resReg, ArgType.array(elType)));
this.data = payload.getData();
this.size = payload.getSize();
this.elemType = elType;
}
@@ -43,14 +50,55 @@ public class FillArrayNode extends InsnNode {
return data;
}
public int getSize() {
return size;
}
public ArgType getElementType() {
return elemType;
}
public void mergeElementType(ArgType foundElemType) {
ArgType r = ArgType.merge(elemType, foundElemType);
public void mergeElementType(DexNode dex, ArgType foundElemType) {
ArgType r = ArgType.merge(dex, elemType, foundElemType);
if (r != null) {
elemType = r;
}
}
public List<LiteralArg> getLiteralArgs() {
List<LiteralArg> list = new ArrayList<LiteralArg>(size);
Object array = data;
if (array instanceof int[]) {
for (int b : (int[]) array) {
list.add(InsnArg.lit(b, elemType));
}
} else if (array instanceof byte[]) {
for (byte b : (byte[]) array) {
list.add(InsnArg.lit(b, elemType));
}
} else if (array instanceof short[]) {
for (short b : (short[]) array) {
list.add(InsnArg.lit(b, elemType));
}
} else if (array instanceof long[]) {
for (long b : (long[]) array) {
list.add(InsnArg.lit(b, elemType));
}
} else {
throw new JadxRuntimeException("Unknown type: " + data.getClass() + ", expected: " + elemType);
}
return list;
}
@Override
public boolean isSame(InsnNode obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof FillArrayNode) || !super.isSame(obj)) {
return false;
}
FillArrayNode other = (FillArrayNode) obj;
return elemType.equals(other.elemType) && data == other.data;
}
}
@@ -0,0 +1,41 @@
package jadx.core.dex.instructions;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.InsnNode;
import org.jetbrains.annotations.NotNull;
public class FilledNewArrayNode extends InsnNode {
private final ArgType elemType;
public FilledNewArrayNode(@NotNull ArgType elemType, int size) {
super(InsnType.FILLED_NEW_ARRAY, size);
this.elemType = elemType;
}
public ArgType getElemType() {
return elemType;
}
public ArgType getArrayType() {
return ArgType.array(elemType);
}
@Override
public boolean isSame(InsnNode obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof FilledNewArrayNode) || !super.isSame(obj)) {
return false;
}
FilledNewArrayNode other = (FilledNewArrayNode) obj;
return elemType == other.elemType;
}
@Override
public String toString() {
return super.toString() + " elemType: " + elemType;
}
}
@@ -4,6 +4,7 @@ import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.PrimitiveType;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.InsnUtils;
import com.android.dx.io.instructions.DecodedInstruction;
@@ -13,9 +14,11 @@ import static jadx.core.utils.BlockUtils.selectOther;
public class IfNode extends GotoNode {
// change default types priority
private static final ArgType ARG_TYPE = ArgType.unknown(
PrimitiveType.INT, PrimitiveType.OBJECT, PrimitiveType.ARRAY,
PrimitiveType.BOOLEAN, PrimitiveType.SHORT, PrimitiveType.CHAR);
PrimitiveType.INT,
PrimitiveType.OBJECT, PrimitiveType.ARRAY,
PrimitiveType.BOOLEAN, PrimitiveType.BYTE, PrimitiveType.SHORT, PrimitiveType.CHAR);
protected IfOp op;
@@ -70,6 +73,18 @@ public class IfNode extends GotoNode {
return elseBlock;
}
@Override
public boolean isSame(InsnNode obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof IfNode) || !super.isSame(obj)) {
return false;
}
IfNode other = (IfNode) obj;
return op == other.op;
}
@Override
public String toString() {
return InsnUtils.formatOffset(offset) + ": "
@@ -12,7 +12,7 @@ public enum IfOp {
private final String symbol;
private IfOp(String symbol) {
IfOp(String symbol) {
this.symbol = symbol;
}
@@ -23,19 +23,19 @@ public enum IfOp {
public IfOp invert() {
switch (this) {
case EQ:
return IfOp.NE;
return NE;
case NE:
return IfOp.EQ;
return EQ;
case LT:
return IfOp.GE;
return GE;
case LE:
return IfOp.GT;
return GT;
case GT:
return IfOp.LE;
return LE;
case GE:
return IfOp.LT;
return LT;
default:
throw new JadxRuntimeException("Unknown if operations type: " + this);
@@ -16,6 +16,23 @@ public class IndexInsnNode extends InsnNode {
return index;
}
@Override
public IndexInsnNode copy() {
return copyCommonParams(new IndexInsnNode(insnType, index, getArgsCount()));
}
@Override
public boolean isSame(InsnNode obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof IndexInsnNode) || !super.isSame(obj)) {
return false;
}
IndexInsnNode other = (IndexInsnNode) obj;
return index == null ? other.index == null : index.equals(other.index);
}
@Override
public String toString() {
return super.toString() + " " + InsnUtils.indexToString(index);
@@ -4,68 +4,68 @@ import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.NamedArg;
import jadx.core.dex.instructions.args.PrimitiveType;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.exceptions.DecodeException;
import com.android.dx.io.Code;
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 final MethodNode method;
private final DecodedInstruction[] insnArr;
private final DexNode dex;
private DecodedInstruction[] insnArr;
public InsnDecoder(MethodNode mthNode, Code mthCode) {
public InsnDecoder(MethodNode mthNode) {
this.method = mthNode;
this.dex = method.dex();
this.insnArr = DecodedInstruction.decodeAll(mthCode.getInstructions());
}
public InsnNode[] run() throws DecodeException {
InsnNode[] instructions = new InsnNode[insnArr.length];
public void decodeInsns(Code mthCode) throws DecodeException {
short[] encodedInstructions = mthCode.getInstructions();
int size = encodedInstructions.length;
DecodedInstruction[] decoded = new DecodedInstruction[size];
ShortArrayCodeInput in = new ShortArrayCodeInput(encodedInstructions);
try {
while (in.hasMore()) {
decoded[in.cursor()] = DecodedInstruction.decode(in);
}
} catch (Exception e) {
throw new DecodeException(method, "", e);
}
insnArr = decoded;
}
public InsnNode[] process() throws DecodeException {
InsnNode[] instructions = new InsnNode[insnArr.length];
for (int i = 0; i < insnArr.length; i++) {
DecodedInstruction rawInsn = insnArr[i];
if (rawInsn != null) {
InsnNode insn = decode(rawInsn, i);
if (insn != null) {
insn.setOffset(i);
insn.setInsnHashCode(calcHashCode(rawInsn));
}
instructions[i] = insn;
} else {
instructions[i] = null;
}
}
insnArr = null;
return instructions;
}
private int calcHashCode(DecodedInstruction insn) {
int hash = insn.getOpcode();
hash = hash * 31 + insn.getClass().getName().hashCode();
hash = hash * 31 + insn.getFormat().ordinal();
hash = hash * 31 + insn.getRegisterCount();
hash = hash * 31 + insn.getIndex();
hash = hash * 31 + insn.getTarget();
hash = hash * 31 + insn.getA();
hash = hash * 31 + insn.getB();
hash = hash * 31 + insn.getC();
hash = hash * 31 + insn.getD();
hash = hash * 31 + insn.getE();
return hash;
}
private InsnNode decode(DecodedInstruction insn, int offset) throws DecodeException {
switch (insn.getOpcode()) {
case Opcodes.NOP:
@@ -388,8 +388,7 @@ public class InsnDecoder {
case Opcodes.MOVE_EXCEPTION:
return insn(InsnType.MOVE_EXCEPTION,
InsnArg.reg(insn, 0, ArgType.unknown(PrimitiveType.OBJECT)),
new NamedArg("e", ArgType.unknown(PrimitiveType.OBJECT)));
InsnArg.reg(insn, 0, ArgType.unknown(PrimitiveType.OBJECT)));
case Opcodes.RETURN_VOID:
return new InsnNode(InsnType.RETURN, 0);
@@ -536,8 +535,9 @@ public class InsnDecoder {
InsnArg.reg(insn, 0, dex.getType(insn.getIndex())));
case Opcodes.NEW_ARRAY:
return insn(InsnType.NEW_ARRAY,
InsnArg.reg(insn, 0, dex.getType(insn.getIndex())),
ArgType arrType = dex.getType(insn.getIndex());
return new NewArrayNode(arrType,
InsnArg.reg(insn, 0, arrType),
InsnArg.reg(insn, 1, ArgType.INT));
case Opcodes.FILL_ARRAY_DATA:
@@ -606,21 +606,27 @@ public class InsnDecoder {
int resReg = getMoveResultRegister(insnArr, offset);
ArgType arrType = dex.getType(insn.getIndex());
ArgType elType = arrType.getArrayElement();
InsnArg[] regs = new InsnArg[insn.getRegisterCount()];
boolean typeImmutable = elType.isPrimitive();
int regsCount = insn.getRegisterCount();
InsnArg[] regs = new InsnArg[regsCount];
if (isRange) {
int r = insn.getA();
for (int i = 0; i < insn.getRegisterCount(); i++) {
regs[i] = InsnArg.reg(r, elType);
for (int i = 0; i < regsCount; i++) {
regs[i] = InsnArg.reg(r, elType, typeImmutable);
r++;
}
} else {
for (int i = 0; i < insn.getRegisterCount(); i++) {
regs[i] = InsnArg.reg(insn, i, elType);
for (int i = 0; i < regsCount; i++) {
int regNum = InsnUtils.getArg(insn, i);
regs[i] = InsnArg.reg(regNum, elType, typeImmutable);
}
}
return insn(InsnType.FILLED_NEW_ARRAY,
resReg == -1 ? null : InsnArg.reg(resReg, arrType),
regs);
InsnNode node = new FilledNewArrayNode(elType, regs.length);
node.setResult(resReg == -1 ? null : InsnArg.reg(resReg, arrType));
for (InsnArg arg : regs) {
node.addArg(arg);
}
return node;
}
private InsnNode cmp(DecodedInstruction insn, InsnType itype, ArgType argType) {
@@ -688,17 +694,6 @@ public class InsnDecoder {
return node;
}
private InsnNode insn(InsnType type, RegisterArg res, InsnArg... args) {
InsnNode node = new InsnNode(type, args == null ? 0 : args.length);
node.setResult(res);
if (args != null) {
for (InsnArg arg : args) {
node.addArg(arg);
}
}
return node;
}
private int getMoveResultRegister(DecodedInstruction[] insnArr, int offset) {
int nextOffset = getNextInsnOffset(insnArr, offset);
if (nextOffset >= 0) {

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