Compare commits

..

908 Commits

Author SHA1 Message Date
Skylot cc29da8e81 build: fix release build 2019-12-07 15:31:24 +00:00
Skylot d1a6841c20 fix: inline assign in complex conditions (#699) 2019-11-30 16:32:29 +00:00
Skylot 600842a1a6 fix: resolve error if input file don't has extension 2019-11-30 16:22:09 +00:00
Skylot 8ba3e935a5 build: update dependencies and gradle 2019-11-24 20:34:36 +00:00
Skylot 87504dd2cc refactor: additional checks for ssa vars and registers 2019-11-24 20:33:19 +00:00
Skylot e4e6f37949 fix: sort inner classes and methods by source lines 2019-11-19 20:05:21 +00:00
Skylot 4b314e9d99 fix: don't eliminate StringBuilder if no String arg present 2019-11-19 18:26:12 +00:00
Skylot a48ce296b8 fix: resolve code generation error for interface methods (#775) 2019-11-05 09:31:12 +00:00
Jan S cf3e17c4b8 feat(gui): support APK signature v3 (PR #773) 2019-11-01 19:31:24 +03:00
Skylot bae36f9720 fix: merge const block before return (#699) 2019-10-31 15:47:29 +00:00
Skylot 11db454b84 fix: duplicate result arg on instruction copy 2019-10-30 20:59:14 +00:00
Skylot 1b60c1d1a8 test: print smali code for debug purpose 2019-10-30 13:42:58 +00:00
Skylot 8321d5e380 fix: preserve arg type on PHI insn inline (#718) 2019-10-28 17:19:52 +00:00
Skylot 64c9ce2ab0 build: update sonarqube 2019-10-27 19:35:43 +00:00
Skylot 08f9a90c95 fix: force cast for null args in method invoke (temp fix for #724) 2019-10-27 16:53:25 +00:00
Skylot 9f06d6744e fix: increase region iterative traversal limit (#767) 2019-10-27 16:19:58 +00:00
Skylot f228a72118 fix(gui): fix search if class contains not generated inner class (#755) 2019-10-21 18:46:55 +01:00
Jan S 3249a5e0bc fix: workaround for IntelliJ bug on import line in build.gradle (PR #766) 2019-10-17 20:23:44 +03:00
Skylot d1ac43de33 fix(gui): add default contructor for classes serialized with GSON (#752) 2019-10-17 17:35:01 +03:00
Skylot 00f5e83506 fix: handle incorrect args count in signature (#763) 2019-10-17 16:51:06 +03:00
Skylot d3ecc1f640 fix: add dummy class if class loading exception occur (#763) 2019-10-17 16:51:03 +03:00
Jan S 902247fcdb fix: don't stop loading classes in case of an error (PR #764)
* fix: don't stop loading classes in case of an error
* style: reformat code
2019-10-15 20:25:18 +03:00
Skylot bd9e1096cc fix: handle methods with all NOPs (#744) 2019-08-30 15:37:38 +01:00
Skylot db892adf34 fix: don't run class process from visitors to avoid deadlock (#743) 2019-08-27 17:24:18 +01:00
Skylot 1cbaad3ec9 fix: make correct class members loading in jadx api (#742) 2019-08-25 19:53:12 +01:00
Skylot 401d08ea49 refactor: move all smali libs usage to one utility class 2019-08-21 14:45:32 +01:00
Skylot ba17f7bc00 refactor: move type with outer generic to different class 2019-08-15 21:43:57 +01:00
Skylot db2b537380 fix: try to resolve generic type variables (#662) 2019-08-15 21:39:43 +01:00
Skylot 06f26ef8f5 refactor: use enum for wildcard bounds instead of int 2019-08-15 14:31:30 +01:00
Skylot a71bb7a532 fix(gui): yet another fix for broken find usage action 2019-08-12 10:32:38 +01:00
Skylot 99934b5100 chore: update dependencies 2019-08-12 10:05:24 +01:00
Skylot ff5f6fca3c fix(gui): fix "Go to declaration" and "Find usage" menu actions 2019-08-11 22:03:47 +03:00
Skylot 3578f7d68f fix(gui): use editor font on tabs 2019-08-11 21:40:46 +03:00
Skylot 7bc01dcfa8 fix(gui): ignore mouse click on empty space in tree (#737) 2019-08-11 19:39:05 +03:00
Skylot bc7a748420 feat(cli): add options for change log level (#735) 2019-08-08 13:14:36 +03:00
Skylot c0194d025d refactor: fix misuse of immutable type flag 2019-08-03 17:31:13 +03:00
Skylot 19ca8a096b chore: resolve minor code issues in debug info parser 2019-08-03 14:19:54 +03:00
Skylot cf5bfc297b test: fix regression for code auto check 2019-08-02 21:05:03 +03:00
Skylot a17f9136dd refactor: enable class unloading after code generation 2019-08-01 23:29:30 +03:00
Skylot 7d07fb0b77 chore: fix issues reported by lgtm.com 2019-08-01 12:14:29 +03:00
Skylot 99935bada6 docs: update readme and contributing rules 2019-07-31 21:40:23 +03:00
Skylot be9dae57b9 fix: add explicit cast for byte literal in method invoke (#719) 2019-07-30 22:46:28 +03:00
Skylot 4629043721 fix: convert inner enums and fix inner classes reference (#719) 2019-07-30 20:49:31 +03:00
Skylot 068234f0ca fix: remove synchronization lock for code generation (#726) 2019-07-29 14:55:50 +03:00
Skylot ccb8ed1394 fix: add assign for inlined getter methods 2019-07-29 12:48:38 +03:00
Skylot 8d68d409eb test: another deboxing issue 2019-07-28 21:09:56 +03:00
Skylot e842e022ba fix: use nice name for 'package-private' in modifiers change message 2019-07-28 20:42:07 +03:00
Skylot 1e6b30343c fix: several improvements for multi-variable type search (#720) 2019-07-28 20:22:28 +03:00
Skylot ddedb8d8a0 fix: don't override type of method parameter in const deboxing (#723) 2019-07-26 16:14:27 +03:00
Skylot 472aa52706 fix: resolve some multi-thread issues 2019-07-25 21:53:37 +03:00
Skylot ab97084058 refactor: move passes list to root node 2019-07-25 17:54:04 +03:00
Skylot 0911b2dc2f test: NYI test for issue #722 2019-07-24 17:00:05 +03:00
Skylot fd7d08cb10 feat: initial deboxing implementation (#717) 2019-07-23 20:37:37 +03:00
Skylot 3ae8359408 fix: improve exception handler remove (#703) 2019-07-22 20:38:16 +03:00
Skylot 6b76a3c787 fix: protect method from second load 2019-07-22 18:43:02 +03:00
Skylot 9fbf9ef667 fix(gui): compare files extension in case insensitive way 2019-07-22 18:43:02 +03:00
Skylot c8de7b97dd fix: instead commenting move constructor call to the top (#704) 2019-07-21 19:45:22 +03:00
Skylot b32dc17dd7 fix: don't change AST before checks in ternary transform (#710) 2019-07-20 21:33:20 +03:00
Skylot 7c53b985cd refactor(gui): remove JCertificate node 2019-07-19 18:19:08 +03:00
Skylot c8df26f227 feat(gui): add class links for AndroidManifest.xml and other minor fixes 2019-07-19 18:03:40 +03:00
Skylot 3bc9671905 perf(gui): speed up line numbers rendering (#714) 2019-07-18 23:19:06 +03:00
Skylot 7fd959e6e3 refactor: improve variables handling in instruction wrapping 2019-07-17 22:53:00 +03:00
Skylot 24dc68652e fix: check that iteration variable in for-each loop not used outside (#708) 2019-07-17 22:42:33 +03:00
Skylot aad2d24c58 fix: unbind unused ssa variable after ternary conversion (#708) 2019-07-16 19:44:48 +03:00
Skylot 15d56abeb6 fix: read correct buffer size for string pool parsing (#712) 2019-07-15 21:19:58 +03:00
Skylot d89ec67888 style: resolve compiler warnings 2019-07-15 17:12:40 +03:00
Skylot f9f840fb9d refactor: remove redundant FieldArg and change arith one arg insn 2019-07-15 17:01:02 +03:00
Skylot 8e8a2faa10 fix(res): skip string if parsing failed (#712) 2019-07-14 17:06:19 +03:00
Skylot 0c2784bb42 refactor: inline fields in arithmetic operations 2019-07-14 15:09:01 +03:00
Skylot c555cd0825 fix: rename packages with reserved names (#711) 2019-07-14 13:13:00 +03:00
Skylot 92e28326a4 misc: don't add same edge insn several times 2019-07-13 13:24:52 +03:00
Skylot 2dbdd1f079 fix: support instructions removing in SimplifyVisitor 2019-07-13 13:19:58 +03:00
Skylot fc58022d56 misc: show shorter exception stacktrace in code 2019-07-13 13:17:22 +03:00
Skylot ed9fe8a573 fix: incorrect init values of inherited fields 2019-07-13 13:10:23 +03:00
Skylot 49e234d9f8 fix: improve finally extraction 2019-07-12 23:26:46 +03:00
Skylot a587ce88ea fix: ignore finally extraction with only one 'if' instruction (#709) 2019-07-12 21:21:14 +03:00
Skylot a530371b6f fix: improve StringBuilder elimination (#704) 2019-07-11 20:07:14 +03:00
Skylot 0c5a83c021 style: fix code style in test 2019-07-10 21:32:11 +03:00
Skylot 12bb632371 fix: always cast null objects in overloaded method (#707) 2019-07-10 21:11:02 +03:00
Skylot e4fc6774b1 fix: make correct hash calculation for GenericObject type (#705) 2019-07-10 16:58:52 +03:00
Skylot f57dfb3f2e test: check method override with generic arguments (#701) 2019-07-09 13:08:32 +03:00
Skylot c3f7a049d8 fix: ignore incorrect dex files in apk (#700) 2019-07-08 12:24:54 +03:00
Skylot 3eee83c2f2 fix: adjust insn reorder check in code shrink visitor (#695) 2019-07-07 14:18:21 +03:00
Skylot ed8c662631 fix: add generic types propagation (#695) 2019-07-06 19:12:31 +03:00
Skylot 850df18d7c refactor: update duplicate methods in InsnArg classes 2019-07-05 20:55:00 +03:00
Skylot 7f4da306c9 refactor: remove cloning library dependency 2019-07-05 20:45:28 +03:00
Skylot 424a8ffaf4 fix: inline constant strings (#685) 2019-07-05 19:10:57 +03:00
Skylot 8410e62531 fix: force one branch ternary in constructors (#685) 2019-07-05 17:14:46 +03:00
Skylot 533b686e0b fix: comment out instructions also before other constructor call (#685) 2019-07-05 17:05:38 +03:00
Skylot c6c54f90dc fix: comment out instructions before super call in constructor (#685) 2019-07-03 14:39:21 +03:00
Kend 0f5fd4e48a fix(gui): update Chinese translation (PR #697) 2019-06-27 12:39:48 +03:00
Skylot a7247e8a88 build: remove unused test-app submodule 2019-06-27 11:59:25 +03:00
Skylot c10a30346b style: reformat gradle files 2019-06-27 11:53:56 +03:00
Skylot 436e86fdf2 build: update gradle and dependencies 2019-06-27 11:16:44 +03:00
Jan S 29a137bde3 fix: jadx-gui.bat and jadx.bat do not work (#692) (PR #694) 2019-06-21 17:44:45 +03:00
Skylot f02a33ace3 fix: ignore NOPs in try-catch (#668) 2019-06-19 21:32:10 +03:00
Skylot 9c34a3154d build: reorder sections in release notes 2019-06-18 20:16:40 +03:00
skylot ed385e8cf1 feat: output decompilation results in json format (#676) 2019-06-18 16:06:56 +03:00
Skylot 554e119eb9 fix: don't rename constructors 2019-06-15 13:59:14 +03:00
Skylot aad70c7199 perf: cache types in dex nodes 2019-06-12 14:42:29 +03:00
Skylot a051ce6cf4 fix(scripts): support spaces in java path for windows scripts (#686) 2019-06-10 14:46:08 +03:00
skylot 40f19cce61 docs: add issue template 2019-06-06 13:33:47 +03:00
Skylot b158858349 fix: prevent NPE while processing try/catch regions (#673) 2019-05-29 15:50:33 +03:00
Antonello d6737860bb docs: added macOS installation with brew documentation (PR #675) 2019-05-29 15:47:50 +03:00
Skylot 123ba2baf1 fix: workaround for primitive values if type resolved incorrectly (#671) 2019-05-25 18:31:51 +03:00
Skylot f2f8936cd1 chore: fix indent of first line in fallback mode 2019-05-24 22:27:27 +03:00
Skylot f0f5c26896 fix: store condition blocks in 'if' region for correct blocks list (#669) 2019-05-24 17:36:18 +03:00
Skylot 6c61ce52a3 fix: handle cases with SSA variable used in several PHI's (#667) 2019-05-23 22:43:13 +03:00
Skylot 1830c273c0 fix: handle NOP instructions in unexpected places (#666) 2019-05-19 22:02:46 +03:00
Skylot 5efe4bd845 fix: add labels from NOP instructions in fallback mode (#666) 2019-05-19 18:47:06 +03:00
Skylot 75a6714057 fix: regenerate method code if unexpected instruction is found (#462) 2019-05-17 20:01:01 +03:00
Skylot 6339cc2088 chore: remove debug method invoke 2019-05-17 18:10:43 +03:00
Skylot 98e4c4b48d fix: merge new-array and fill-array-data with move between (#462) 2019-05-16 21:18:09 +03:00
Skylot 9d5dda12be fix: handle anonymous class self inlining (#604) 2019-05-16 21:18:01 +03:00
Skylot 84b9f11120 fix: improve errors handling 2019-05-16 13:07:00 +03:00
Skylot 2383c40105 fix: correct arg replace in PHI instruction (#462) 2019-05-15 19:04:54 +03:00
Skylot 305cf5379d fix: UnsupportedOperationException in overloaded method process (#462) 2019-05-15 17:01:52 +03:00
Skylot 9189f23e3e refactor(gui): rename Utils class due to clash with class from core 2019-05-15 17:01:52 +03:00
Skylot 628263343b fix(gui): use alias for field and method types in tree view 2019-05-15 17:01:52 +03:00
Skylot 19cf7c9f14 refactor: improve multi line warning print 2019-05-15 17:01:52 +03:00
Skylot 363cd85ba6 refactor: remove useless array creation 2019-05-15 17:01:52 +03:00
Skylot 7bb752715f fix: NPE if loading local file from CLI (jadx sample.apk) 2019-05-15 17:01:52 +03:00
Skylot 9622c948c9 refactor: use mode flags instead ThreadLocal in type inference 2019-05-15 17:01:52 +03:00
Skylot baea5247f4 test: type inference issue (#462) 2019-05-15 17:01:52 +03:00
Skylot 0ca2789a18 fix: prevent stack overflow in type inference if update tree is too deep 2019-05-15 17:01:52 +03:00
Skylot 119709b844 fix: stack overflow cause lamdba throw BootstrapMethodError in JDK 8 2019-05-15 17:01:52 +03:00
Jan S 1c914ff286 fix(gui): back button was defect since #653 2019-05-15 16:23:35 +03:00
Skylot 31a02a70a0 fix: rename class if all chars not printable (#622) 2019-05-10 22:43:00 +03:00
Jan S 8e0df4c423 fix(gui): printUsage for jadx-gui now shows the new gui only parameter(s) (PR #660) 2019-05-10 22:42:32 +03:00
Skylot 86a4ed7fb3 refactor: store all temp files in one temp dir, use NIO api instead commons-io 2019-05-10 21:27:45 +03:00
Skylot 19c57258fe fix: improve rename checks and show rename reason (#584) 2019-05-10 21:10:04 +03:00
Jan S fef3e55c55 feat(gui): select a class to open via command-line (PR #658) 2019-05-10 21:06:54 +03:00
Jan S 6f973ca2af feat(cli): decompile only a single class (PR #657) 2019-05-08 19:06:49 +03:00
Jan S 4b73d24d4b fix(gui): separate SearchBar for Java and Smali code areas (PR #653) 2019-05-06 20:58:13 +03:00
Jan S 65818dccb1 feat(gui): save the class tree width and restore it upon jadx-gui start (PR #606) 2019-05-06 20:54:52 +03:00
Skylot 7ac0b9f57c fix: redone class alias processing (#532) 2019-05-06 17:59:35 +03:00
Skylot 699f7f6716 fix: treat filesystem as case insensitive by default, option added for change 2019-05-05 22:34:34 +03:00
Skylot dae882d55c fix: improve generated code on errors 2019-05-05 18:23:37 +03:00
Skylot c0a0bba5d8 style: add design checkstyle rules 2019-05-04 11:13:02 +03:00
Skylot 52ba33c5a3 fix: avoid local variables collision with full class names (#647) 2019-05-03 22:40:18 +03:00
Skylot 156c979842 fix: search smali class by original name 2019-05-03 18:32:36 +03:00
Skylot f846df5371 fix: rename field if collide with any root package (#647) 2019-05-03 18:05:45 +03:00
Jan S 4a39af7cb3 feat(gui): make search bar usable for smali code (PR #652) 2019-05-01 00:17:08 +03:00
skylot c7890f2468 style: enforce code style using checkstyle and spotless with eclipse formatter (PR #650) 2019-04-30 00:04:16 +03:00
Ahmed Ashour e1dfb4ee59 fix: byte to number without cast (#596) (PR #638) 2019-04-29 16:08:28 +03:00
Ahmed Ashour 031582dd55 feat(gui): show smali (#197) (PR #635) 2019-04-29 16:07:31 +03:00
Ahmed Ashour 745c52e8db fix(gui): closing jadx main window terminates JVM (#639) (PR #641) 2019-04-29 15:53:00 +03:00
Ahmed Ashour cab3f5daa7 fix: always use FileUtils.createTempFile (PR #634) 2019-04-25 20:25:55 +03:00
Ahmed Ashour 77cee15d64 fix: add cast for null in overloaded methods (#636) (PR #637) 2019-04-25 20:24:37 +03:00
Skylot e7e7b664dd feat: add option to disable anonymous class inline (#633) 2019-04-25 12:37:52 +03:00
Ahmed Ashour db7f2cf548 fix: loading aar/jar files (#631) (PR #632) 2019-04-25 11:11:03 +03:00
Ahmed Ashour 58365a8907 fix(gui): remove tree children expansions as well (PR #630) 2019-04-24 19:45:36 +03:00
Ahmed Ashour 172f7f7534 fix(gui): preserve main window maximized state (PR #626) 2019-04-24 19:42:13 +03:00
Ahmed Ashour 05e5c82c9b fix: remove redundant cast over similar conditions (PR #612) 2019-04-24 19:34:10 +03:00
Ahmed Ashour 30fbf4bcfa refactor: better place for removing parenthesis (PR #627) 2019-04-24 19:33:05 +03:00
Ahmed Ashour 9645f33c7b fix: bitwise or/and with non-boolean (#628) (PR #629) 2019-04-24 19:31:49 +03:00
Skylot 336d6ce189 fix(gui): use same font loader as code viewer (#584) 2019-04-23 21:30:03 +03:00
Skylot f283ef4342 fix: improve class renaming and add checks for class alias usage (#532) 2019-04-22 21:31:27 +03:00
Skylot 41abbb12a0 fix: resolve check cast exception in string constructor simplify 2019-04-22 16:55:47 +03:00
Skylot 89b80900f0 fix: produce more deterministic code 2019-04-22 16:55:47 +03:00
Skylot f1539d2e37 fix: resolve NPE due to not yet processed class (#595) 2019-04-22 16:55:47 +03:00
Ahmed Ashour 84ef6d0049 test: add test case for #596 (PR #619) 2019-04-22 16:53:27 +03:00
Ahmed Ashour aa41a4d93b feat(gui): add "Go To Declaration" in menu (PR #618) 2019-04-22 16:52:42 +03:00
Skylot 616752759b chore: fix some issues reported by sonar 2019-04-21 16:34:31 +03:00
Skylot dc004f37ee style: fix code formatting 2019-04-21 16:23:34 +03:00
sergey-wowwow cfbbd99bb8 fix(gui): use command (CMD) button for MacOS (#165) (PR #616) 2019-04-21 16:18:49 +03:00
sergey-wowwow c74b7f20a5 fix: generates XML key names if empty (#394) (PR #615) 2019-04-21 14:26:07 +03:00
Ahmed Ashour 9d22b3caa8 fix(gui): sort classes by case insensitivity (PR #613) 2019-04-20 20:03:27 +03:00
Ahmed Ashour f8039733cc feat(gui): save tree expansions in project (PR #605) 2019-04-20 20:02:00 +03:00
Ahmed Ashour 87ca14afea test: add test case for incorrect continue (PR #611) 2019-04-20 19:37:12 +03:00
Ahmed Ashour c134329ce9 fix: cast of int-to-(number) when int is boolean (#596) (PR #602) 2019-04-20 19:29:41 +03:00
Ahmed Ashour 2148d4b0f5 test: add test case for #597 (PR #603) 2019-04-15 21:25:47 +03:00
Ahmed Ashour 632cc3ec16 fix: add primitive cast in ternary for byte and short (PR #601) 2019-04-15 18:09:01 +03:00
Ahmed Ashour bcfed5b362 fix: generics constructor types (PR #594) 2019-04-14 22:01:08 +03:00
Skylot 4cb9f23a7d fix: inline anonymous classes with not default constructor (#450) 2019-04-14 19:02:42 +03:00
Ahmed Ashour 0aa7173e83 chore: upgrade smali to 2.2.7 (PR #593) 2019-04-14 15:30:21 +03:00
Ahmed Ashour b1b49e6195 fix: remove declaration of unused variable (PR #590) 2019-04-14 14:29:38 +03:00
Ahmed Ashour d23f4ac16a feat: support smali files (#391) (PR #588) 2019-04-14 12:05:07 +03:00
Ahmed Ashour 01da127c4e fix: remove generics cast when object types match (#591) (PR #592) 2019-04-14 11:39:27 +03:00
Skylot ccb9c46005 style: fix imports and indents 2019-04-12 18:23:41 +03:00
Ahmed Ashour 01dfae4ac7 test: add test case for anonymous type, and move assertions to NYI (PR #589) 2019-04-12 18:15:08 +03:00
Ahmed Ashour 395cae439e fix: handle NPE for methods with removed instructions (#342) (PR #583) 2019-04-12 18:12:38 +03:00
Ahmed Ashour eb77aa51b2 fix: conditions in ternary if (#449) (PR #558) 2019-04-12 18:11:22 +03:00
Ahmed Ashour ac1d1a5858 fix(gui): disable all components on saving the settings (PR #586) 2019-04-12 18:10:16 +03:00
Ahmed Ashour 74a72a5ce0 feat: add options to configure "renaming" (#570) (PR #582) 2019-04-12 18:08:50 +03:00
Ahmed Ashour a1bfdc6323 fix: remove static field redundant array type when initialized (PR #580) 2019-04-11 13:07:14 +03:00
Ahmed Ashour 0720992998 test: add test case for #130 (PR #578) 2019-04-09 19:10:47 +03:00
Ahmed Ashour ef28875a8e test: add test case for #43 (PR #576) 2019-04-09 19:02:04 +03:00
Ahmed Ashour 10fb57f6fb test: add test case for #101 (PR #577) 2019-04-09 18:45:40 +03:00
Ahmed Ashour 7186a4a2d7 test: add two cases for switch-try-break combination (PR #575) 2019-04-09 18:04:16 +03:00
Ahmed Ashour ab4721a8b3 fix: don't rename R class in deobfuscation (#572) (PR #573) 2019-04-09 17:39:59 +03:00
Ahmed Ashour 23c05bb5f6 fix: search in resource classes by original name, not by alias (#562) (PR #571) 2019-04-09 17:36:51 +03:00
Ahmed Ashour fe41174be8 feat: add generic method information to .jcst (PR #564) 2019-04-09 17:35:34 +03:00
Ahmed Ashour 513766d45b fix: remove field redundant array type when initialized (PR #567) 2019-04-08 17:21:07 +03:00
Ahmed Ashour 79ccaadaff fix: handle big .jar files (using multi-dex option) (#390) (PR #568) 2019-04-08 17:20:04 +03:00
Ahmed Ashour ecaa87e7ae fix: remove redundant array type when initialized with declaration (PR #566) 2019-04-08 13:07:34 +03:00
Ahmed Ashour 0a08d8b653 fix(gui): NPE if the autosave is enabled and project is initial (PR #565) 2019-04-08 13:05:22 +03:00
Ahmed Ashour 7b18d3a3a8 fix: ignore not generated insns in CodeShrinker visitor (PR #560) 2019-04-08 13:04:45 +03:00
Ahmed Ashour 058e4c9fd7 fix: remove redundant wrapping for same arith operations (PR #559) 2019-04-04 15:22:05 +03:00
Ahmed Ashour 9d257cd115 fix(res): ignore resource entry with -1 key (#556) (PR #557) 2019-04-03 12:11:33 +03:00
Ahmed Ashour 1e5541175e fix: move test class and rename GUI message (PR #555) 2019-04-02 23:05:58 +03:00
Ahmed Ashour bae7f1b09c fix: field increment (PR #550) 2019-04-02 12:10:44 +03:00
Ahmed Ashour e6e8f6367e fix: variable usage with enhanced for loop (#535) (PR #547) 2019-04-01 21:07:28 +03:00
Ahmed Ashour 3970fce503 test: use NYI instead of comment (PR #551) 2019-04-01 21:05:51 +03:00
Ahmed Ashour eda2272430 chore: use functional interface instead of ISettingsUpdater (PR #548) 2019-04-01 15:33:47 +03:00
Ahmed Ashour 207ce6cbbe chore: fix "unused" warnings (PR #549) 2019-04-01 15:19:09 +03:00
Ahmed Ashour 1d3e6ecbcf chore: use lambda (PR #544) 2019-03-31 21:36:11 +03:00
Ahmed Ashour a5a951cfa1 test: add test case for #535 (PR #545) 2019-03-31 21:34:18 +03:00
Ahmed Ashour a6f935ed68 fix: close resource (PR #546) 2019-03-31 21:33:45 +03:00
Ahmed Ashour b09c7ba6b8 feat(gui): support project (#526) (PR #543) 2019-03-31 20:20:27 +03:00
Ahmed Ashour ec66476ac6 fix: better String constructor from byte and char arrays (#530) (PR #533) 2019-03-31 20:17:14 +03:00
Skylot 008216d599 fix: don't cast overloaded methods with generics from other class (#448) 2019-03-30 20:50:12 +03:00
Skylot 4a92275adb test: allow use Eclipse compiler in tests (#536) 2019-03-29 16:25:52 +03:00
Ahmed Ashour 6fca311de0 test: add test case for #536 (PR #537) 2019-03-29 16:10:33 +03:00
Skylot 8e279f55f1 style: fix editorconfig to preserve formating in IntelliJ Idea 2019-03-29 12:42:02 +03:00
Skylot 2caac21b73 test: limit auto check execution time 2019-03-29 12:40:51 +03:00
Skylot c5d977baca test: always use runtime compiler for build dex (#536) 2019-03-29 12:39:58 +03:00
Ahmed Ashour b5344f4577 fix: redundant byte and short cast (#538) (PR #539) 2019-03-29 11:33:39 +03:00
Ahmed Ashour 0fa3842a70 test: warn about compiler not found (PR #540) 2019-03-29 11:27:11 +03:00
Ahmed Ashour 6fc7c7a462 chore: don't create unneeded StringBuilder (PR #541) 2019-03-29 11:23:42 +03:00
Ahmed Ashour 98dbd48890 chore: better logging (#528) 2019-03-28 14:27:48 +03:00
Ahmed Ashour 55fc498359 refactor: use Path instead of File (PR #527) 2019-03-27 20:40:13 +03:00
Ahmed Ashour ba6dd081e9 fix(gui): add missing translations keys, and ensure all files match (PR #525) 2019-03-27 20:10:30 +03:00
Skylot 7cdb0318b1 style: resolve some sonar warnings 2019-03-27 14:07:16 +03:00
Skylot 17d8516d3b fix: made correct instructions remove in new filled array replacement (#461) 2019-03-27 13:30:01 +03:00
Ahmed Ashour b78349aef7 fix: handle boolean condition with bitwise OR and AND (#202) (PR #522) 2019-03-27 11:41:56 +03:00
Skylot eb141ad12b test: add tests for #474 2019-03-26 23:13:11 +03:00
Skylot b446bf275c refactor: move filesystem case sensitivity value to JadxArgs 2019-03-26 23:12:17 +03:00
Ahmed Ashour b7109b1b2b test: remove commented code and style fixes (PR #520) 2019-03-26 21:04:22 +03:00
Ahmed Ashour 3537f849ef fix(gui): detect if a window is opened inside a visible screen (PR #521) 2019-03-26 19:43:23 +03:00
Ahmed Ashour 9557f04fe7 test: add test case for #202 (PR #519) 2019-03-26 19:41:16 +03:00
Skylot 1bb53329b5 fix: use alias as a base for class rename if file system is case sensitive (#474) 2019-03-26 19:13:40 +03:00
Skylot e026345a45 feat: new implementation for type inference approach and variable declaration
BREAKING CHANGE: some parts of jadx was rewritten from scratch
  - type inference
  - variable declaration
  - `finish` block extraction
2019-03-26 16:16:54 +03:00
Skylot 3492ec3517 fix: change exception to soft warning for getType in RegisterArg 2019-03-26 15:44:32 +03:00
Ahmed Ashour eb2a1734d3 fix: xor with boolean (#409) (PR #516) 2019-03-26 15:29:39 +03:00
skylot aa8a7c03c3 style: enforce strict style rules with editorconfig (PR #510) 2019-03-26 14:21:47 +03:00
Ahmed Ashour 36ee994eb8 test: add test case for "xor with boolean" (#409) (PR #514) 2019-03-25 19:53:59 +03:00
Ahmed Ashour 65544c64bf test: warn about compiler not found, and gracefully report failure (PR #511) 2019-03-25 16:09:15 +03:00
wwj402 b49acfdacf fix(gui): update chinese simplified language (PR #508) 2019-03-24 23:05:23 +03:00
Skylot 29d3ce15a8 fix: rename parameters in annotations (#504) 2019-03-24 16:59:55 +03:00
Skylot 84cb6b9569 Merge branch 'master' into type-inference-wip
# Conflicts:
#	jadx-core/src/main/java/jadx/core/codegen/NameGen.java
#	jadx-core/src/main/java/jadx/core/dex/attributes/AttributeStorage.java
#	jadx-core/src/main/java/jadx/core/dex/attributes/nodes/PhiListAttr.java
#	jadx-core/src/main/java/jadx/core/dex/instructions/IndexInsnNode.java
#	jadx-core/src/main/java/jadx/core/dex/instructions/InsnDecoder.java
#	jadx-core/src/main/java/jadx/core/dex/instructions/args/ArgType.java
#	jadx-core/src/main/java/jadx/core/dex/instructions/args/RegisterArg.java
#	jadx-core/src/main/java/jadx/core/dex/instructions/args/SSAVar.java
#	jadx-core/src/main/java/jadx/core/dex/regions/conditions/IfRegion.java
#	jadx-core/src/main/java/jadx/core/dex/visitors/ModVisitor.java
#	jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/helpers/BlocksPair.java
#	jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/helpers/BlocksRemoveInfo.java
#	jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/LocalVar.java
#	jadx-core/src/main/java/jadx/core/dex/visitors/regions/ProcessVariables.java
#	jadx-core/src/main/java/jadx/core/dex/visitors/shrink/CodeShrinkVisitor.java
#	jadx-core/src/main/java/jadx/core/xmlgen/entry/EntryConfig.java
2019-03-24 12:38:20 +03:00
Skylot a848eab407 Merge branch 'master'' 2019-03-24 12:24:57 +03:00
Skylot e1f4955286 Merge branch 'master' into type-inference-wip
# Conflicts:
#	jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java
#	jadx-core/src/main/java/jadx/core/Jadx.java
#	jadx-core/src/main/java/jadx/core/dex/attributes/AFlag.java
#	jadx-core/src/main/java/jadx/core/dex/attributes/AType.java
#	jadx-core/src/main/java/jadx/core/dex/visitors/regions/LoopRegionVisitor.java
#	jadx-core/src/main/java/jadx/core/dex/visitors/regions/RegionMakerVisitor.java
#	jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java
#	jadx-core/src/test/java/jadx/tests/functional/TypeMergeTest.java
#	jadx-core/src/test/java/jadx/tests/integration/TestFloatValue.java
#	jadx-core/src/test/java/jadx/tests/integration/TestStringBuilderElimination2.java
#	jadx-core/src/test/java/jadx/tests/integration/arith/TestArith.java
#	jadx-core/src/test/java/jadx/tests/integration/arith/TestFieldIncrement2.java
#	jadx-core/src/test/java/jadx/tests/integration/arrays/TestArrays3.java
#	jadx-core/src/test/java/jadx/tests/integration/arrays/TestArrays4.java
#	jadx-core/src/test/java/jadx/tests/integration/conditions/TestTernary2.java
#	jadx-core/src/test/java/jadx/tests/integration/debuginfo/TestReturnSourceLine.java
#	jadx-core/src/test/java/jadx/tests/integration/generics/TestGenerics2.java
#	jadx-core/src/test/java/jadx/tests/integration/inline/TestInlineInLoop.java
#	jadx-core/src/test/java/jadx/tests/integration/invoke/TestCastInOverloadedInvoke.java
#	jadx-core/src/test/java/jadx/tests/integration/loops/TestArrayForEach2.java
#	jadx-core/src/test/java/jadx/tests/integration/loops/TestIndexForLoop.java
#	jadx-core/src/test/java/jadx/tests/integration/names/TestNameAssign2.java
#	jadx-core/src/test/java/jadx/tests/integration/switches/TestSwitchBreak.java
#	jadx-core/src/test/java/jadx/tests/integration/trycatch/TestFinallyExtract.java
#	jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatchFinally8.java
2019-03-24 12:19:19 +03:00
Skylot ca21ca5d81 test: rewrite Spock tests to JUnit 5 2019-03-24 11:23:24 +03:00
Ahmed Ashour 2399bfb784 test: add case for #504 2019-03-24 10:47:52 +03:00
Ahmed Ashour 11cee083ba style: use character instead of string, for better performance (PR #503) 2019-03-24 10:05:26 +03:00
Skylot 6e66dc25c8 fix: additional checks for loop exit edges and 'for' conversion (#483) 2019-03-23 23:58:05 +03:00
Skylot 999793c944 fix: skip trailing bytes in resource table decoding (#487) 2019-03-23 13:25:06 +03:00
Ahmed Ashour d111fd0680 feat: add a flag to disable debug info (#276) (PR #502) 2019-03-23 10:35:00 +03:00
Ahmed Ashour eed762df44 fix(gui): exclusion disablement should be exact package match (PR #500) 2019-03-23 10:32:24 +03:00
Ahmed Ashour d3dbdb24af test: use @NYI instead of commented TODO (PR #499) 2019-03-23 10:26:44 +03:00
Ahmed Ashour e585c4ec46 test: add MissingGenericsTypesTest (PR #498) 2019-03-23 10:25:14 +03:00
Skylot 66421be942 test: add tests for some known issues 2019-03-22 21:00:34 +03:00
Ahmed Ashour 9695291e37 test: case for #62 (PR #497) 2019-03-22 21:00:10 +03:00
Ahmed Ashour b65c386b6a test: migrate all assertions to JUnit5 (PR #496) 2019-03-22 20:37:20 +03:00
Ahmed Ashour cd6f6b7a83 test: add NotYetImplemented feature (PR #495) 2019-03-22 20:11:36 +03:00
Ahmed Ashour cdaecb31df chore: upgrade dependencies (PR #494) 2019-03-22 20:10:48 +03:00
Skylot 5169dc52dd fix: remove invalid chars from class names (#488) 2019-03-22 19:54:45 +03:00
Skylot f72abb2867 test: add test methods for load and check classes from smali files 2019-03-22 19:51:29 +03:00
Skylot 2c0725390e fix: check variable usage before convert indexed loop to for-each variant (#483) 2019-03-22 19:30:20 +03:00
Skylot 5a940a3baf build: update gitlab config 2019-03-22 19:30:20 +03:00
Ahmed Ashour 16b6345c7f test: migrate to JUnit 5 (PR #493) 2019-03-22 17:36:13 +03:00
tRuNKator 5f0dbf856b fix: don't rename R class fields alias (PR #492)
* fix: R class fields alias
* implemented with clearer approach
2019-03-22 14:54:22 +03:00
Ahmed Ashour 2e9039da4e fix(gui): show java version, instead of VM version in about dialog (PR #489) 2019-03-22 11:26:29 +03:00
tRuNKator 650cf31562 fix: resource qualifiers (PR #487) 2019-03-22 11:05:08 +03:00
tRuNKator 42b7843761 fix: use quantity attribute for plurals (PR #486) 2019-03-22 11:04:30 +03:00
Ahmed Ashour d5f4266283 fix: rename class with reserved java keywords (#485) (PR #488) 2019-03-22 10:55:44 +03:00
Ahmed Ashour 988ada3ce9 style: remove unneeded casts (PR #481)
As detected by Eclipse, and the test case doesn't depend on the explicit casting.
2019-03-21 17:16:33 +03:00
Ahmed Ashour 74562e6868 test: assertion already passes (PR #482) 2019-03-21 17:14:00 +03:00
Ahmed Ashour dd2e7e879b fix: add missing import for class generics map (PR #480)
* Fix missing import for class Generics map.
* Add import only when needed (in non-inner class declaration)
* Remove unneeded import
2019-03-21 17:11:56 +03:00
Skylot 365c1faf25 Merge branch 'master' into type-inference-wip 2019-03-20 14:36:20 +03:00
Ahmed Ashour 9797fe5b81 fix(gui): sort resources according to their type, then name (PR #479) 2019-03-20 14:04:51 +03:00
Skylot 6d052d39ad build: check repo name and build type to skip deploy on PR and forks 2019-03-20 13:59:19 +03:00
Ahmed Ashour 2b242b9109 style: remove unused imports (PR #475) 2019-03-20 13:28:58 +03:00
skylot 1c8741332e GUI: disable package if "excluded" (#477) 2019-03-20 13:25:17 +03:00
Ahmed Ashour a3ff03c8f3 GUI: disable package if "excluded" 2019-03-20 09:37:57 +01:00
Skylot 3019ee5655 build: check secret variable to skip deploy on pull requests 2019-03-19 21:22:28 +03:00
Skylot 03ae3bcefa fix: process field init code in dependency collector (#467) 2019-03-19 16:45:40 +03:00
Skylot 52deb48aac fix: move instruction out of try/catch (#468) 2019-03-19 16:45:40 +03:00
Jan Peter Stotz 214866fcb9 chore: About box and logos 2019-03-16 18:12:39 +01:00
Skylot 7654661b77 fix: inline desugared lambda classes (#467) 2019-03-15 22:19:43 +03:00
Skylot 51a9c741a5 chore: update dependencies and gradle 2019-03-15 19:37:19 +03:00
Skylot bce86d3211 build: check env variables for deploy stages (i.e skip deploy in forks) 2019-03-14 18:22:19 +03:00
tRuNKator a4a8b05ef0 fix: replace dot character with underscore in style resource name (PR #466) 2019-03-14 15:46:37 +03:00
Skylot 6116a75022 fix: rename R fields using resource names (#465) 2019-03-12 20:27:25 +03:00
Skylot 7243ab5cb6 fix: don't replace resources names with field names (#465) 2019-03-12 19:35:05 +03:00
Jan S 43538902a3 fix: restore support for AAR files (issue #95) (PR #464) 2019-03-09 17:20:34 +03:00
Skylot cf79a519d3 refactor: move code shrink visitor to separate package and extract inner classes 2019-03-03 21:03:03 +03:00
Skylot d069928613 fix: check if synthetic class not yet processed but must be removed (#450) 2019-03-02 19:09:31 +03:00
Skylot dd13edf262 fix: don't remove empty default constructor if other constructors exists (#460) 2019-03-02 17:31:12 +03:00
Skylot 653bb2ac10 fix: hide first argument instead remove for constructor in inner class 2019-03-02 16:02:17 +03:00
Skylot cbdc2496fc fix: check block before insert additional move instruction for type inference 2019-03-01 23:36:26 +03:00
Skylot 3de04cb638 refactor: use flags to mark registers with immutable type 2019-02-28 16:32:11 +03:00
Skylot 68d074aecf fix: change type update collection to produce deterministic results 2019-02-27 19:03:05 +03:00
Skylot 0df5aa80fe refactor(cli): add missing generic for JCommanderWrapper 2019-02-26 20:12:40 +03:00
Skylot 28bcad202a refactor: resolve deprecation warning for TypeGen.literalToString method 2019-02-26 20:09:05 +03:00
Skylot 16d8d41baf Merge branch 'master' into type-inference-wip
# Conflicts:
#	jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockSplitter.java
#	jadx-core/src/main/java/jadx/core/dex/visitors/ssa/EliminatePhiNodes.java
2019-02-26 20:04:35 +03:00
Skylot 7bd175220e fix: add correct type propagation for check-cast and move instructions (#401) 2019-02-26 19:56:09 +03:00
Skylot 28d348b364 fix: additional checks for synthetic methods remove, rename and inline (#452) 2019-02-22 17:50:31 +03:00
Jan S 91691fbd6a fix: allow APK files without code (no contained dex files) (PR #455) 2019-02-22 17:45:21 +03:00
Skylot 9856b6d3c5 fix: remove invalid chars from class names (#453) 2019-02-21 19:14:55 +03:00
Skylot e1ca290424 fix: avoid ConcurrentModificationException in EliminatePhiNodes pass 2019-02-21 15:29:38 +03:00
Skylot 0fa19fb0ac fix: remove unreachable blocks (#451) 2019-02-21 15:24:18 +03:00
Skylot ab7b6fc29f refactor: don't use additional class for jadx warnings 2019-02-20 14:43:59 +03:00
Skylot f8acc31b0b fix(gui): remove output directories from persistent settings (#447) 2019-02-20 10:40:39 +03:00
Skylot 4197365131 fix: improve fallback mode dump (add types, remove label after if) 2019-02-19 21:11:15 +03:00
Skylot 389caf1825 fix: improve filled array detection 2019-02-18 23:57:53 +03:00
Skylot bcadc28207 fix(gui): use system font as default instead bundled Hack (#442, #445) 2019-02-17 19:01:03 +03:00
Skylot 7e95758a6e fix(gui): add scroll to preferences dialog 2019-02-17 18:48:27 +03:00
Skylot d44dd0de84 fix(gui): show current font in preferences 2019-02-17 18:46:05 +03:00
Skylot 5cee498e1d Merge branch 'master' into type-inference-wip 2019-02-17 16:10:09 +03:00
Skylot db1b027da2 fix: improve bridge methods renaming (#397) 2019-02-16 13:07:13 +03:00
Skylot 7f4e641860 fix: skip duplicated block in complex if (#441) 2019-02-15 16:27:43 +03:00
Skylot 710245d597 fix: replace recursive analysis algorithms with iterations to avoid StackOverflow on big methods (#441) 2019-02-14 21:17:31 +03:00
Skylot b689efcc9f fix: forbid to change types for methods arguments 2019-02-11 19:21:26 +03:00
Skylot 89563b624b Merge branch 'master' into type-inference-wip 2019-02-11 15:02:52 +03:00
skylot 8c7140d6b8 fix: change not allowed access modifiers for methods (#387) (PR #439)
Fix visibility access modifies for methods (see discussions in #370 and #387):
    * all virtual methods become public
    * direct methods become private (instead constructors and static methods for now)
    * such modifications perform by default and can be disabled by the option in preferences (`--respect-bytecode-access-modifiers` in jadx-cli)
    * if changed to method added comment (`Access modifiers changed, original: private`)
2019-02-11 14:56:03 +03:00
Skylot c892395089 fix: redone finally extract 2019-02-11 14:50:16 +03:00
Skylot 4ce5cc8492 fix: use multi-variable type search algorithm if type propagation is failed 2019-02-04 18:34:36 +03:00
Skylot 9b091b7c08 fix: reimplement variable declaration visitor 2019-02-04 18:34:00 +03:00
Skylot 7b14e322d3 test: improve test checks 2019-02-04 16:22:50 +03:00
Skylot 21acaa8d37 fix: resolve mix up in SKIP and DONT_GENERATE flags 2019-02-04 16:22:11 +03:00
Jan S bf42b97580 build: compile for Java 8; enable G1GC on Java 8 (PR #436) 2019-01-23 17:34:03 +03:00
Skylot c705f8cbff Merge branch 'master' into type-inference-wip 2019-01-23 11:09:47 +03:00
skylot f8c0449d4e feat(gui): add icons to jadx-gui (#420) (PR #428) 2019-01-23 11:00:24 +03:00
Skylot b28eaa1a94 fix(gui): add synchronization to SimpleIndex class (#435) 2019-01-23 10:06:13 +03:00
Skylot be509c7104 fix(gui): use editor font in search node column 2019-01-23 10:05:00 +03:00
Skylot 2931617202 fix(gui): use editor font in files tree and fix bundled font loading 2019-01-23 09:39:57 +03:00
Skylot 82d0d622a8 fix: refactor, improve performance and fix some issues in resource processing
fix(gui): instead gradle export was executed normal export
fix(gui): content of some resource files was not shown
perf: direct resource files saving without full length buffer in memory
perf(gui): line numbers will be disabled on big files due to performance issue
feat(gui): click on HeapUsageBar will run GC and update memory info
feat(gui): add more file types for syntax highlights
refactor: ResContainer class changed for support more types of data (added link to resource file)
2019-01-22 18:51:09 +03:00
Skylot bcaca781b1 style(gui): reformat code and fix some warnings 2019-01-21 13:47:05 +03:00
Jan S ffedaea505 fix(gui): limit the spare memory to max. 512MiB (#434) 2019-01-21 09:52:00 +03:00
Skylot aec986447e fix: support multi-exception catch blocks (#421) 2019-01-19 16:28:25 +03:00
Skylot b0e3cfedf4 fix: update apksig library to latest version (#431) 2019-01-19 09:49:20 +03:00
Skylot da41efa3db fix: force rename by checks from RenameVisitor (#432) 2019-01-18 16:50:11 +03:00
Jan S 9e0cd2e14e fix(gui): add synchronizations to search index creation (#433)
* fix: unsynchronized search index creation (code usage) results in ArrayIndexOutOfBoundsException and stuck at 99%

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

* add node

* add certificate panel

* add certifcate manager

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

* ресурсы

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

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

* start tests

* more tests

* signature test

* fingerprint test

* public key test

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

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

Java 1.7 version of dx.jar compiled with plaform/libcore at commit
c3e562a and platform/dalvik at commit db9197b with
https://android-review.googlesource.com/#/c/221127/1 cherry-picked on
top of it.
2016-05-02 09:50:39 -07:00
齐振芳 b4472fd7d4 delete comments 2016-05-02 09:03:25 +08:00
Alex Light 796d02506a Replace jadx-core/lib/dx-1.10.jar with recent AOSP dx.jar
Recently support has been added to AOSP for generating and running
version 037 dex files. In order to load these we update the dx.jar
with a recent version built from AOSP.
2016-04-28 16:54:55 -07:00
齐振芳 467f729f06 add file type detect, jadx file by file's header, not only file's extension 2016-04-28 16:25:14 +08:00
Tneciv 050ec8b988 Create Messages_zh_CN.properties
add translation of Chinese
2016-04-05 18:35:52 +08:00
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
968 changed files with 63995 additions and 12042 deletions
+14
View File
@@ -0,0 +1,14 @@
coverage:
precision: 2
round: down
range: "50...100"
status:
project:
default: on
patch:
default: on
changes:
default: off
comment: false
+20
View File
@@ -0,0 +1,20 @@
# EditorConfig is awesome: https://EditorConfig.org
root = true
[*]
end_of_line = lf
insert_final_newline = true
indent_style = tab
tab_width = 4
continuation_indent_size = 8 #IntelliJ Idea specific workaround
charset = utf-8
trim_trailing_whitespace = true
[*.yml]
indent_style = space
indent_size = 2
[*.bat]
end_of_line = crlf
@@ -0,0 +1,22 @@
---
name: Decompilation error
about: Create a report to help us improve jadx decompiler
title: "[core]"
labels: Core, bug
assignees: ''
---
**Checks before report**
- check [latest unstable build](https://bintray.com/skylot/jadx/unstable/_latestVersion#files) (maybe issue already fixed)
- check [Troubleshooting Q&A](https://github.com/skylot/jadx/wiki/Troubleshooting-Q&A) section on wiki
- search existing issues by exception message
**Describe error**
- provide full name of method or class with error
- provide full java stacktrace
**Note**: no need to copy method fallback code (commented pseudocode)
- attach or provide link to apk file (double check apk version)
**Note**: GitHub don't allow attach files with `.apk` extension, but you can change extension by adding `.zip` at the end :)
+10
View File
@@ -0,0 +1,10 @@
---
name: Feature Request
about: Suggest an idea for jadx
title: "[feature]"
labels: new feature
assignees: ''
---
*Describe your idea:*
+5
View File
@@ -0,0 +1,5 @@
:exclamation: Please review the [guidelines for contributing](../CONTRIBUTING.md#Pull-Request-Process)
### Description
Please describe your pull request.
Reference issue it fix.
+8 -2
View File
@@ -9,14 +9,20 @@ out/
*.iml
*.ipr
*.iws
.attach_pid*
*.hprof
**/.DS_Store
bin/
target/
build/
classes/
idea/
.gradle/
gradle.properties
node_modules/
jadx-output/
*-tmp/
*.dex
@@ -24,4 +30,4 @@ gradle.properties
*.dump
*.log
*.cfg
*.orig
+37
View File
@@ -0,0 +1,37 @@
variables:
GRADLE_OPTS: "-Dorg.gradle.daemon=false"
TERM: "dumb"
before_script:
- chmod +x gradlew
stages:
- test
- check
java-8:
stage: test
image: openjdk:8
script: ./gradlew clean build
java-11:
stage: test
image: openjdk:11
script: ./gradlew clean build
java-12:
stage: test
image: gradle:jdk12 # latest gradle and jdk12
script: gradle clean build
check:
stage: check
image: openjdk:8
script:
- export JADX_LAST_TAG="$(git describe --abbrev=0 --tags)"
- export JADX_VERSION="${JADX_LAST_TAG:1}-dev-$(git rev-parse --short HEAD)"
- ./gradlew clean sonarqube -Dsonar.host.url="${SONAR_HOST}" -Dsonar.projectKey=jadx -Dsonar.organization="${SONAR_ORG}" -Dsonar.login="${SONAR_TOKEN}" -Dsonar.branch.name=dev || echo "Skip sonar build and upload"
- ./gradlew clean dist
artifacts:
paths:
- build/jadx*.zip
+35
View File
@@ -0,0 +1,35 @@
branch: release
verifyConditions:
- '@semantic-release/github'
prepare:
- path: '@semantic-release/exec'
cmd: "JADX_VERSION=${nextRelease.version} ./gradlew clean dist"
preset: "angular"
generateNotes:
- path: '@semantic-release/release-notes-generator'
writerOpts:
groupBy: "type"
commitGroupsSort:
- "feat"
- "perf"
- "fix"
commitsSort: "header"
publish:
- path: '@semantic-release/exec'
cmd: "JADX_VERSION=${nextRelease.version} BINTRAY_PACKAGE=releases bash scripts/bintray-upload.sh"
- path: '@semantic-release/github'
assets:
- path: 'build/*.zip'
- path: 'build/*.exe'
success:
- path: '@semantic-release/github'
successComment: false
fail:
- path: '@semantic-release/github'
failComment: false
+35 -14
View File
@@ -1,20 +1,41 @@
language: java
jdk:
- oraclejdk7
- openjdk7
- openjdk6
env:
- TERM=dumb
os: linux
dist: trusty
# don't build on tag push
if: tag IS blank
git:
depth: false
before_install:
- chmod +x gradlew
- chmod +x gradlew
script:
- ./gradlew clean build dist
# override install to skip 'gradle assemble'
install: true
after_success:
- ./gradlew jacocoTestReport coveralls
env:
global:
- TERM=dumb
- JADX_LAST_TAG=$(git describe --abbrev=0 --tags)
- JADX_VERSION="${JADX_LAST_TAG:1}-b$TRAVIS_BUILD_NUMBER-$(git rev-parse --short HEAD)"
notifications:
email:
- skylot@gmail.com
jdk:
- openjdk8
- openjdk11
script: ./gradlew clean build
jobs:
include:
- stage: deploy-unstable
jdk: openjdk8
if: branch = master AND repo = env(MAIN_REPO) AND type = push
script: bash scripts/travis-master.sh
- stage: deploy-release
language: node_js
jdk: openjdk8
node_js: 11
if: branch = release AND repo = env(MAIN_REPO) AND type = push
script: bash scripts/travis-release.sh
+76
View File
@@ -0,0 +1,76 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at skylot@gmail.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq
+34
View File
@@ -0,0 +1,34 @@
# Contributing
Please note we have a [code of conduct](CODE_OF_CONDUCT.md), please follow it in all your interactions with the project.
## Open Issue
1. Before proceed please do:
- check [latest unstable build](https://bintray.com/skylot/jadx/unstable/_latestVersion#files) (maybe issue already fixed)
- check [Troubleshooting Q&A](https://github.com/skylot/jadx/wiki/Troubleshooting-Q&A) section on wiki
- search existing issues by exception message
2. Describe error
- provide full name of method or class with error
- provide full java stacktrace
**Note**: no need to copy method fallback code (commented pseudocode)
- attach or provide link to apk file (double check apk version)
**Note**: GitHub don't allow attach files with `.apk` extension, but you can change extension by adding `.zip` at the end :)
## Pull Request Process
1. Please don't submit any code style fixes, dependencies updates or other changes which are not fixing any issues.
1. Before open a PR please discuss the change you wish to make via issue. PR without corresponding issue will be rejected.
1. Use only features and API from Java 8 or below.
1. If possible don't add additional dependencies especially if they are big.
1. Make sure your code is correctly formatted, see description here: [Code Formatting](https://github.com/skylot/jadx/wiki/Code-Formatting).
1. Make sure your changes is passing build: `./gradlew clean build dist`
+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/)
+99 -30
View File
@@ -1,56 +1,125 @@
## JADX
[![Build Status](https://travis-ci.org/skylot/jadx.png?branch=master)](https://travis-ci.org/skylot/jadx)
[![Build Status](https://drone.io/github.com/skylot/jadx/status.png)](https://drone.io/github.com/skylot/jadx/latest)
[![Coverage Status](https://coveralls.io/repos/skylot/jadx/badge.png)](https://coveralls.io/r/skylot/jadx)
[![Coverity Scan Build Status](https://scan.coverity.com/projects/2166/badge.svg)](https://scan.coverity.com/projects/2166)
[![Code Coverage](https://codecov.io/gh/skylot/jadx/branch/master/graph/badge.svg)](https://codecov.io/gh/skylot/jadx)
[![Alerts from lgtm.com](https://img.shields.io/lgtm/alerts/g/skylot/jadx.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/skylot/jadx/alerts/)
[![SonarQube Bugs](https://sonarcloud.io/api/project_badges/measure?project=jadx&metric=bugs)](https://sonarcloud.io/dashboard?id=jadx)
[![License](http://img.shields.io/:license-apache-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0.html)
[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)
**jadx** - Dex to Java decompiler
Command line and GUI tools for produce Java source code from Android Dex and Apk files
**Main features:**
- decompile Dalvik bytecode to java classes from APK, dex, aar and zip files
- decode `AndroidManifest.xml` and other resources from `resources.arsc`
- deobfuscator included
### Downloads
- [unstable](https://drone.io/github.com/skylot/jadx/files)
- from [github](https://github.com/skylot/jadx/releases)
- from [sourceforge](http://sourceforge.net/projects/jadx/files/)
**jadx-gui features:**
- view decompiled code with highlighted syntax
- jump to declaration
- find usage
- full text search
See these features in action here: [jadx-gui features overview](https://github.com/skylot/jadx/wiki/jadx-gui-features-overview)
### Building from source
git clone https://github.com/skylot/jadx.git
cd jadx
./gradlew dist
![jadx-gui screenshot](https://i.imgur.com/h917IBZ.png)
### Download
- latest [unstable build: ![Download](https://api.bintray.com/packages/skylot/jadx/unstable/images/download.svg) ](https://bintray.com/skylot/jadx/unstable/_latestVersion#files)
- release from [github: ![Latest release](https://img.shields.io/github/release/skylot/jadx.svg)](https://github.com/skylot/jadx/releases/latest)
- release from [bintray: ![Download](https://api.bintray.com/packages/skylot/jadx/releases/images/download.svg) ](https://bintray.com/skylot/jadx/releases/_latestVersion#files)
After download unpack zip file go to `bin` directory and run:
- `jadx` - command line version
- `jadx-gui` - UI version
On Windows run `.bat` files with double-click\
**Note:** ensure you have installed Java 8 or later 64-bit version.
For windows you can download it from [adoptopenjdk.net](https://adoptopenjdk.net/releases.html?variant=openjdk11&jvmVariant=hotspot#x64_win) (select "Install JRE").
### Install
1. Arch linux
```bash
sudo pacman -S jadx
```
2. macOS
```bash
brew install jadx
```
### Build from source
JDK 8 or higher must be installed:
```
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`
and also packed to `build/jadx-<version>.zip`
### Run
Run **jadx** on itself:
cd build/jadx/
bin/jadx -d out lib/jadx-core-*.jar
#or
bin/jadx-gui lib/jadx-core-*.jar
### Usage
```
jadx[-gui] [options] <input file> (.dex, .apk or .jar)
jadx[-gui] [options] <input file> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc)
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
-ds, --output-dir-src - output directory for sources
-dr, --output-dir-res - output directory for resources
-r, --no-res - do not decode resources
-s, --no-src - do not decompile source code
--single-class - decompile a single class
--output-format - can be 'java' or 'json', default: java
-e, --export-gradle - save as android gradle project
-j, --threads-count - processing threads count, default: 4
--show-bad-code - show inconsistent code (incorrectly decompiled)
--no-imports - disable use of imports, always write entire package name
--no-debug-info - disable debug info
--no-inline-anonymous - disable anonymous classes inline
--no-replace-consts - don't replace constant value with matching constant field
--escape-unicode - escape non latin characters in strings (with \u)
--respect-bytecode-access-modifiers - don't change original access modifiers
--deobf - activate deobfuscation
--deobf-min - min length of name, renamed if shorter, default: 3
--deobf-max - max length of name, renamed if longer, default: 64
--deobf-rewrite-cfg - force to save deobfuscation map
--deobf-use-sourcename - use source file name as class name alias
--rename-flags - what to rename, comma-separated, 'case' for system case sensitivity, 'valid' for java identifiers, 'printable' characters, 'none' or 'all' (default)
--fs-case-sensitive - treat filesystem as case sensitive, false by default
--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 (set --log-level to DEBUG)
-q, --quiet - turn off output (set --log-level to QUIET)
--log-level - set log level, values: QUIET, PROGRESS, ERROR, WARN, INFO, DEBUG, default: PROGRESS
--version - print jadx version
-h, --help - print this help
Example:
jadx -d out classes.dex
jadx --rename-flags "none" classes.dex
jadx --rename-flags "valid,printable" classes.dex
jadx --log-level error app.apk
```
These options also worked on jadx-gui running from command line and override options from preferences dialog
### Troubleshooting
Please check wiki page [Troubleshooting Q&A](https://github.com/skylot/jadx/wiki/Troubleshooting-Q&A)
### Contributing
To support this project you can:
- Post thoughts about new features/optimizations that important to you
- Submit decompilation issues, please read before proceed: [Open issue](CONTRIBUTING.md#Open-Issue)
- Open pull request, please follow these rules: [Pull Request Process](CONTRIBUTING.md#Pull-Request-Process)
### Related projects:
- [PyJadx](https://github.com/romainthomas/pyjadx) - python binding for jadx by [@romainthomas](https://github.com/romainthomas)
---------------------------------------
*Licensed under the Apache 2.0 License*
*Copyright 2014 by Skylot*
*Copyright 2019 by Skylot*
+133 -64
View File
@@ -1,92 +1,161 @@
ext.jadxVersion = file('version').readLines().get(0)
plugins {
id 'org.sonarqube' version '2.8'
id 'com.github.ben-manes.versions' version '0.27.0'
id "com.diffplug.gradle.spotless" version "3.26.0"
}
ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev"
version = jadxVersion
println("jadx version: ${jadxVersion}")
apply plugin: 'sonar-runner'
allprojects {
apply plugin: 'java'
apply plugin: 'jacoco'
apply plugin: 'checkstyle'
subprojects {
apply plugin: 'java'
apply plugin: 'groovy'
apply plugin: 'jacoco'
apply plugin: 'coveralls'
version = jadxVersion
version = jadxVersion
tasks.withType(JavaCompile) {
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
compileJava {
options.encoding = "UTF-8"
}
if (!"$it".contains(':jadx-samples:')) {
options.compilerArgs << '-Xlint' << '-Xlint:unchecked' << '-Xlint:deprecation'
}
}
jar {
version = jadxVersion
manifest {
mainAttributes('jadx-version': jadxVersion)
}
}
jar {
version = jadxVersion
manifest {
mainAttributes('jadx-version': jadxVersion)
}
}
dependencies {
compile 'org.slf4j:slf4j-api:1.7.29'
dependencies {
compile 'org.slf4j:slf4j-api:1.7.7'
testCompile 'ch.qos.logback:logback-classic:1.2.3'
testCompile 'org.hamcrest:hamcrest-library:2.2'
testCompile 'org.mockito:mockito-core:3.1.0'
testCompile 'org.assertj:assertj-core:3.14.0'
testCompile 'ch.qos.logback:logback-classic:1.1.2'
testCompile 'junit:junit:4.11'
testCompile 'org.mockito:mockito-core:1.9.5'
testCompile 'org.spockframework:spock-core:0.7-groovy-2.0'
testCompile 'cglib:cglib-nodep:3.1'
}
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.5.2'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.5.2'
repositories {
mavenCentral()
}
testCompile 'org.eclipse.jdt.core.compiler:ecj:4.6.1'
}
jacocoTestReport {
reports {
xml.enabled = true // coveralls plugin depends on xml format report
html.enabled = true
}
}
test {
useJUnitPlatform()
}
repositories {
mavenLocal()
mavenCentral()
jcenter()
google()
}
jacoco {
toolVersion = "0.8.2"
}
jacocoTestReport {
reports {
xml.enabled = true
html.enabled = true
}
}
checkstyleMain {
// exclude all sources in samples module
exclude '**/samples/**'
}
}
buildscript {
repositories {
mavenCentral()
}
dependencies {
// setup coveralls (http://coveralls.io/) see http://github.com/kt3k/coveralls-gradle-plugin
classpath 'org.kt3k.gradle.plugin:coveralls-gradle-plugin:0.4.0'
}
sonarqube {
properties {
property 'sonar.exclusions', '**/jadx/samples/**/*,**/test-app/**/*'
property 'sonar.coverage.exclusions', '**/jadx/gui/**/*'
}
}
task copyArtifacts(type: Sync, dependsOn: ['jadx-cli:installApp', 'jadx-gui:installApp']) {
destinationDir file("$buildDir/jadx")
['jadx-cli', 'jadx-gui'].each {
from tasks.getByPath(":${it}:installApp").destinationDir
}
spotless {
java {
target fileTree(rootDir).matching {
include 'jadx-cli/src/**/java/**/*.java'
include 'jadx-core/src/**/java/**/*.java'
include 'jadx-gui/src/**/java/**/*.java'
}
importOrderFile 'config/code-formatter/eclipse.importorder'
eclipse().configFile 'config/code-formatter/eclipse.xml'
removeUnusedImports()
lineEndings(com.diffplug.spotless.LineEnding.UNIX)
encoding("UTF-8")
trimTrailingWhitespace()
endWithNewline()
}
format 'misc', {
target '**/*.gradle', '**/*.md', '**/*.xml', '**/.gitignore', '**/.properties'
targetExclude ".gradle/**", ".idea/**"
lineEndings(com.diffplug.spotless.LineEnding.UNIX)
encoding("UTF-8")
trimTrailingWhitespace()
endWithNewline()
}
}
dependencyUpdates.resolutionStrategy = {
componentSelection { rules ->
rules.all { ComponentSelection selection ->
boolean rejected = ['alpha', 'beta', 'rc', 'cr', 'm', 'atlassian'].any { qualifier ->
selection.candidate.version ==~ /(?i).*[.-]${qualifier}[.\d-]*/
}
if (rejected) {
selection.reject('Release candidate')
}
}
}
}
task copyArtifacts(type: Sync, dependsOn: ['jadx-cli:installDist', 'jadx-gui:installDist']) {
destinationDir file("$buildDir/jadx")
['jadx-cli', 'jadx-gui'].each {
from tasks.getByPath(":${it}:installDist").destinationDir
}
}
task pack(type: Zip, dependsOn: copyArtifacts) {
destinationDir buildDir
archiveName "jadx-${jadxVersion}.zip"
from copyArtifacts.destinationDir
destinationDir buildDir
archiveName "jadx-${jadxVersion}.zip"
from copyArtifacts.destinationDir
}
task dist(dependsOn: pack) {
description = 'Build jadx distribution zip'
task copyExe(type: Copy, dependsOn: 'jadx-gui:createExe') {
group 'jadx'
description = 'Copy exe to build dir'
destinationDir buildDir
from tasks.getByPath('jadx-gui:createExe').outputs
include '*.exe'
}
task dist(dependsOn: [pack, copyExe]) {
group 'jadx'
description = 'Build jadx distribution zip'
}
task samples(dependsOn: 'jadx-samples:samples') {
group 'jadx'
}
task build(dependsOn: [dist, samples]) {
task cleanBuildDir(type: Delete) {
group 'jadx'
delete buildDir
}
task clean(type: Delete) {
delete buildDir
}
test.dependsOn(samples)
task wrapper(type: Wrapper) {
gradleVersion = '1.12'
}
clean.dependsOn(cleanBuildDir)
+126
View File
@@ -0,0 +1,126 @@
<?xml version="1.0" ?>
<!DOCTYPE module PUBLIC
"-//Puppy Crawl//DTD Check Configuration 1.2//EN"
"http://www.puppycrawl.com/dtds/configuration_1_2.dtd">
<module name="Checker">
<property name="fileExtensions" value="java, properties, xml"/>
<property name="charset" value="UTF-8"/>
<module name="TreeWalker">
<property name="tabWidth" value="4"/>
<module name="RegexpSinglelineJava">
<property name="format" value="^\t* "/>
<property name="message" value="Indent must use tab characters"/>
<property name="ignoreComments" value="true"/>
</module>
<module name="RegexpSinglelineJava">
<property name="format" value="^(?!\s+\* $).*?\s+$"/>
<property name="message" value="Line has trailing spaces."/>
</module>
<module name="AvoidEscapedUnicodeCharacters">
<property name="allowEscapesForControlCharacters" value="true"/>
<property name="allowByTailComment" value="true"/>
<property name="allowNonPrintableEscapes" value="true"/>
</module>
<module name="EmptyLineSeparator">
<property name="allowNoEmptyLineBetweenFields" value="true"/>
<property name="allowMultipleEmptyLines" value="false"/>
</module>
<!-- whitespaces -->
<module name="SingleSpaceSeparator"/>
<module name="GenericWhitespace"/>
<module name="MethodParamPad"/>
<module name="NoWhitespaceBefore"/>
<module name="OperatorWrap"/>
<module name="ParenPad"/>
<module name="TypecastParenPad"/>
<module name="WhitespaceAfter"/>
<module name="WhitespaceAround">
<property name="allowEmptyMethods" value="true"/>
</module>
<!-- <module name="EmptyForIteratorPad"/> -->
<!-- <module name="NoWhitespaceAfter"/>-->
<module name="NoLineWrap"/>
<module name="IllegalImport"/> <!-- defaults to sun.* packages -->
<module name="RedundantImport"/>
<module name="UnusedImports"/>
<!-- <module name="AvoidStarImport"/> -->
<module name="NeedBraces"/>
<module name="LeftCurly"/>
<module name="RightCurly"/>
<module name="EmptyCatchBlock">
<property name="exceptionVariableName" value="expected|ignore"/>
</module>
<!-- naming -->
<module name="PackageName"/>
<module name="TypeName"/>
<module name="InterfaceTypeParameterName"/>
<module name="ClassTypeParameterName"/>
<module name="StaticVariableName"/>
<module name="ConstantName"/>
<module name="MemberName"/>
<module name="MethodName"/>
<module name="MethodTypeParameterName"/>
<module name="ParameterName"/>
<module name="LambdaParameterName"/>
<module name="LocalVariableName"/>
<module name="LocalFinalVariableName"/>
<module name="CatchParameterName"/>
<!-- <module name="HiddenField"/> -->
<!-- annotations -->
<module name="AnnotationLocation"/>
<module name="AnnotationUseStyle">
<property name="elementStyle" value="compact"/>
</module>
<module name="MissingOverride"/>
<!-- <module name="MissingDeprecated"/> -->
<module name="ModifierOrder"/>
<!-- <module name="RedundantModifier"/> -->
<!-- <module name="ParameterNumber"/> -->
<module name="EmptyStatement"/>
<module name="DefaultComesLast"/>
<module name="EqualsHashCode"/>
<module name="FallThrough"/>
<!-- <module name="IllegalCatch"/> -->
<module name="IllegalThrows"/>
<module name="IllegalType"/>
<module name="InnerAssignment"/>
<module name="MultipleVariableDeclarations"/>
<module name="NoClone"/>
<module name="NoFinalizer"/>
<module name="OneStatementPerLine"/>
<module name="PackageDeclaration"/>
<module name="StringLiteralEquality"/>
<!-- design -->
<module name="OneTopLevelClass"/>
<module name="MutableException"/>
<module name="InterfaceIsType"/>
<module name="ThrowsCount">
<property name="max" value="2"/>
</module>
<!-- misc -->
<module name="ArrayTypeStyle"/>
<module name="OuterTypeFilename"/>
<!-- sizes -->
<module name="OuterTypeNumber"/>
<module name="SuppressWarningsHolder"/>
</module>
<module name="NewlineAtEndOfFile"/>
<module name="SuppressWarningsFilter"/>
</module>
@@ -0,0 +1,8 @@
#Import Order
0=java
1=javax
2=org
3=com
4=
5=jadx
6=\#
+348
View File
@@ -0,0 +1,348 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<profiles version="16">
<profile kind="CodeFormatterProfile" name="jadx eclipse" version="16">
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_ellipsis" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_for_statment" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_logical_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_method_invocation" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_imports" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_switch_statement" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_javadoc_comments" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.indentation.size" value="4"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_enum_constant_declaration" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.align_with_spaces" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.disabling_tag" value="@formatter:off"/>
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation" value="2"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_enum_constants" value="48"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_imports" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_package" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_if_while_statement" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.indent_root_tags" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.enabling_tag" value="@formatter:on"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.count_line_length_from_starting_position" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_multiplicative_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameterized_type_references" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_logical_operator" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_annotation_declaration_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_enum_constant" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_multiplicative_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_block" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.align_tags_descriptions_grouped" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.comment.line_length" value="100"/>
<setting id="org.eclipse.jdt.core.formatter.use_on_off_tags" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.keep_method_body_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_loop_body_block_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_method_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.keep_enum_constant_declaration_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.align_variable_declarations_on_columns" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_type_declaration_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_catch_clause" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_additive_operator" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_relational_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_multiplicative_operator" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.keep_anonymous_type_declaration_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_shift_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_lambda_body" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.compact_else_if" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_bitwise_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_type_parameters" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_loops" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_simple_for_body_on_same_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_relational_operator" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_unary_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation" value="separate_lines_if_wrapped"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_ellipsis" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_additive_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_string_concatenation" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_line_comments" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.align_type_members_on_columns" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_assignment" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_module_statements" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.align_tags_names_descriptions" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.keep_if_then_body_block_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression" value="48"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.align_assignment_statements_on_columns" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block_in_case" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression_chain" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_additive_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_method_declaration" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.join_wrapped_lines" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_conditional_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_shift_operator" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines" value="2147483647"/>
<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_bitwise_operator" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_resources_in_try" value="80"/>
<setting id="org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_try_clause" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.keep_code_block_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.tabulation.size" value="4"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_bitwise_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_source_code" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_field" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer" value="2"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_method" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_assignment_operator" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_switch" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_html" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_method_delcaration" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_if" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.keep_lambda_body_block_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.indent_empty_lines" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_type_arguments" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_unary_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_label" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_member_type" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_logical_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_bitwise_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_relational_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_block_comments" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.indent_tag_description" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_string_concatenation" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_body" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_multiple_fields" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_simple_while_body_on_same_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_array_initializer" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_logical_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_shift_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_lambda_declaration" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_shift_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_simple_do_while_body_on_same_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_enum_declaration_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_constant" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_type_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_multiplicative_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_package" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_for_loop_header" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_additive_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.keep_simple_getter_setter_on_one_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_string_concatenation" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.join_lines_in_comments" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.indent_parameter_description" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.tabulation.char" value="tab"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_relational_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_string_concatenation" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_import_groups" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.lineSplit" value="140"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch" value="insert"/>
</profile>
</profiles>
+1
View File
@@ -0,0 +1 @@
org.gradle.daemon=false
Binary file not shown.
+1 -1
View File
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=http\://services.gradle.org/distributions/gradle-1.12-bin.zip
Vendored
+83 -64
View File
@@ -1,4 +1,20 @@
#!/usr/bin/env bash
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# 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
#
# https://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.
#
##############################################################################
##
@@ -6,47 +22,6 @@
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# For Cygwin, ensure paths are in UNIX format before anything is touched.
if $cygwin ; then
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
fi
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
@@ -61,9 +36,49 @@ while [ -h "$PRG" ] ; do
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >&-
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >&-
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
@@ -90,7 +105,7 @@ location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
@@ -110,10 +125,11 @@ if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
@@ -138,27 +154,30 @@ if $cygwin ; then
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
i=`expr $i + 1`
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
APP_ARGS=`save "$@"`
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"
Vendored
+20 -10
View File
@@ -1,3 +1,19 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@@ -8,14 +24,14 @@
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
@@ -46,10 +62,9 @@ echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
@@ -60,11 +75,6 @@ set _SKIP=2
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
+18 -23
View File
@@ -1,29 +1,24 @@
apply plugin: 'application'
mainClassName = 'jadx.cli.JadxCLI'
applicationName = 'jadx'
dependencies {
compile(project(':jadx-core'))
compile 'com.beust:jcommander:1.35'
compile 'ch.qos.logback:logback-classic:1.1.2'
plugins {
id 'application'
}
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)
}
dependencies {
compile(project(':jadx-core'))
compile 'com.beust:jcommander:1.78'
compile 'ch.qos.logback:logback-classic:1.2.3'
}
application {
applicationName = 'jadx'
mainClassName = 'jadx.cli.JadxCLI'
applicationDefaultJvmArgs = ['-Xms128M', '-Xmx4g', '-XX:+UseG1GC']
}
applicationDistribution.with {
into('') {
from '../.'
include 'README.md'
include 'NOTICE'
}
into('') {
from '../.'
include 'README.md'
include 'NOTICE'
include 'LICENSE'
}
}
@@ -0,0 +1,137 @@
package jadx.cli;
import java.io.PrintStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.jetbrains.annotations.Nullable;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.ParameterDescription;
import com.beust.jcommander.ParameterException;
import com.beust.jcommander.Parameterized;
import jadx.api.JadxDecompiler;
public class JCommanderWrapper<T> {
private final JCommander jc;
public JCommanderWrapper(T obj) {
this.jc = JCommander.newBuilder().addObject(obj).build();
}
public boolean parse(String[] args) {
try {
jc.parse(args);
return true;
} catch (ParameterException e) {
System.err.println("Arguments parse error: " + e.getMessage());
printUsage();
return false;
}
}
public void overrideProvided(T obj) {
List<ParameterDescription> fieldsParams = jc.getParameters();
List<ParameterDescription> parameters = new ArrayList<>(1 + fieldsParams.size());
parameters.add(jc.getMainParameterValue());
parameters.addAll(fieldsParams);
for (ParameterDescription parameter : parameters) {
if (parameter.isAssigned()) {
// copy assigned field value to obj
Parameterized parameterized = parameter.getParameterized();
Object val = parameterized.get(parameter.getObject());
parameterized.set(obj, val);
}
}
}
public void printUsage() {
// print usage in not sorted fields order (by default its sorted by description)
PrintStream out = System.out;
out.println();
out.println("jadx - dex to java decompiler, version: " + JadxDecompiler.getVersion());
out.println();
out.println("usage: jadx [options] " + jc.getMainParameterDescription());
out.println("options:");
List<ParameterDescription> params = jc.getParameters();
Map<String, ParameterDescription> paramsMap = new LinkedHashMap<>(params.size());
int maxNamesLen = 0;
for (ParameterDescription p : params) {
paramsMap.put(p.getParameterized().getName(), p);
int len = p.getNames().length();
if (len > maxNamesLen) {
maxNamesLen = len;
}
}
JadxCLIArgs args = (JadxCLIArgs) jc.getObjects().get(0);
for (Field f : getFields(args.getClass())) {
String name = f.getName();
ParameterDescription p = paramsMap.get(name);
if (p == null) {
continue;
}
StringBuilder opt = new StringBuilder();
opt.append(" ").append(p.getNames());
addSpaces(opt, maxNamesLen - opt.length() + 3);
opt.append("- ").append(p.getDescription());
String defaultValue = getDefaultValue(args, f, opt);
if (defaultValue != null) {
opt.append(", default: ").append(defaultValue);
}
out.println(opt);
}
out.println("Example:");
out.println(" jadx -d out classes.dex");
}
/**
* Get all declared fields of the specified class and all super classes
*
* @param clazz
* @return
*/
private List<Field> getFields(Class<?> clazz) {
List<Field> fieldList = new LinkedList<>();
while (clazz != null) {
fieldList.addAll(Arrays.asList(clazz.getDeclaredFields()));
clazz = clazz.getSuperclass();
}
return fieldList;
}
@Nullable
private String getDefaultValue(JadxCLIArgs args, Field f, StringBuilder opt) {
try {
Class<?> fieldType = f.getType();
if (fieldType == int.class) {
return Integer.toString(f.getInt(args));
}
if (fieldType == String.class) {
return (String) f.get(args);
}
if (Enum.class.isAssignableFrom(fieldType)) {
Enum<?> val = (Enum<?>) f.get(args);
if (val != null) {
return val.name();
}
}
} catch (Exception e) {
// ignore
}
return null;
}
private static void addSpaces(StringBuilder str, int count) {
for (int i = 0; i < count; i++) {
str.append(' ');
}
}
}
+29 -53
View File
@@ -1,73 +1,49 @@
package jadx.cli;
import jadx.api.JadxDecompiler;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.exceptions.JadxException;
import java.io.File;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs;
import jadx.api.JadxDecompiler;
import jadx.api.impl.NoOpCodeCache;
import jadx.core.utils.exceptions.JadxArgsValidateException;
public class JadxCLI {
private static final Logger LOG = LoggerFactory.getLogger(JadxCLI.class);
public static void main(String[] args) {
int result = 0;
try {
JadxCLIArgs jadxArgs = new JadxCLIArgs();
if (processArgs(jadxArgs, args)) {
processAndSave(jadxArgs);
if (jadxArgs.processArgs(args)) {
result = processAndSave(jadxArgs);
}
} catch (JadxException e) {
LOG.error(e.getMessage());
System.exit(1);
} catch (Exception e) {
LOG.error("jadx error: {}", e.getMessage(), e);
result = 1;
} finally {
System.exit(result);
}
}
static void processAndSave(JadxCLIArgs jadxArgs) throws JadxException {
static int processAndSave(JadxCLIArgs inputArgs) {
JadxArgs args = inputArgs.toJadxArgs();
args.setCodeCache(new NoOpCodeCache());
JadxDecompiler jadx = new JadxDecompiler(args);
try {
JadxDecompiler jadx = new JadxDecompiler(jadxArgs);
jadx.loadFiles(jadxArgs.getInput());
jadx.setOutputDir(jadxArgs.getOutDir());
jadx.save();
} catch (Throwable e) {
throw new JadxException("jadx error: " + e.getMessage(), e);
jadx.load();
} catch (JadxArgsValidateException e) {
LOG.error("Incorrect arguments: {}", e.getMessage());
return 1;
}
if (ErrorsCounter.getErrorCount() != 0) {
ErrorsCounter.printReport();
throw new JadxException("finished with errors");
jadx.save();
int errorsCount = jadx.getErrorsCount();
if (errorsCount != 0) {
jadx.printErrorsReport();
LOG.error("finished with errors, count: {}", errorsCount);
} else {
LOG.info("done");
}
LOG.info("done");
}
static boolean processArgs(JadxCLIArgs jadxArgs, String[] args) throws JadxException {
if (!jadxArgs.processArgs(args)) {
return false;
}
if (jadxArgs.getInput().isEmpty()) {
LOG.error("Please specify input file");
jadxArgs.printUsage();
return false;
}
File outputDir = jadxArgs.getOutDir();
if (outputDir == null) {
String outDirName;
File file = jadxArgs.getInput().get(0);
String name = file.getName();
int pos = name.lastIndexOf('.');
if (pos != -1) {
outDirName = name.substring(0, pos);
} else {
outDirName = name + "-jadx-out";
}
LOG.info("output directory: " + outDirName);
outputDir = new File(outDirName);
jadxArgs.setOutputDir(outputDir);
}
if (outputDir.exists() && !outputDir.isDirectory()) {
throw new JadxException("Output directory exists as file " + outputDir);
}
return true;
return 0;
}
}
+290 -125
View File
@@ -1,187 +1,352 @@
package jadx.cli;
import jadx.api.IJadxArgs;
import jadx.core.Consts;
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.Arrays;
import java.util.EnumSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.IStringConverter;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.ParameterDescription;
import com.beust.jcommander.ParameterException;
public final class JadxCLIArgs implements IJadxArgs {
import jadx.api.JadxArgs;
import jadx.api.JadxArgs.RenameEnum;
import jadx.api.JadxDecompiler;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.files.FileUtils;
@Parameter(description = "<input file> (.dex, .apk or .jar)")
protected List<String> files;
public class JadxCLIArgs {
@Parameter(names = {"-d", "--output-dir"}, description = "output directory")
protected String outDirName;
@Parameter(description = "<input file> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc)")
protected List<String> files = new ArrayList<>(1);
@Parameter(names = {"-j", "--threads-count"}, description = "processing threads count")
protected int threadsCount = Runtime.getRuntime().availableProcessors();
@Parameter(names = { "-d", "--output-dir" }, description = "output directory")
protected String outDir;
@Parameter(names = {"-f", "--fallback"}, description = "make simple dump (using goto instead of 'if', 'for', etc)", help = true)
protected boolean fallbackMode = false;
@Parameter(names = { "-ds", "--output-dir-src" }, description = "output directory for sources")
protected String outDirSrc;
@Parameter(names = {"--cfg"}, description = "save methods control flow graph to dot file")
@Parameter(names = { "-dr", "--output-dir-res" }, description = "output directory for resources")
protected String outDirRes;
@Parameter(names = { "-r", "--no-res" }, description = "do not decode resources")
protected boolean skipResources = false;
@Parameter(names = { "-s", "--no-src" }, description = "do not decompile source code")
protected boolean skipSources = false;
@Parameter(names = { "--single-class" }, description = "decompile a single class")
protected String singleClass = null;
@Parameter(names = { "--output-format" }, description = "can be 'java' or 'json'")
protected String outputFormat = "java";
@Parameter(names = { "-e", "--export-gradle" }, description = "save as android gradle project")
protected boolean exportAsGradleProject = false;
@Parameter(names = { "-j", "--threads-count" }, description = "processing threads count")
protected int threadsCount = JadxArgs.DEFAULT_THREADS_COUNT;
@Parameter(names = { "--show-bad-code" }, description = "show inconsistent code (incorrectly decompiled)")
protected boolean showInconsistentCode = false;
@Parameter(names = { "--no-imports" }, description = "disable use of imports, always write entire package name")
protected boolean useImports = true;
@Parameter(names = { "--no-debug-info" }, description = "disable debug info")
protected boolean debugInfo = true;
@Parameter(names = { "--no-inline-anonymous" }, description = "disable anonymous classes inline")
protected boolean inlineAnonymousClasses = true;
@Parameter(names = "--no-replace-consts", description = "don't replace constant value with matching constant field")
protected boolean replaceConsts = true;
@Parameter(names = { "--escape-unicode" }, description = "escape non latin characters in strings (with \\u)")
protected boolean escapeUnicode = false;
@Parameter(names = { "--respect-bytecode-access-modifiers" }, description = "don't change original access modifiers")
protected boolean respectBytecodeAccessModifiers = false;
@Parameter(names = { "--deobf" }, description = "activate deobfuscation")
protected boolean deobfuscationOn = false;
@Parameter(names = { "--deobf-min" }, description = "min length of name, renamed if shorter")
protected int deobfuscationMinLength = 3;
@Parameter(names = { "--deobf-max" }, description = "max length of name, renamed if longer")
protected int deobfuscationMaxLength = 64;
@Parameter(names = { "--deobf-rewrite-cfg" }, description = "force to save deobfuscation map")
protected boolean deobfuscationForceSave = false;
@Parameter(names = { "--deobf-use-sourcename" }, description = "use source file name as class name alias")
protected boolean deobfuscationUseSourceNameAsAlias = false;
@Parameter(
names = { "--rename-flags" },
description = "what to rename, comma-separated,"
+ " 'case' for system case sensitivity,"
+ " 'valid' for java identifiers,"
+ " 'printable' characters,"
+ " 'none' or 'all' (default)",
converter = RenameConverter.class
)
protected Set<RenameEnum> renameFlags = EnumSet.allOf(RenameEnum.class);
@Parameter(names = { "--fs-case-sensitive" }, description = "treat filesystem as case sensitive, false by default")
protected boolean fsCaseSensitive = false;
@Parameter(names = { "--cfg" }, description = "save methods control flow graph to dot file")
protected boolean cfgOutput = false;
@Parameter(names = {"--raw-cfg"}, description = "save methods control flow graph (use raw instructions)")
@Parameter(names = { "--raw-cfg" }, description = "save methods control flow graph (use raw instructions)")
protected boolean rawCfgOutput = false;
@Parameter(names = {"-v", "--verbose"}, description = "verbose output")
@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 (set --log-level to DEBUG)")
protected boolean verbose = false;
@Parameter(names = {"-h", "--help"}, description = "print this help", help = true)
@Parameter(names = { "-q", "--quiet" }, description = "turn off output (set --log-level to QUIET)")
protected boolean quiet = false;
@Parameter(
names = { "--log-level" },
description = "set log level, values: QUIET, PROGRESS, ERROR, WARN, INFO, DEBUG",
converter = LogHelper.LogLevelConverter.class
)
protected LogHelper.LogLevelEnum logLevel = LogHelper.LogLevelEnum.PROGRESS;
@Parameter(names = { "--version" }, description = "print jadx version")
protected boolean printVersion = false;
@Parameter(names = { "-h", "--help" }, description = "print this help", help = true)
protected boolean printHelp = false;
private final List<File> input = new ArrayList<File>(1);
private File outputDir;
public boolean processArgs(String[] args) {
return parse(args) && process();
JCommanderWrapper<JadxCLIArgs> jcw = new JCommanderWrapper<>(this);
return jcw.parse(args) && process(jcw);
}
private boolean parse(String[] args) {
try {
new JCommander(this, args);
return true;
} catch (ParameterException e) {
System.err.println("Arguments parse error: " + e.getMessage());
printUsage();
/**
* Set values only for options provided in cmd.
* Used to merge saved options and options passed in command line.
*/
public boolean overrideProvided(String[] args) {
JCommanderWrapper<JadxCLIArgs> jcw = new JCommanderWrapper<>(newInstance());
if (!jcw.parse(args)) {
return false;
}
jcw.overrideProvided(this);
return process(jcw);
}
private boolean process() {
if (isPrintHelp()) {
printUsage();
protected JadxCLIArgs newInstance() {
return new JadxCLIArgs();
}
private boolean process(JCommanderWrapper<JadxCLIArgs> jcw) {
if (printHelp) {
jcw.printUsage();
return false;
}
if (printVersion) {
System.out.println(JadxDecompiler.getVersion());
return false;
}
try {
if (threadsCount <= 0) {
throw new JadxException("Threads count must be positive");
}
if (files != null) {
for (String fileName : files) {
File file = new File(fileName);
if (file.exists()) {
input.add(file);
} else {
throw new JadxException("File not found: " + file);
}
}
}
if (input.size() > 1) {
throw new JadxException("Only one input file is supported");
}
if (outDirName != null) {
outputDir = new File(outDirName);
}
if (isVerbose()) {
ch.qos.logback.classic.Logger rootLogger =
(ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
rootLogger.setLevel(ch.qos.logback.classic.Level.DEBUG);
throw new JadxException("Threads count must be positive, got: " + threadsCount);
}
LogHelper.setLogLevelFromArgs(this);
} catch (JadxException e) {
System.err.println("ERROR: " + e.getMessage());
printUsage();
jcw.printUsage();
return false;
}
return true;
}
public void printUsage() {
JCommander jc = new JCommander(this);
// print usage in not sorted fields order (by default its sorted by description)
PrintStream out = System.out;
out.println();
out.println("jadx - dex to java decompiler, version: " + Consts.JADX_VERSION);
out.println();
out.println("usage: jadx [options] " + jc.getMainParameterDescription());
out.println("options:");
List<ParameterDescription> params = jc.getParameters();
int maxNamesLen = 0;
for (ParameterDescription p : params) {
int len = p.getNames().length();
if (len > maxNamesLen) {
maxNamesLen = len;
}
public JadxArgs toJadxArgs() {
JadxArgs args = new JadxArgs();
args.setInputFiles(files.stream().map(FileUtils::toFile).collect(Collectors.toList()));
args.setOutDir(FileUtils.toFile(outDir));
args.setOutDirSrc(FileUtils.toFile(outDirSrc));
args.setOutDirRes(FileUtils.toFile(outDirRes));
args.setOutputFormat(JadxArgs.OutputFormatEnum.valueOf(outputFormat.toUpperCase()));
args.setThreadsCount(threadsCount);
args.setSkipSources(skipSources);
if (singleClass != null) {
args.setClassFilter(className -> singleClass.equals(className));
}
Field[] fields = this.getClass().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;
}
}
}
out.println("Example:");
out.println(" jadx -d out classes.dex");
args.setSkipResources(skipResources);
args.setFallbackMode(fallbackMode);
args.setShowInconsistentCode(showInconsistentCode);
args.setCfgOutput(cfgOutput);
args.setRawCFGOutput(rawCfgOutput);
args.setReplaceConsts(replaceConsts);
args.setDeobfuscationOn(deobfuscationOn);
args.setDeobfuscationForceSave(deobfuscationForceSave);
args.setDeobfuscationMinLength(deobfuscationMinLength);
args.setDeobfuscationMaxLength(deobfuscationMaxLength);
args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias);
args.setEscapeUnicode(escapeUnicode);
args.setRespectBytecodeAccModifiers(respectBytecodeAccessModifiers);
args.setExportAsGradleProject(exportAsGradleProject);
args.setUseImports(useImports);
args.setDebugInfo(debugInfo);
args.setInlineAnonymousClasses(inlineAnonymousClasses);
args.setRenameCaseSensitive(isRenameCaseSensitive());
args.setRenameValid(isRenameValid());
args.setRenamePrintable(isRenamePrintable());
args.setFsCaseSensitive(fsCaseSensitive);
return args;
}
private static void addSpaces(StringBuilder str, int count) {
for (int i = 0; i < count; i++) {
str.append(' ');
}
public List<String> getFiles() {
return files;
}
public List<File> getInput() {
return input;
public String getOutDir() {
return outDir;
}
public File getOutDir() {
return outputDir;
public String getOutDirSrc() {
return outDirSrc;
}
public void setOutputDir(File outputDir) {
this.outputDir = outputDir;
public String getOutDirRes() {
return outDirRes;
}
public boolean isPrintHelp() {
return printHelp;
public boolean isSkipResources() {
return skipResources;
}
public boolean isSkipSources() {
return skipSources;
}
@Override
public int getThreadsCount() {
return threadsCount;
}
@Override
public boolean isCFGOutput() {
return cfgOutput;
}
@Override
public boolean isRawCFGOutput() {
return rawCfgOutput;
}
@Override
public boolean isFallbackMode() {
return fallbackMode;
}
@Override
public boolean isVerbose() {
return verbose;
public boolean isShowInconsistentCode() {
return showInconsistentCode;
}
public boolean isUseImports() {
return useImports;
}
public boolean isDebugInfo() {
return debugInfo;
}
public boolean isInlineAnonymousClasses() {
return inlineAnonymousClasses;
}
public boolean isDeobfuscationOn() {
return deobfuscationOn;
}
public int getDeobfuscationMinLength() {
return deobfuscationMinLength;
}
public int getDeobfuscationMaxLength() {
return deobfuscationMaxLength;
}
public boolean isDeobfuscationForceSave() {
return deobfuscationForceSave;
}
public boolean isDeobfuscationUseSourceNameAsAlias() {
return deobfuscationUseSourceNameAsAlias;
}
public boolean isEscapeUnicode() {
return escapeUnicode;
}
public boolean isCfgOutput() {
return cfgOutput;
}
public boolean isRawCfgOutput() {
return rawCfgOutput;
}
public boolean isReplaceConsts() {
return replaceConsts;
}
public boolean isRespectBytecodeAccessModifiers() {
return respectBytecodeAccessModifiers;
}
public boolean isExportAsGradleProject() {
return exportAsGradleProject;
}
public boolean isRenameCaseSensitive() {
return renameFlags.contains(RenameEnum.CASE);
}
public boolean isRenameValid() {
return renameFlags.contains(RenameEnum.VALID);
}
public boolean isRenamePrintable() {
return renameFlags.contains(RenameEnum.PRINTABLE);
}
public boolean isFsCaseSensitive() {
return fsCaseSensitive;
}
static class RenameConverter implements IStringConverter<Set<RenameEnum>> {
private final String paramName;
RenameConverter(String paramName) {
this.paramName = paramName;
}
@Override
public Set<RenameEnum> convert(String value) {
if (value.equalsIgnoreCase("NONE")) {
return EnumSet.noneOf(RenameEnum.class);
}
if (value.equalsIgnoreCase("ALL")) {
return EnumSet.allOf(RenameEnum.class);
}
Set<RenameEnum> set = EnumSet.noneOf(RenameEnum.class);
for (String s : value.split(",")) {
try {
set.add(RenameEnum.valueOf(s.toUpperCase(Locale.ROOT)));
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException(
'\'' + s + "' is unknown for parameter " + paramName
+ ", possible values are " + enumValuesString(RenameEnum.values()));
}
}
return set;
}
}
public static String enumValuesString(Enum<?>[] values) {
return Arrays.stream(values)
.map(v -> '\'' + v.name().toLowerCase(Locale.ROOT) + '\'')
.collect(Collectors.joining(", "));
}
}
@@ -0,0 +1,93 @@
package jadx.cli;
import org.slf4j.LoggerFactory;
import com.beust.jcommander.IStringConverter;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import jadx.api.JadxDecompiler;
public class LogHelper {
private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(LogHelper.class);
public enum LogLevelEnum {
QUIET(Level.OFF),
PROGRESS(Level.OFF),
ERROR(Level.ERROR),
WARN(Level.WARN),
INFO(Level.INFO),
DEBUG(Level.DEBUG);
private final Level level;
LogLevelEnum(Level level) {
this.level = level;
}
public Level getLevel() {
return level;
}
}
public static void setLogLevelFromArgs(JadxCLIArgs args) {
if (isCustomLogConfig()) {
return;
}
LogLevelEnum logLevel = args.logLevel;
if (args.quiet) {
logLevel = LogLevelEnum.QUIET;
} else if (args.verbose) {
logLevel = LogLevelEnum.DEBUG;
}
applyLogLevel(logLevel);
}
public static void applyLogLevel(LogLevelEnum logLevel) {
Logger rootLogger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
rootLogger.setLevel(logLevel.getLevel());
if (logLevel != LogLevelEnum.QUIET) {
// show progress for all levels except quiet
setLevelForClass(JadxCLI.class, Level.INFO);
setLevelForClass(JadxDecompiler.class, Level.INFO);
}
}
private static void setLevelForClass(Class<?> cls, Level level) {
((Logger) LoggerFactory.getLogger(cls)).setLevel(level);
}
/**
* Try to detect if user provide custom logback config via -Dlogback.configurationFile=
*/
private static boolean isCustomLogConfig() {
try {
String logbackConfig = System.getProperty("logback.configurationFile");
if (logbackConfig == null) {
return false;
}
LOG.debug("Use custom log config: {}", logbackConfig);
return true;
} catch (Exception e) {
LOG.error("Failed to detect custom log config", e);
}
return false;
}
public static class LogLevelConverter implements IStringConverter<LogLevelEnum> {
@Override
public LogLevelEnum convert(String value) {
try {
return LogLevelEnum.valueOf(value.toUpperCase());
} catch (Exception e) {
throw new IllegalArgumentException(
'\'' + value + "' is unknown log level, possible values are "
+ JadxCLIArgs.enumValuesString(LogLevelEnum.values()));
}
}
}
}
+8 -10
View File
@@ -1,13 +1,11 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%-5level - %msg%n</pattern>
</encoder>
</appender>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%-5level - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT"/>
</root>
<root level="INFO">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
@@ -0,0 +1,67 @@
package jadx.cli;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
public class JadxCLIArgsTest {
private static final Logger LOG = LoggerFactory.getLogger(JadxCLIArgsTest.class);
@Test
public void testInvertedBooleanOption() {
assertThat(parse("--no-replace-consts").isReplaceConsts(), is(false));
assertThat(parse("").isReplaceConsts(), is(true));
}
@Test
public void testEscapeUnicodeOption() {
assertThat(parse("--escape-unicode").isEscapeUnicode(), is(true));
assertThat(parse("").isEscapeUnicode(), is(false));
}
@Test
public void testSrcOption() {
assertThat(parse("--no-src").isSkipSources(), is(true));
assertThat(parse("-s").isSkipSources(), is(true));
assertThat(parse("").isSkipSources(), is(false));
}
@Test
public void testOptionsOverride() {
assertThat(override(new JadxCLIArgs(), "--no-imports").isUseImports(), is(false));
assertThat(override(new JadxCLIArgs(), "--no-debug-info").isDebugInfo(), is(false));
assertThat(override(new JadxCLIArgs(), "").isUseImports(), is(true));
JadxCLIArgs args = new JadxCLIArgs();
args.useImports = false;
assertThat(override(args, "--no-imports").isUseImports(), is(false));
args.debugInfo = false;
assertThat(override(args, "--no-debug-info").isDebugInfo(), is(false));
args = new JadxCLIArgs();
args.useImports = false;
assertThat(override(args, "").isUseImports(), is(false));
}
private JadxCLIArgs parse(String... args) {
return parse(new JadxCLIArgs(), args);
}
private JadxCLIArgs parse(JadxCLIArgs jadxArgs, String... args) {
boolean res = jadxArgs.processArgs(args);
assertThat(res, is(true));
LOG.info("Jadx args: {}", jadxArgs.toJadxArgs());
return jadxArgs;
}
private JadxCLIArgs override(JadxCLIArgs jadxArgs, String... args) {
boolean res = jadxArgs.overrideProvided(args);
assertThat(res, is(true));
LOG.info("Jadx args: {}", jadxArgs.toJadxArgs());
return jadxArgs;
}
}
@@ -0,0 +1,49 @@
package jadx.cli;
import java.util.Set;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import jadx.api.JadxArgs.RenameEnum;
import jadx.cli.JadxCLIArgs.RenameConverter;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class RenameConverterTest {
private RenameConverter converter;
@BeforeEach
public void init() {
converter = new RenameConverter("someParam");
}
@Test
public void all() {
Set<RenameEnum> set = converter.convert("all");
assertEquals(3, set.size());
assertTrue(set.contains(RenameEnum.CASE));
assertTrue(set.contains(RenameEnum.VALID));
assertTrue(set.contains(RenameEnum.PRINTABLE));
}
@Test
public void none() {
Set<RenameEnum> set = converter.convert("none");
assertTrue(set.isEmpty());
}
@Test
public void wrong() {
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class,
() -> converter.convert("wrong"),
"Expected convert() to throw, but it didn't");
assertEquals("'wrong' is unknown for parameter someParam, "
+ "possible values are 'case', 'valid', 'printable'",
thrown.getMessage());
}
}
+15 -9
View File
@@ -1,12 +1,18 @@
ext.jadxClasspath = 'clsp-data/android-4.3.jar'
dependencies {
compile files('lib/dx-1.8.jar')
runtime files(jadxClasspath)
}
runtime files('clsp-data/android-29-clst.jar')
runtime files('clsp-data/android-29-res.jar')
task packTests(type: Jar) {
classifier = 'tests'
from sourceSets.test.output
}
compile files('lib/dx-1.16.jar') // TODO: dx don't support java version > 9 (53)
compile 'org.ow2.asm:asm:7.2'
compile 'org.jetbrains:annotations:18.0.0'
compile 'com.google.code.gson:gson:2.8.6'
compile 'org.smali:baksmali:2.3.4'
compile('org.smali:smali:2.3.4') {
exclude group: 'com.google.guava'
}
compile 'com.google.guava:guava:28.1-jre'
testCompile 'org.apache.commons:commons-lang3:3.9'
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -2,24 +2,32 @@ package jadx.api;
public final class CodePosition {
private final JavaClass cls;
private final JavaNode node;
private final int line;
private final int offset;
public CodePosition(JavaClass cls, int line, int offset) {
this.cls = cls;
public CodePosition(JavaNode node, int line, int offset) {
this.node = node;
this.line = line;
this.offset = offset;
}
public CodePosition(int line, int offset) {
this.cls = null;
this.node = null;
this.line = line;
this.offset = offset;
}
public JavaNode getNode() {
return node;
}
public JavaClass getJavaClass() {
return cls;
JavaClass parent = node.getDeclaringClass();
if (parent == null && node instanceof JavaClass) {
return (JavaClass) node;
}
return parent;
}
public int getLine() {
@@ -30,10 +38,6 @@ public final class CodePosition {
return offset;
}
public boolean isSet() {
return line != 0 || offset != 0;
}
@Override
public boolean equals(Object o) {
if (this == o) {
@@ -53,6 +57,14 @@ public final class CodePosition {
@Override
public String toString() {
return line + ":" + offset + (cls != null ? " " + cls : "");
StringBuilder sb = new StringBuilder();
sb.append(line);
if (offset != 0) {
sb.append(':').append(offset);
}
if (node != null) {
sb.append(' ').append(node);
}
return sb.toString();
}
}
@@ -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;
}
}
@@ -0,0 +1,11 @@
package jadx.api;
import org.jetbrains.annotations.Nullable;
public interface ICodeCache {
void add(String clsFullName, ICodeInfo codeInfo);
@Nullable
ICodeInfo get(String clsFullName);
}
@@ -0,0 +1,11 @@
package jadx.api;
import java.util.Map;
public interface ICodeInfo {
String getCodeStr();
Map<Integer, Integer> getLineMapping();
Map<CodePosition, Object> getAnnotations();
}
@@ -1,13 +0,0 @@
package jadx.api;
public interface IJadxArgs {
int getThreadsCount();
boolean isCFGOutput();
boolean isRawCFGOutput();
boolean isFallbackMode();
boolean isVerbose();
}
@@ -0,0 +1,370 @@
package jadx.api;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import jadx.api.impl.InMemoryCodeCache;
public class JadxArgs {
public static final int DEFAULT_THREADS_COUNT = Math.max(1, Runtime.getRuntime().availableProcessors() / 2);
public static final String DEFAULT_OUT_DIR = "jadx-output";
public static final String DEFAULT_SRC_DIR = "sources";
public static final String DEFAULT_RES_DIR = "resources";
private List<File> inputFiles = new ArrayList<>(1);
private File outDir;
private File outDirSrc;
private File outDirRes;
private ICodeCache codeCache = new InMemoryCodeCache();
private int threadsCount = DEFAULT_THREADS_COUNT;
private boolean cfgOutput = false;
private boolean rawCFGOutput = false;
private boolean fallbackMode = false;
private boolean showInconsistentCode = false;
private boolean useImports = true;
private boolean debugInfo = true;
private boolean inlineAnonymousClasses = true;
private boolean skipResources = false;
private boolean skipSources = false;
/**
* Predicate that allows to filter the classes to be process based on their full name
*/
private Predicate<String> classFilter = null;
private boolean deobfuscationOn = false;
private boolean deobfuscationForceSave = 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 respectBytecodeAccModifiers = false;
private boolean exportAsGradleProject = false;
private boolean fsCaseSensitive;
public enum RenameEnum {
CASE, VALID, PRINTABLE
}
private Set<RenameEnum> renameFlags = EnumSet.allOf(RenameEnum.class);
public enum OutputFormatEnum {
JAVA, JSON
}
private OutputFormatEnum outputFormat = OutputFormatEnum.JAVA;
public JadxArgs() {
// use default options
}
public void setRootDir(File rootDir) {
setOutDir(rootDir);
setOutDirSrc(new File(rootDir, DEFAULT_SRC_DIR));
setOutDirRes(new File(rootDir, DEFAULT_RES_DIR));
}
public List<File> getInputFiles() {
return inputFiles;
}
public void setInputFile(File inputFile) {
this.inputFiles = Collections.singletonList(inputFile);
}
public void setInputFiles(List<File> inputFiles) {
this.inputFiles = inputFiles;
}
public File getOutDir() {
return outDir;
}
public void setOutDir(File outDir) {
this.outDir = outDir;
}
public File getOutDirSrc() {
return outDirSrc;
}
public void setOutDirSrc(File outDirSrc) {
this.outDirSrc = outDirSrc;
}
public File getOutDirRes() {
return outDirRes;
}
public void setOutDirRes(File outDirRes) {
this.outDirRes = outDirRes;
}
public int getThreadsCount() {
return threadsCount;
}
public void setThreadsCount(int threadsCount) {
this.threadsCount = threadsCount;
}
public boolean isCfgOutput() {
return cfgOutput;
}
public void setCfgOutput(boolean cfgOutput) {
this.cfgOutput = cfgOutput;
}
public boolean isRawCFGOutput() {
return rawCFGOutput;
}
public void setRawCFGOutput(boolean rawCFGOutput) {
this.rawCFGOutput = rawCFGOutput;
}
public boolean isFallbackMode() {
return fallbackMode;
}
public void setFallbackMode(boolean fallbackMode) {
this.fallbackMode = fallbackMode;
}
public boolean isShowInconsistentCode() {
return showInconsistentCode;
}
public void setShowInconsistentCode(boolean showInconsistentCode) {
this.showInconsistentCode = showInconsistentCode;
}
public boolean isUseImports() {
return useImports;
}
public void setUseImports(boolean useImports) {
this.useImports = useImports;
}
public boolean isDebugInfo() {
return debugInfo;
}
public void setDebugInfo(boolean debugInfo) {
this.debugInfo = debugInfo;
}
public boolean isInlineAnonymousClasses() {
return inlineAnonymousClasses;
}
public void setInlineAnonymousClasses(boolean inlineAnonymousClasses) {
this.inlineAnonymousClasses = inlineAnonymousClasses;
}
public boolean isSkipResources() {
return skipResources;
}
public void setSkipResources(boolean skipResources) {
this.skipResources = skipResources;
}
public boolean isSkipSources() {
return skipSources;
}
public void setSkipSources(boolean skipSources) {
this.skipSources = skipSources;
}
public Predicate<String> getClassFilter() {
return classFilter;
}
public void setClassFilter(Predicate<String> classFilter) {
this.classFilter = classFilter;
}
public boolean isDeobfuscationOn() {
return deobfuscationOn;
}
public void setDeobfuscationOn(boolean deobfuscationOn) {
this.deobfuscationOn = deobfuscationOn;
}
public boolean isDeobfuscationForceSave() {
return deobfuscationForceSave;
}
public void setDeobfuscationForceSave(boolean deobfuscationForceSave) {
this.deobfuscationForceSave = deobfuscationForceSave;
}
public boolean isUseSourceNameAsClassAlias() {
return useSourceNameAsClassAlias;
}
public void setUseSourceNameAsClassAlias(boolean useSourceNameAsClassAlias) {
this.useSourceNameAsClassAlias = useSourceNameAsClassAlias;
}
public int getDeobfuscationMinLength() {
return deobfuscationMinLength;
}
public void setDeobfuscationMinLength(int deobfuscationMinLength) {
this.deobfuscationMinLength = deobfuscationMinLength;
}
public int getDeobfuscationMaxLength() {
return deobfuscationMaxLength;
}
public void setDeobfuscationMaxLength(int deobfuscationMaxLength) {
this.deobfuscationMaxLength = deobfuscationMaxLength;
}
public boolean isEscapeUnicode() {
return escapeUnicode;
}
public void setEscapeUnicode(boolean escapeUnicode) {
this.escapeUnicode = escapeUnicode;
}
public boolean isReplaceConsts() {
return replaceConsts;
}
public void setReplaceConsts(boolean replaceConsts) {
this.replaceConsts = replaceConsts;
}
public boolean isRespectBytecodeAccModifiers() {
return respectBytecodeAccModifiers;
}
public void setRespectBytecodeAccModifiers(boolean respectBytecodeAccModifiers) {
this.respectBytecodeAccModifiers = respectBytecodeAccModifiers;
}
public boolean isExportAsGradleProject() {
return exportAsGradleProject;
}
public void setExportAsGradleProject(boolean exportAsGradleProject) {
this.exportAsGradleProject = exportAsGradleProject;
}
public boolean isFsCaseSensitive() {
return fsCaseSensitive;
}
public void setFsCaseSensitive(boolean fsCaseSensitive) {
this.fsCaseSensitive = fsCaseSensitive;
}
public boolean isRenameCaseSensitive() {
return renameFlags.contains(RenameEnum.CASE);
}
public void setRenameCaseSensitive(boolean renameCaseSensitive) {
updateRenameFlag(renameCaseSensitive, RenameEnum.CASE);
}
public boolean isRenameValid() {
return renameFlags.contains(RenameEnum.VALID);
}
public void setRenameValid(boolean renameValid) {
updateRenameFlag(renameValid, RenameEnum.VALID);
}
public boolean isRenamePrintable() {
return renameFlags.contains(RenameEnum.PRINTABLE);
}
public void setRenamePrintable(boolean renamePrintable) {
updateRenameFlag(renamePrintable, RenameEnum.PRINTABLE);
}
private void updateRenameFlag(boolean enabled, RenameEnum flag) {
if (enabled) {
renameFlags.add(flag);
} else {
renameFlags.remove(flag);
}
}
public OutputFormatEnum getOutputFormat() {
return outputFormat;
}
public boolean isJsonOutput() {
return outputFormat == OutputFormatEnum.JSON;
}
public void setOutputFormat(OutputFormatEnum outputFormat) {
this.outputFormat = outputFormat;
}
public ICodeCache getCodeCache() {
return codeCache;
}
public void setCodeCache(ICodeCache codeCache) {
this.codeCache = codeCache;
}
@Override
public String toString() {
return "JadxArgs{" + "inputFiles=" + inputFiles
+ ", outDir=" + outDir
+ ", outDirSrc=" + outDirSrc
+ ", outDirRes=" + outDirRes
+ ", threadsCount=" + threadsCount
+ ", cfgOutput=" + cfgOutput
+ ", rawCFGOutput=" + rawCFGOutput
+ ", fallbackMode=" + fallbackMode
+ ", showInconsistentCode=" + showInconsistentCode
+ ", useImports=" + useImports
+ ", skipResources=" + skipResources
+ ", skipSources=" + skipSources
+ ", deobfuscationOn=" + deobfuscationOn
+ ", deobfuscationForceSave=" + deobfuscationForceSave
+ ", useSourceNameAsClassAlias=" + useSourceNameAsClassAlias
+ ", deobfuscationMinLength=" + deobfuscationMinLength
+ ", deobfuscationMaxLength=" + deobfuscationMaxLength
+ ", escapeUnicode=" + escapeUnicode
+ ", replaceConsts=" + replaceConsts
+ ", respectBytecodeAccModifiers=" + respectBytecodeAccModifiers
+ ", exportAsGradleProject=" + exportAsGradleProject
+ ", fsCaseSensitive=" + fsCaseSensitive
+ ", renameFlags=" + renameFlags
+ ", outputFormat=" + outputFormat
+ ", codeCache=" + codeCache
+ '}';
}
}
@@ -0,0 +1,104 @@
package jadx.api;
import java.io.File;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.utils.exceptions.JadxArgsValidateException;
public class JadxArgsValidator {
private static final Logger LOG = LoggerFactory.getLogger(JadxArgsValidator.class);
public static void validate(JadxArgs args) {
checkInputFiles(args);
validateOutDirs(args);
if (LOG.isDebugEnabled()) {
LOG.debug("Effective jadx args: {}", args);
}
}
private static void checkInputFiles(JadxArgs args) {
List<File> inputFiles = args.getInputFiles();
if (inputFiles.isEmpty()) {
throw new JadxArgsValidateException("Please specify input file");
}
if (inputFiles.size() > 1) {
for (File inputFile : inputFiles) {
String fileName = inputFile.getName();
if (fileName.startsWith("--")) {
throw new JadxArgsValidateException("Unknown argument: " + fileName);
}
}
throw new JadxArgsValidateException("Only one input file supported");
}
for (File file : inputFiles) {
checkFile(file);
}
}
private static void validateOutDirs(JadxArgs args) {
File outDir = args.getOutDir();
File srcDir = args.getOutDirSrc();
File resDir = args.getOutDirRes();
if (outDir == null) {
if (srcDir != null) {
outDir = srcDir;
} else if (resDir != null) {
outDir = resDir;
} else {
outDir = makeDirFromInput(args);
}
args.setOutDir(outDir);
}
if (srcDir == null) {
args.setOutDirSrc(new File(args.getOutDir(), JadxArgs.DEFAULT_SRC_DIR));
}
if (resDir == null) {
args.setOutDirRes(new File(args.getOutDir(), JadxArgs.DEFAULT_RES_DIR));
}
checkDir(args.getOutDir(), "Output");
checkDir(args.getOutDirSrc(), "Source output");
checkDir(args.getOutDirRes(), "Resources output");
}
@NotNull
private static File makeDirFromInput(JadxArgs args) {
File outDir;
String outDirName;
File file = args.getInputFiles().get(0);
String name = file.getName();
int pos = name.lastIndexOf('.');
if (pos != -1) {
outDirName = name.substring(0, pos);
} else {
outDirName = name + '-' + JadxArgs.DEFAULT_OUT_DIR;
}
LOG.info("output directory: {}", outDirName);
outDir = new File(outDirName);
return outDir;
}
private static void checkFile(File file) {
if (!file.exists()) {
throw new JadxArgsValidateException("File not found " + file.getAbsolutePath());
}
if (file.isDirectory()) {
throw new JadxArgsValidateException("Expected file but found directory instead: " + file.getAbsolutePath());
}
}
private static void checkDir(File dir, String desc) {
if (dir != null && dir.exists() && !dir.isDirectory()) {
throw new JadxArgsValidateException(desc + " directory exists as file " + dir);
}
}
private JadxArgsValidator() {
}
}
@@ -1,120 +1,157 @@
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.JadxException;
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.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.Jadx;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.nodes.LineAttrNode;
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.SaveCode;
import jadx.core.export.ExportGradleProject;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.InputFile;
import jadx.core.xmlgen.BinaryXMLParser;
import jadx.core.xmlgen.ResourcesSaver;
/**
* 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>
*
* <pre>
* <code>
* JadxArgs args = new JadxArgs();
* args.getInputFiles().add(new File("test.apk"));
* args.setOutDir(new File("jadx-test-output"));
*
* JadxDecompiler jadx = new JadxDecompiler(args);
* jadx.load();
* jadx.save();
* </code>
* </pre>
* <p>
* Instead of 'save()' you can iterate over decompiled classes:
*
* <pre>
* <code>
* for(JavaClass cls : jadx.getClasses()) {
* System.out.println(cls.getCode());
* }
* </code></pre>
* </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 JadxArgs args;
private List<InputFile> inputFiles;
private RootNode root;
private List<IDexTreeVisitor> passes;
private List<JavaClass> classes;
private List<ResourceFile> resources;
private BinaryXMLParser xmlParser;
private Map<ClassNode, JavaClass> classesMap = new ConcurrentHashMap<>();
private Map<MethodNode, JavaMethod> methodsMap = new ConcurrentHashMap<>();
private Map<FieldNode, JavaField> fieldsMap = new ConcurrentHashMap<>();
public JadxDecompiler() {
this.args = new DefaultJadxArgs();
init();
this(new JadxArgs());
}
public JadxDecompiler(IJadxArgs jadxArgs) {
this.args = jadxArgs;
init();
public JadxDecompiler(JadxArgs args) {
this.args = args;
}
public void setOutputDir(File outDir) {
this.outDir = outDir;
init();
}
void init() {
public void load() {
reset();
if (outDir == null) {
outDir = new File("jadx-output");
}
this.passes = Jadx.getPassesList(args, outDir);
JadxArgsValidator.validate(args);
LOG.info("loading ...");
inputFiles = loadFiles(args.getInputFiles());
root = new RootNode(args);
root.load(inputFiles);
root.initClassPath();
root.loadResources(getResources());
root.initPasses();
}
void reset() {
ClassInfo.clearCache();
ErrorsCounter.reset();
private void reset() {
root = null;
classes = null;
resources = null;
xmlParser = null;
classesMap.clear();
methodsMap.clear();
fieldsMap.clear();
}
public void loadFile(File file) throws JadxException {
loadFiles(Collections.singletonList(file));
public static String getVersion() {
return Jadx.getVersion();
}
public void loadFiles(List<File> files) throws JadxException {
private List<InputFile> loadFiles(List<File> files) {
if (files.isEmpty()) {
throw new JadxException("Empty file list");
throw new JadxRuntimeException("Empty file list");
}
inputFiles.clear();
List<InputFile> filesList = new ArrayList<>();
for (File file : files) {
try {
inputFiles.add(new InputFile(file));
} catch (IOException e) {
throw new JadxException("Error load file: " + file, e);
InputFile.addFilesFrom(file, filesList, args.isSkipSources());
} catch (Exception e) {
throw new JadxRuntimeException("Error load file: " + file, e);
}
}
parse();
return filesList;
}
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) {
ExecutorService ex = getSaveExecutor(saveSources, saveResources);
ex.shutdown();
try {
ExecutorService ex = getSaveExecutor();
ex.shutdown();
ex.awaitTermination(1, TimeUnit.DAYS);
} catch (InterruptedException e) {
throw new JadxRuntimeException("Save interrupted", e);
LOG.error("Save interrupted", e);
Thread.currentThread().interrupt();
}
}
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");
}
@@ -123,16 +160,51 @@ public final class JadxDecompiler {
LOG.info("processing ...");
ExecutorService executor = Executors.newFixedThreadPool(threadsCount);
for (final JavaClass cls : getClasses()) {
executor.execute(new Runnable() {
@Override
public void run() {
cls.decompile();
SaveCode.save(outDir, args, cls.getClassNode());
File sourcesOutDir;
File resOutDir;
if (args.isExportAsGradleProject()) {
ExportGradleProject export = new ExportGradleProject(root, args.getOutDir());
export.init();
sourcesOutDir = export.getSrcOutDir();
resOutDir = export.getResOutDir();
} else {
sourcesOutDir = args.getOutDirSrc();
resOutDir = args.getOutDirRes();
}
if (saveResources) {
appendResourcesSave(executor, resOutDir);
}
if (saveSources) {
appendSourcesSave(executor, sourcesOutDir);
}
return executor;
}
private void appendResourcesSave(ExecutorService executor, File outDir) {
for (ResourceFile resourceFile : getResources()) {
executor.execute(new ResourcesSaver(outDir, resourceFile));
}
}
private void appendSourcesSave(ExecutorService executor, File outDir) {
final Predicate<String> classFilter = args.getClassFilter();
for (JavaClass cls : getClasses()) {
if (cls.getClassNode().contains(AFlag.DONT_GENERATE)) {
continue;
}
if (classFilter != null && !classFilter.test(cls.getFullName())) {
continue;
}
executor.execute(() -> {
try {
ICodeInfo code = cls.getCodeInfo();
SaveCode.save(outDir, cls.getClassNode(), code);
} catch (Exception e) {
LOG.error("Error saving class: {}", cls.getFullName(), e);
}
});
}
return executor;
}
public List<JavaClass> getClasses() {
@@ -141,79 +213,220 @@ public final class JadxDecompiler {
}
if (classes == null) {
List<ClassNode> classNodeList = root.getClasses(false);
List<JavaClass> clsList = new ArrayList<JavaClass>(classNodeList.size());
List<JavaClass> clsList = new ArrayList<>(classNodeList.size());
classesMap.clear();
for (ClassNode classNode : classNodeList) {
clsList.add(new JavaClass(classNode, this));
if (!classNode.contains(AFlag.DONT_GENERATE)) {
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>>();
Map<String, List<JavaClass>> map = new HashMap<>();
for (JavaClass javaClass : classList) {
String pkg = javaClass.getPackage();
List<JavaClass> clsList = map.get(pkg);
if (clsList == null) {
clsList = new ArrayList<JavaClass>();
map.put(pkg, clsList);
}
List<JavaClass> clsList = map.computeIfAbsent(pkg, k -> new ArrayList<>());
clsList.add(javaClass);
}
List<JavaPackage> packages = new ArrayList<JavaPackage>(map.size());
List<JavaPackage> packages = new ArrayList<>(map.size());
for (Map.Entry<String, List<JavaClass>> entry : map.entrySet()) {
packages.add(new JavaPackage(entry.getKey(), entry.getValue()));
}
Collections.sort(packages);
for (JavaPackage pkg : packages) {
Collections.sort(pkg.getClasses(), new Comparator<JavaClass>() {
@Override
public int compare(JavaClass o1, JavaClass o2) {
return o1.getName().compareTo(o2.getName());
}
});
pkg.getClasses().sort(Comparator.comparing(JavaClass::getName, String.CASE_INSENSITIVE_ORDER));
}
return Collections.unmodifiableList(packages);
}
public int getErrorsCount() {
return ErrorsCounter.getErrorCount();
if (root == null) {
return 0;
}
return root.getErrorsCounter().getErrorCount();
}
void parse() throws DecodeException {
reset();
root = new RootNode();
LOG.info("loading ...");
root.load(inputFiles);
public int getWarnsCount() {
if (root == null) {
return 0;
}
return root.getErrorsCounter().getWarnsCount();
}
void processClass(ClassNode cls) {
ProcessClass.process(cls, passes);
public void printErrorsReport() {
if (root == null) {
return;
}
root.getClsp().printMissingClasses();
root.getErrorsCounter().printReport();
}
RootNode getRoot() {
return root;
}
JavaClass findJavaClass(ClassNode cls) {
if (cls == null) {
synchronized BinaryXMLParser getXmlParser() {
if (xmlParser == null) {
xmlParser = new BinaryXMLParser(root);
}
return xmlParser;
}
private void loadJavaClass(JavaClass javaClass) {
javaClass.getMethods().forEach(mth -> methodsMap.put(mth.getMethodNode(), mth));
javaClass.getFields().forEach(fld -> fieldsMap.put(fld.getFieldNode(), fld));
for (JavaClass innerCls : javaClass.getInnerClasses()) {
classesMap.put(innerCls.getClassNode(), innerCls);
loadJavaClass(innerCls);
}
}
@Nullable("For not generated classes")
private JavaClass getJavaClassByNode(ClassNode cls) {
JavaClass javaClass = classesMap.get(cls);
if (javaClass != null) {
return javaClass;
}
// load parent class if inner
ClassNode parentClass = cls.getTopParentClass();
if (parentClass.contains(AFlag.DONT_GENERATE)) {
return null;
}
for (JavaClass javaClass : getClasses()) {
if (javaClass.getClassNode().equals(cls)) {
if (parentClass != cls) {
JavaClass parentJavaClass = classesMap.get(parentClass);
if (parentJavaClass == null) {
getClasses();
parentJavaClass = classesMap.get(parentClass);
}
loadJavaClass(parentJavaClass);
javaClass = classesMap.get(cls);
if (javaClass != null) {
return javaClass;
}
}
return null;
// class or parent classes can be excluded from generation
if (cls.hasNotGeneratedParent()) {
return null;
}
throw new JadxRuntimeException("JavaClass not found by ClassNode: " + cls);
}
@Nullable
private JavaMethod getJavaMethodByNode(MethodNode mth) {
JavaMethod javaMethod = methodsMap.get(mth);
if (javaMethod != null) {
return javaMethod;
}
// parent class not loaded yet
JavaClass javaClass = getJavaClassByNode(mth.getParentClass().getTopParentClass());
if (javaClass == null) {
return null;
}
loadJavaClass(javaClass);
javaMethod = methodsMap.get(mth);
if (javaMethod != null) {
return javaMethod;
}
if (mth.getParentClass().hasNotGeneratedParent()) {
return null;
}
throw new JadxRuntimeException("JavaMethod not found by MethodNode: " + mth);
}
@Nullable
private JavaField getJavaFieldByNode(FieldNode fld) {
JavaField javaField = fieldsMap.get(fld);
if (javaField != null) {
return javaField;
}
// parent class not loaded yet
JavaClass javaClass = getJavaClassByNode(fld.getParentClass().getTopParentClass());
if (javaClass == null) {
return null;
}
loadJavaClass(javaClass);
javaField = fieldsMap.get(fld);
if (javaField != null) {
return javaField;
}
if (fld.getParentClass().hasNotGeneratedParent()) {
return null;
}
throw new JadxRuntimeException("JavaField not found by FieldNode: " + fld);
}
@Nullable
JavaNode convertNode(Object obj) {
if (!(obj instanceof LineAttrNode)) {
return null;
}
LineAttrNode node = (LineAttrNode) obj;
if (node.contains(AFlag.DONT_GENERATE)) {
return null;
}
if (obj instanceof ClassNode) {
return getJavaClassByNode((ClassNode) obj);
}
if (obj instanceof MethodNode) {
return getJavaMethodByNode(((MethodNode) obj));
}
if (obj instanceof FieldNode) {
return getJavaFieldByNode((FieldNode) obj);
}
throw new JadxRuntimeException("Unexpected node type: " + obj);
}
@Nullable
public JavaNode getJavaNodeAtPosition(ICodeInfo codeInfo, int line, int offset) {
Map<CodePosition, Object> map = codeInfo.getAnnotations();
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(JavaNode javaNode) {
JavaClass jCls = javaNode.getTopParentClass();
jCls.decompile();
int defLine = javaNode.getDecompiledLine();
if (defLine == 0) {
return null;
}
return new CodePosition(jCls, defLine, 0);
}
public JadxArgs getArgs() {
return args;
}
@Override
public String toString() {
return "jadx decompiler";
return "jadx decompiler " + getVersion();
}
}
+92 -67
View File
@@ -1,19 +1,20 @@
package jadx.api;
import jadx.core.codegen.CodeWriter;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jetbrains.annotations.Nullable;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
public final class JavaClass implements JavaNode {
private final JadxDecompiler decompiler;
@@ -23,6 +24,7 @@ public final class JavaClass implements JavaNode {
private List<JavaClass> innerClasses = Collections.emptyList();
private List<JavaField> fields = Collections.emptyList();
private List<JavaMethod> methods = Collections.emptyList();
private boolean listsLoaded;
JavaClass(ClassNode classNode, JadxDecompiler decompiler) {
this.decompiler = decompiler;
@@ -40,39 +42,48 @@ public final class JavaClass implements JavaNode {
}
public String getCode() {
CodeWriter code = cls.getCode();
if (code == null) {
decompile();
code = cls.getCode();
}
ICodeInfo code = getCodeInfo();
if (code == null) {
return "";
}
return code.toString();
return code.getCodeStr();
}
public ICodeInfo getCodeInfo() {
return cls.decompile();
}
public void decompile() {
if (decompiler == null) {
return;
}
if (cls.getCode() == null) {
decompiler.processClass(cls);
load();
}
cls.decompile();
}
ClassNode getClassNode() {
public synchronized String getSmali() {
return cls.getSmali();
}
public synchronized void unload() {
cls.unload();
listsLoaded = false;
}
public ClassNode getClassNode() {
return cls;
}
private void load() {
private void loadLists() {
if (listsLoaded) {
return;
}
listsLoaded = true;
decompile();
int inClsCount = cls.getInnerClasses().size();
if (inClsCount != 0) {
List<JavaClass> list = new ArrayList<JavaClass>(inClsCount);
List<JavaClass> list = new ArrayList<>(inClsCount);
for (ClassNode inner : cls.getInnerClasses()) {
if (!inner.contains(AFlag.DONT_GENERATE)) {
JavaClass javaClass = new JavaClass(inner, this);
javaClass.load();
javaClass.loadLists();
list.add(javaClass);
}
}
@@ -81,10 +92,11 @@ public final class JavaClass implements JavaNode {
int fieldsCount = cls.getFields().size();
if (fieldsCount != 0) {
List<JavaField> flds = new ArrayList<JavaField>(fieldsCount);
List<JavaField> flds = new ArrayList<>(fieldsCount);
for (FieldNode f : cls.getFields()) {
if (!f.contains(AFlag.DONT_GENERATE)) {
flds.add(new JavaField(f, this));
JavaField javaField = new JavaField(f, this);
flds.add(javaField);
}
}
this.fields = Collections.unmodifiableList(flds);
@@ -92,57 +104,64 @@ public final class JavaClass implements JavaNode {
int methodsCount = cls.getMethods().size();
if (methodsCount != 0) {
List<JavaMethod> mths = new ArrayList<JavaMethod>(methodsCount);
List<JavaMethod> mths = new ArrayList<>(methodsCount);
for (MethodNode m : cls.getMethods()) {
if (!m.contains(AFlag.DONT_GENERATE)) {
mths.add(new JavaMethod(this, m));
JavaMethod javaMethod = new JavaMethod(this, m);
mths.add(javaMethod);
}
}
Collections.sort(mths, new Comparator<JavaMethod>() {
@Override
public int compare(JavaMethod o1, JavaMethod o2) {
return o1.getName().compareTo(o2.getName());
}
});
mths.sort(Comparator.comparing(JavaMethod::getName));
this.methods = Collections.unmodifiableList(mths);
}
}
private Map<CodePosition, Object> getCodeAnnotations() {
decompile();
return cls.getCode().getAnnotations();
private JadxDecompiler getRootDecompiler() {
if (parent != null) {
return parent.getRootDecompiler();
}
return decompiler;
}
public CodePosition getDefinitionPosition(int line, int offset) {
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 (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);
private Map<CodePosition, Object> getCodeAnnotations() {
ICodeInfo code = getCodeInfo();
if (code == null) {
return Collections.emptyMap();
}
return null;
return code.getAnnotations();
}
public Map<CodePosition, JavaNode> getUsageMap() {
Map<CodePosition, Object> map = getCodeAnnotations();
if (map.isEmpty() || decompiler == null) {
return Collections.emptyMap();
}
Map<CodePosition, JavaNode> resultMap = new HashMap<>(map.size());
for (Map.Entry<CodePosition, Object> entry : map.entrySet()) {
CodePosition codePosition = entry.getKey();
Object obj = entry.getValue();
JavaNode node = getRootDecompiler().convertNode(obj);
if (node != null) {
resultMap.put(codePosition, node);
}
}
return resultMap;
}
@Nullable
@Deprecated
public JavaNode getJavaNodeAtPosition(int line, int offset) {
return getRootDecompiler().getJavaNodeAtPosition(getCodeInfo(), line, offset);
}
@Nullable
@Deprecated
public CodePosition getDefinitionPosition() {
return getRootDecompiler().getDefinitionPosition(this);
}
public Integer getSourceLine(int decompiledLine) {
decompile();
return cls.getCode().getLineMapping().get(decompiledLine);
return getCodeInfo().getLineMapping().get(decompiledLine);
}
@Override
@@ -164,25 +183,31 @@ public final class JavaClass implements JavaNode {
return parent;
}
@Override
public JavaClass getTopParentClass() {
return parent == null ? this : parent.getTopParentClass();
}
public AccessInfo getAccessInfo() {
return cls.getAccessFlags();
}
public List<JavaClass> getInnerClasses() {
decompile();
loadLists();
return innerClasses;
}
public List<JavaField> getFields() {
decompile();
loadLists();
return fields;
}
public List<JavaMethod> getMethods() {
decompile();
loadLists();
return methods;
}
@Override
public int getDecompiledLine() {
return cls.getDecompiledLine();
}
@@ -16,12 +16,12 @@ public final class JavaField implements JavaNode {
@Override
public String getName() {
return field.getName();
return field.getAlias();
}
@Override
public String getFullName() {
return parent.getFullName() + "." + field.getName();
return parent.getFullName() + '.' + getName();
}
@Override
@@ -29,15 +29,39 @@ public final class JavaField implements JavaNode {
return parent;
}
@Override
public JavaClass getTopParentClass() {
return parent.getTopParentClass();
}
public AccessInfo getAccessFlags() {
return field.getAccessFlags();
}
public ArgType getType() {
return field.getType();
return ArgType.tryToResolveClassAlias(field.dex(), field.getType());
}
public int getDecompiledLine() {
return field.getDecompiledLine();
}
FieldNode getFieldNode() {
return field;
}
@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();
}
}
@@ -1,10 +1,12 @@
package jadx.api;
import java.util.Collections;
import java.util.List;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.MethodNode;
import java.util.List;
import jadx.core.utils.Utils;
public final class JavaMethod implements JavaNode {
private final MethodNode mth;
@@ -17,7 +19,7 @@ public final class JavaMethod implements JavaNode {
@Override
public String getName() {
return mth.getName();
return mth.getAlias();
}
@Override
@@ -30,16 +32,34 @@ public final class JavaMethod implements JavaNode {
return parent;
}
@Override
public JavaClass getTopParentClass() {
return parent.getTopParentClass();
}
public AccessInfo getAccessFlags() {
return mth.getAccessFlags();
}
public List<ArgType> getArguments() {
return mth.getMethodInfo().getArgumentsTypes();
List<ArgType> infoArgTypes = mth.getMethodInfo().getArgumentsTypes();
if (infoArgTypes.isEmpty()) {
return Collections.emptyList();
}
List<ArgType> arguments = mth.getArgTypes();
if (arguments == null) {
arguments = infoArgTypes;
}
return Utils.collectionMap(arguments,
type -> ArgType.tryToResolveClassAlias(mth.dex(), type));
}
public ArgType getReturnType() {
return mth.getReturnType();
ArgType retType = mth.getReturnType();
if (retType == null) {
retType = mth.getMethodInfo().getReturnType();
}
return ArgType.tryToResolveClassAlias(mth.dex(), retType);
}
public boolean isConstructor() {
@@ -53,4 +73,23 @@ public final class JavaMethod implements JavaNode {
public int getDecompiledLine() {
return mth.getDecompiledLine();
}
MethodNode getMethodNode() {
return mth;
}
@Override
public int hashCode() {
return mth.hashCode();
}
@Override
public boolean equals(Object o) {
return this == o || o instanceof JavaMethod && mth.equals(((JavaMethod) o).mth);
}
@Override
public String toString() {
return mth.toString();
}
}
@@ -7,4 +7,8 @@ public interface JavaNode {
String getFullName();
JavaClass getDeclaringClass();
JavaClass getTopParentClass();
int getDecompiledLine();
}
@@ -2,6 +2,8 @@ package jadx.api;
import java.util.List;
import org.jetbrains.annotations.NotNull;
public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
private final String name;
private final List<JavaClass> classes;
@@ -32,7 +34,17 @@ public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
}
@Override
public int compareTo(JavaPackage o) {
public JavaClass getTopParentClass() {
return null;
}
@Override
public int getDecompiledLine() {
return 0;
}
@Override
public int compareTo(@NotNull JavaPackage o) {
return name.compareTo(o.name);
}
@@ -0,0 +1,75 @@
package jadx.api;
import java.io.File;
import jadx.core.utils.files.ZipSecurity;
import jadx.core.xmlgen.ResContainer;
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;
public static ResourceFile createResourceFile(JadxDecompiler decompiler, String name, ResourceType type) {
if (!ZipSecurity.isValidZipEntryName(name)) {
return null;
}
return new ResourceFile(decompiler, name, type);
}
protected ResourceFile(JadxDecompiler decompiler, String name, ResourceType type) {
this.decompiler = decompiler;
this.name = name;
this.type = type;
}
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;
}
public ZipRef getZipRef() {
return zipRef;
}
@Override
public String toString() {
return "ResourceFile{name='" + name + '\'' + ", type=" + type + '}';
}
}
@@ -0,0 +1,18 @@
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.textResource(getName(), content);
}
}
@@ -0,0 +1,34 @@
package jadx.api;
public enum ResourceType {
CODE(".dex", ".jar", ".class"),
MANIFEST("AndroidManifest.xml"),
XML(".xml"),
ARSC(".arsc"),
FONT(".ttf", ".otf"),
IMG(".png", ".gif", ".jpg"),
MEDIA(".mp3", ".wav"),
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.toLowerCase().endsWith(ext)) {
return type;
}
}
}
return UNKNOWN;
}
}
@@ -0,0 +1,170 @@
package jadx.api;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
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 jadx.api.ResourceFile.ZipRef;
import jadx.core.codegen.CodeWriter;
import jadx.core.utils.Utils;
import jadx.core.utils.android.Res9patchStreamDecoder;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.files.InputFile;
import jadx.core.utils.files.ZipSecurity;
import jadx.core.xmlgen.ResContainer;
import jadx.core.xmlgen.ResTableParser;
import static jadx.core.utils.files.FileUtils.READ_BUFFER_SIZE;
import static jadx.core.utils.files.FileUtils.copyStream;
// TODO: move to core package
public final class ResourcesLoader {
private static final Logger LOG = LoggerFactory.getLogger(ResourcesLoader.class);
private final JadxDecompiler jadxRef;
ResourcesLoader(JadxDecompiler jadxRef) {
this.jadxRef = jadxRef;
}
List<ResourceFile> load(List<InputFile> inputFiles) {
List<ResourceFile> list = new ArrayList<>(inputFiles.size());
for (InputFile file : inputFiles) {
loadFile(list, file.getFile());
}
return list;
}
public interface ResourceDecoder<T> {
T decode(long size, InputStream is) throws IOException;
}
public static <T> T decodeStream(ResourceFile rf, ResourceDecoder<T> decoder) throws JadxException {
try {
ZipRef zipRef = rf.getZipRef();
if (zipRef == null) {
File file = new File(rf.getName());
try (InputStream inputStream = new BufferedInputStream(new FileInputStream(file))) {
return decoder.decode(file.length(), inputStream);
}
} else {
try (ZipFile zipFile = new ZipFile(zipRef.getZipFile())) {
ZipEntry entry = zipFile.getEntry(zipRef.getEntryName());
if (entry == null) {
throw new IOException("Zip entry not found: " + zipRef);
}
if (!ZipSecurity.isValidZipEntry(entry)) {
return null;
}
try (InputStream inputStream = new BufferedInputStream(zipFile.getInputStream(entry))) {
return decoder.decode(entry.getSize(), inputStream);
}
}
}
} catch (Exception e) {
throw new JadxException("Error decode: " + rf.getName(), e);
}
}
static ResContainer loadContent(JadxDecompiler jadxRef, ResourceFile rf) {
try {
return decodeStream(rf, (size, is) -> loadContent(jadxRef, rf, is));
} catch (JadxException e) {
LOG.error("Decode error", e);
CodeWriter cw = new CodeWriter();
cw.add("Error decode ").add(rf.getType().toString().toLowerCase());
Utils.appendStackTrace(cw, e.getCause());
return ResContainer.textResource(rf.getName(), cw);
}
}
private static ResContainer loadContent(JadxDecompiler jadxRef, ResourceFile rf,
InputStream inputStream) throws IOException {
switch (rf.getType()) {
case MANIFEST:
case XML:
CodeWriter content = jadxRef.getXmlParser().parse(inputStream);
return ResContainer.textResource(rf.getName(), content);
case ARSC:
return new ResTableParser(jadxRef.getRoot()).decodeFiles(inputStream);
case IMG:
return decodeImage(rf, inputStream);
default:
return ResContainer.resourceFileLink(rf);
}
}
private static ResContainer decodeImage(ResourceFile rf, InputStream inputStream) {
String name = rf.getName();
if (name.endsWith(".9.png")) {
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
Res9patchStreamDecoder decoder = new Res9patchStreamDecoder();
decoder.decode(inputStream, os);
return ResContainer.decodedData(rf.getName(), os.toByteArray());
} catch (Exception e) {
LOG.error("Failed to decode 9-patch png image, path: {}", name, e);
}
}
return ResContainer.resourceFileLink(rf);
}
private void loadFile(List<ResourceFile> list, File file) {
if (file == null) {
return;
}
try (ZipFile zip = new ZipFile(file)) {
Enumeration<? extends ZipEntry> entries = zip.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
if (ZipSecurity.isValidZipEntry(entry)) {
addEntry(list, file, entry);
}
}
} catch (Exception e) {
LOG.debug("Not a zip file: {}", file.getAbsolutePath());
addResourceFile(list, file);
}
}
private void addResourceFile(List<ResourceFile> list, File file) {
String name = file.getAbsolutePath();
ResourceType type = ResourceType.getFileType(name);
ResourceFile rf = ResourceFile.createResourceFile(jadxRef, name, type);
if (rf != null) {
list.add(rf);
}
}
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 = ResourceFile.createResourceFile(jadxRef, name, type);
if (rf != null) {
rf.setZipRef(new ZipRef(zipFile, name));
list.add(rf);
}
}
public static CodeWriter loadToCodeWriter(InputStream is) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream(READ_BUFFER_SIZE);
copyStream(is, baos);
return new CodeWriter(baos.toString("UTF-8"));
}
}
@@ -0,0 +1,24 @@
package jadx.api.impl;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.jetbrains.annotations.Nullable;
import jadx.api.ICodeCache;
import jadx.api.ICodeInfo;
public class InMemoryCodeCache implements ICodeCache {
private final Map<String, ICodeInfo> storage = new ConcurrentHashMap<>();
@Override
public void add(String clsFullName, ICodeInfo codeInfo) {
storage.put(clsFullName, codeInfo);
}
@Override
public @Nullable ICodeInfo get(String clsFullName) {
return storage.get(clsFullName);
}
}
@@ -0,0 +1,19 @@
package jadx.api.impl;
import org.jetbrains.annotations.Nullable;
import jadx.api.ICodeCache;
import jadx.api.ICodeInfo;
public class NoOpCodeCache implements ICodeCache {
@Override
public void add(String clsFullName, ICodeInfo codeInfo) {
// do nothing
}
@Override
public @Nullable ICodeInfo get(String clsFullName) {
return null;
}
}
@@ -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,10 @@ 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;";
private Consts() {
}
}
+92 -61
View File
@@ -1,32 +1,6 @@
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.DebugInfoVisitor;
import jadx.core.dex.visitors.DotGraphVisitor;
import jadx.core.dex.visitors.EnumVisitor;
import jadx.core.dex.visitors.FallbackModeVisitor;
import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.dex.visitors.MethodInlineVisitor;
import jadx.core.dex.visitors.ModVisitor;
import jadx.core.dex.visitors.PrepareForCodeGen;
import jadx.core.dex.visitors.SimplifyVisitor;
import jadx.core.dex.visitors.regions.CheckRegions;
import jadx.core.dex.visitors.regions.IfRegionVisitor;
import jadx.core.dex.visitors.regions.ProcessVariables;
import jadx.core.dex.visitors.regions.RegionMakerVisitor;
import jadx.core.dex.visitors.regions.ReturnVisitor;
import jadx.core.dex.visitors.ssa.EliminatePhiNodes;
import jadx.core.dex.visitors.ssa.SSATransform;
import jadx.core.dex.visitors.typeinference.FinishTypeInference;
import jadx.core.dex.visitors.typeinference.TypeInference;
import jadx.core.utils.Utils;
import java.io.File;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
@@ -36,76 +10,133 @@ import java.util.jar.Manifest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs;
import jadx.core.dex.visitors.ClassModifier;
import jadx.core.dex.visitors.ConstInlineVisitor;
import jadx.core.dex.visitors.ConstructorVisitor;
import jadx.core.dex.visitors.DeboxingVisitor;
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.FixAccessModifiers;
import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.dex.visitors.InitCodeVariables;
import jadx.core.dex.visitors.MarkFinallyVisitor;
import jadx.core.dex.visitors.MethodInlineVisitor;
import jadx.core.dex.visitors.ModVisitor;
import jadx.core.dex.visitors.PrepareForCodeGen;
import jadx.core.dex.visitors.ProcessAnonymous;
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.BlockFinish;
import jadx.core.dex.visitors.blocksmaker.BlockProcessor;
import jadx.core.dex.visitors.blocksmaker.BlockSplitter;
import jadx.core.dex.visitors.debuginfo.DebugInfoApplyVisitor;
import jadx.core.dex.visitors.debuginfo.DebugInfoParseVisitor;
import jadx.core.dex.visitors.regions.CheckRegions;
import jadx.core.dex.visitors.regions.CleanRegions;
import jadx.core.dex.visitors.regions.IfRegionVisitor;
import jadx.core.dex.visitors.regions.LoopRegionVisitor;
import jadx.core.dex.visitors.regions.RegionMakerVisitor;
import jadx.core.dex.visitors.regions.ReturnVisitor;
import jadx.core.dex.visitors.regions.variables.ProcessVariables;
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
import jadx.core.dex.visitors.ssa.SSATransform;
import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor;
public class Jadx {
private static final Logger LOG = LoggerFactory.getLogger(Jadx.class);
private Jadx() {
}
static {
if (Consts.DEBUG) {
LOG.info("debug enabled");
}
if (Jadx.class.desiredAssertionStatus()) {
LOG.info("assertions enabled");
}
}
public static List<IDexTreeVisitor> getPassesList(IJadxArgs args, File outDir) {
List<IDexTreeVisitor> passes = new ArrayList<IDexTreeVisitor>();
public static List<IDexTreeVisitor> getPassesList(JadxArgs args) {
List<IDexTreeVisitor> passes = new ArrayList<>();
if (args.isFallbackMode()) {
passes.add(new FallbackModeVisitor());
} else {
passes.add(new BlockMakerVisitor());
passes.add(new SSATransform());
passes.add(new DebugInfoVisitor());
passes.add(new TypeInference());
passes.add(new ConstInlinerVisitor());
passes.add(new FinishTypeInference());
if (args.isRawCFGOutput()) {
passes.add(new DotGraphVisitor(outDir, false, true));
if (args.isDebugInfo()) {
passes.add(new DebugInfoParseVisitor());
}
passes.add(new EliminatePhiNodes());
passes.add(new BlockSplitter());
if (args.isRawCFGOutput()) {
passes.add(DotGraphVisitor.dumpRaw());
}
passes.add(new BlockProcessor());
passes.add(new BlockExceptionHandler());
passes.add(new BlockFinish());
passes.add(new SSATransform());
passes.add(new ConstructorVisitor());
passes.add(new InitCodeVariables());
passes.add(new MarkFinallyVisitor());
passes.add(new ConstInlineVisitor());
passes.add(new TypeInferenceVisitor());
if (args.isDebugInfo()) {
passes.add(new DebugInfoApplyVisitor());
}
passes.add(new DeboxingVisitor());
passes.add(new ModVisitor());
passes.add(new EnumVisitor());
passes.add(new CodeShrinkVisitor());
passes.add(new ReSugarCode());
if (args.isCfgOutput()) {
passes.add(DotGraphVisitor.dump());
}
passes.add(new CodeShrinker());
passes.add(new RegionMakerVisitor());
passes.add(new IfRegionVisitor());
passes.add(new ReturnVisitor());
passes.add(new CleanRegions());
passes.add(new CodeShrinker());
passes.add(new CodeShrinkVisitor());
passes.add(new SimplifyVisitor());
passes.add(new ProcessVariables());
passes.add(new CheckRegions());
if (args.isCFGOutput()) {
passes.add(new DotGraphVisitor(outDir, true));
}
passes.add(new MethodInlineVisitor());
passes.add(new ExtractFieldInit());
passes.add(new FixAccessModifiers());
passes.add(new ProcessAnonymous());
passes.add(new ClassModifier());
passes.add(new PrepareForCodeGen());
passes.add(new MethodInlineVisitor());
passes.add(new EnumVisitor());
passes.add(new LoopRegionVisitor());
if (args.isCFGOutput()) {
passes.add(new DotGraphVisitor(outDir, false));
passes.add(new ProcessVariables());
passes.add(new PrepareForCodeGen());
if (args.isCfgOutput()) {
passes.add(DotGraphVisitor.dumpRegions());
}
passes.add(new DependencyCollector());
passes.add(new RenameVisitor());
}
passes.add(new CodeGen(args));
return passes;
}
public static String getVersion() {
try {
ClassLoader classLoader = Utils.class.getClassLoader();
ClassLoader classLoader = Jadx.class.getClassLoader();
if (classLoader != null) {
Enumeration<URL> resources = classLoader.getResources("META-INF/MANIFEST.MF");
while (resources.hasMoreElements()) {
Manifest manifest = new Manifest(resources.nextElement().openStream());
String ver = manifest.getMainAttributes().getValue("jadx-version");
if (ver != null) {
return ver;
try (InputStream is = resources.nextElement().openStream()) {
Manifest manifest = new Manifest(is);
String ver = manifest.getMainAttributes().getValue("jadx-version");
if (ver != null) {
return ver;
}
}
}
}
@@ -1,30 +1,68 @@
package jadx.core;
import org.jetbrains.annotations.NotNull;
import jadx.api.ICodeInfo;
import jadx.core.codegen.CodeGen;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.visitors.DepthTraversal;
import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.exceptions.JadxRuntimeException;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static jadx.core.dex.nodes.ProcessState.LOADED;
import static jadx.core.dex.nodes.ProcessState.NOT_LOADED;
import static jadx.core.dex.nodes.ProcessState.PROCESS_COMPLETE;
import static jadx.core.dex.nodes.ProcessState.PROCESS_STARTED;
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) {
ClassNode topParentClass = cls.getTopParentClass();
if (topParentClass != cls) {
process(topParentClass);
return;
}
if (cls.getState().isProcessed()) {
// nothing to do
return;
}
synchronized (cls.getClassInfo()) {
try {
if (cls.getState() == NOT_LOADED) {
cls.load();
}
if (cls.getState() == LOADED) {
cls.setState(PROCESS_STARTED);
for (IDexTreeVisitor visitor : cls.root().getPasses()) {
DepthTraversal.visit(visitor, cls);
}
cls.setState(PROCESS_COMPLETE);
}
} catch (Throwable e) {
ErrorsCounter.classError(cls, e.getClass().getSimpleName(), e);
}
} catch (Exception e) {
LOG.error("Class process exception: " + cls, e);
} finally {
}
}
@NotNull
public static ICodeInfo generateCode(ClassNode cls) {
ClassNode topParentClass = cls.getTopParentClass();
if (topParentClass != cls) {
return generateCode(topParentClass);
}
try {
process(cls);
cls.getDependencies().forEach(ProcessClass::process);
ICodeInfo code = CodeGen.generate(cls);
cls.unload();
return code;
} catch (Throwable e) {
throw new JadxRuntimeException("Failed to generate code for class: " + cls.getFullName(), e);
}
}
}
@@ -1,22 +1,18 @@
package jadx.core.clsp;
import jadx.core.dex.info.ClassInfo;
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 java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -27,6 +23,17 @@ import java.util.zip.ZipOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.GenericInfo;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.FileUtils;
import jadx.core.utils.files.ZipSecurity;
/**
* Classes list for import into classpath graph
*/
@@ -38,24 +45,42 @@ public class ClsSet {
private static final String CLST_PKG_PATH = ClsSet.class.getPackage().getName().replace('.', '/');
private static final String JADX_CLS_SET_HEADER = "jadx-cst";
private static final int VERSION = 1;
private static final int VERSION = 2;
private static final String STRING_CHARSET = "US-ASCII";
private static final NClass[] EMPTY_NCLASS_ARRAY = new NClass[0];
private enum TypeEnum {
WILDCARD, GENERIC, GENERIC_TYPE, OBJECT, ARRAY, PRIMITIVE
}
private NClass[] classes;
public void load(RootNode root) {
public void loadFromClstFile() throws IOException, DecodeException {
try (InputStream input = getClass().getResourceAsStream(CLST_FILENAME)) {
if (input == null) {
throw new JadxRuntimeException("Can't load classpath file: " + CLST_FILENAME);
}
load(input);
}
}
public void loadFrom(RootNode root) {
List<ClassNode> list = root.getClasses(true);
Map<String, NClass> names = new HashMap<String, NClass>(list.size());
Map<String, NClass> names = new HashMap<>(list.size());
int k = 0;
for (ClassNode cls : list) {
String clsRawName = cls.getRawName();
if (cls.getAccessFlags().isPublic()) {
cls.load();
NClass nClass = new NClass(clsRawName, k);
if (names.put(clsRawName, nClass) != null) {
throw new JadxRuntimeException("Duplicate class: " + clsRawName);
}
k++;
nClass.setGenerics(cls.getGenerics());
nClass.setMethods(getMethodsDetails(cls));
} else {
names.put(clsRawName, null);
}
@@ -75,122 +100,237 @@ public class ClsSet {
}
}
private List<NMethod> getMethodsDetails(ClassNode cls) {
List<NMethod> methods = new ArrayList<>();
for (MethodNode m : cls.getMethods()) {
AccessInfo accessFlags = m.getAccessFlags();
if (accessFlags.isPublic() || accessFlags.isProtected()) {
processMethodDetails(methods, m, accessFlags);
}
}
return methods;
}
private void processMethodDetails(List<NMethod> methods, MethodNode mth, AccessInfo accessFlags) {
List<ArgType> args = mth.getArgTypes();
boolean genericArg = false;
ArgType[] genericArgs;
if (args.isEmpty()) {
genericArgs = null;
} else {
int argsCount = args.size();
genericArgs = new ArgType[argsCount];
for (int i = 0; i < argsCount; i++) {
ArgType argType = args.get(i);
if (argType.isGeneric() || argType.isGenericType()) {
genericArgs[i] = argType;
genericArg = true;
}
}
}
ArgType retType = mth.getReturnType();
if (!retType.isGeneric() && !retType.isGenericType()) {
retType = null;
}
boolean varArgs = accessFlags.isVarArgs();
if (genericArg || retType != null || varArgs) {
methods.add(new NMethod(mth.getMethodInfo().getShortId(), genericArgs, retType, varArgs));
}
}
public static NClass[] makeParentsArray(ClassNode cls, Map<String, NClass> names) {
List<NClass> parents = new ArrayList<NClass>(1 + cls.getInterfaces().size());
ClassInfo superClass = cls.getSuperClass();
List<NClass> parents = new ArrayList<>(1 + cls.getInterfaces().size());
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);
}
}
return parents.toArray(new NClass[parents.size()]);
int size = parents.size();
if (size == 0) {
return EMPTY_NCLASS_ARRAY;
}
return parents.toArray(new NClass[size]);
}
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);
NClass cls = names.get(fullName);
if (cls == null) {
LOG.debug("Class not found: {}", fullName);
}
return id;
return cls;
}
void save(File output) throws IOException {
Utils.makeDirsForFile(output);
BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(output));
try {
String outputName = output.getName();
if (outputName.endsWith(CLST_EXTENSION)) {
void save(Path path) throws IOException {
FileUtils.makeDirsForFile(path);
String outputName = path.getFileName().toString();
if (outputName.endsWith(CLST_EXTENSION)) {
try (BufferedOutputStream outputStream = new BufferedOutputStream(Files.newOutputStream(path))) {
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();
}
} else {
throw new JadxRuntimeException("Unknown file format: " + outputName);
}
} finally {
outputStream.close();
} else if (outputName.endsWith(".jar")) {
Path temp = FileUtils.createTempFile(".zip");
Files.copy(path, temp, StandardCopyOption.REPLACE_EXISTING);
try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(path));
ZipInputStream in = new ZipInputStream(Files.newInputStream(temp))) {
String clst = CLST_PKG_PATH + '/' + CLST_FILENAME;
out.putNextEntry(new ZipEntry(clst));
save(out);
ZipEntry entry = in.getNextEntry();
while (entry != null) {
if (!entry.getName().equals(clst)) {
out.putNextEntry(new ZipEntry(entry.getName()));
FileUtils.copyStream(in, out);
}
entry = in.getNextEntry();
}
}
} else {
throw new JadxRuntimeException("Unknown file format: " + outputName);
}
}
public void save(OutputStream output) throws IOException {
DataOutputStream out = new DataOutputStream(output);
try {
out.writeBytes(JADX_CLS_SET_HEADER);
out.writeByte(VERSION);
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());
LOG.info("Classes count: {}", classes.length);
Map<String, NClass> names = new HashMap<>(classes.length);
out.writeInt(classes.length);
for (NClass cls : classes) {
writeString(out, cls.getName());
names.put(cls.getName(), cls);
}
for (NClass cls : classes) {
NClass[] parents = cls.getParents();
out.writeByte(parents.length);
for (NClass parent : parents) {
out.writeInt(parent.getId());
}
for (NClass cls : classes) {
NClass[] parents = cls.getParents();
out.writeByte(parents.length);
for (NClass parent : parents) {
out.writeInt(parent.getId());
writeGenerics(out, cls, names);
List<NMethod> methods = cls.getMethodsList();
out.writeByte(methods.size());
for (NMethod method : methods) {
writeMethod(out, method, names);
}
}
}
private static void writeGenerics(DataOutputStream out, NClass cls, Map<String, NClass> names) throws IOException {
List<GenericInfo> genericsList = cls.getGenerics();
out.writeByte(genericsList.size());
for (GenericInfo genericInfo : genericsList) {
writeArgType(out, genericInfo.getGenericType(), names);
List<ArgType> extendsList = genericInfo.getExtendsList();
out.writeByte(extendsList.size());
for (ArgType type : extendsList) {
writeArgType(out, type, names);
}
}
}
private static void writeMethod(DataOutputStream out, NMethod method, Map<String, NClass> names) throws IOException {
writeLongString(out, method.getShortId());
ArgType[] argTypes = method.getGenericArgs();
if (argTypes == null) {
out.writeByte(0);
} else {
int argCount = 0;
for (ArgType arg : argTypes) {
if (arg != null) {
argCount++;
}
}
} finally {
out.close();
out.writeByte(argCount);
// last argument first
for (int i = argTypes.length - 1; i >= 0; i--) {
ArgType argType = argTypes[i];
if (argType != null) {
out.writeByte(i);
writeArgType(out, argType, names);
}
}
}
if (method.getReturnType() == null) {
out.writeBoolean(false);
} else {
out.writeBoolean(true);
writeArgType(out, method.getReturnType(), names);
}
out.writeBoolean(method.isVarArgs());
}
private static void writeArgType(DataOutputStream out, ArgType argType, Map<String, NClass> names) throws IOException {
if (argType.getWildcardType() != null) {
out.writeByte(TypeEnum.WILDCARD.ordinal());
ArgType.WildcardBound bound = argType.getWildcardBound();
out.writeByte(bound.getNum());
if (bound != ArgType.WildcardBound.UNBOUND) {
writeArgType(out, argType.getWildcardType(), names);
}
} else if (argType.isGeneric()) {
out.writeByte(TypeEnum.GENERIC.ordinal());
out.writeInt(names.get(argType.getObject()).getId());
ArgType[] types = argType.getGenericTypes();
if (types == null) {
out.writeByte(0);
} else {
out.writeByte(types.length);
for (ArgType type : types) {
writeArgType(out, type, names);
}
}
} else if (argType.isGenericType()) {
out.writeByte(TypeEnum.GENERIC_TYPE.ordinal());
writeString(out, argType.getObject());
} else if (argType.isObject()) {
out.writeByte(TypeEnum.OBJECT.ordinal());
out.writeInt(names.get(argType.getObject()).getId());
} else if (argType.isArray()) {
out.writeByte(TypeEnum.ARRAY.ordinal());
writeArgType(out, argType.getArrayElement(), names);
} else if (argType.isPrimitive()) {
out.writeByte(TypeEnum.PRIMITIVE.ordinal());
out.writeByte(argType.getPrimitiveType().getShortName().charAt(0));
} else {
throw new JadxRuntimeException("Cannot save type: " + argType);
}
}
public void load() throws IOException, DecodeException {
InputStream input = getClass().getResourceAsStream(CLST_FILENAME);
if (input == null) {
throw new JadxRuntimeException("Can't load classpath file: " + CLST_FILENAME);
}
try {
load(input);
} finally {
input.close();
}
}
public void load(File input) throws IOException, DecodeException {
private void load(File input) throws IOException, DecodeException {
String name = input.getName();
InputStream inputStream = new FileInputStream(input);
try {
try (InputStream inputStream = new FileInputStream(input)) {
if (name.endsWith(CLST_EXTENSION)) {
load(inputStream);
} else if (name.endsWith(".jar")) {
ZipInputStream in = new ZipInputStream(inputStream);
try {
try (ZipInputStream in = new ZipInputStream(inputStream)) {
ZipEntry entry = in.getNextEntry();
while (entry != null) {
if (entry.getName().endsWith(CLST_EXTENSION)) {
if (entry.getName().endsWith(CLST_EXTENSION) && ZipSecurity.isValidZipEntry(entry)) {
load(in);
}
entry = in.getNextEntry();
}
} finally {
in.close();
}
} else {
throw new JadxRuntimeException("Unknown file format: " + name);
}
} finally {
inputStream.close();
}
}
public void load(InputStream input) throws IOException, DecodeException {
DataInputStream in = new DataInputStream(input);
try {
private void load(InputStream input) throws IOException, DecodeException {
try (DataInputStream in = new DataInputStream(input)) {
byte[] header = new byte[JADX_CLS_SET_HEADER.length()];
int readHeaderLength = in.read(header);
int version = in.readByte();
@@ -211,21 +351,127 @@ public class ClsSet {
for (int j = 0; j < pCount; j++) {
parents[j] = classes[in.readInt()];
}
classes[i].setParents(parents);
NClass nClass = classes[i];
nClass.setParents(parents);
nClass.setGenerics(readGenerics(in));
nClass.setMethods(readClsMethods(in));
}
} finally {
in.close();
}
}
private void writeString(DataOutputStream out, String name) throws IOException {
private List<GenericInfo> readGenerics(DataInputStream in) throws IOException {
int count = in.readByte();
if (count == 0) {
return Collections.emptyList();
}
List<GenericInfo> list = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
ArgType genericType = readArgType(in);
List<ArgType> extendsList;
byte extCount = in.readByte();
if (extCount == 0) {
extendsList = Collections.emptyList();
} else {
extendsList = new ArrayList<>(extCount);
for (int j = 0; j < extCount; j++) {
extendsList.add(readArgType(in));
}
}
list.add(new GenericInfo(genericType, extendsList));
}
return list;
}
private List<NMethod> readClsMethods(DataInputStream in) throws IOException {
int mCount = in.readByte();
List<NMethod> methods = new ArrayList<>(mCount);
for (int j = 0; j < mCount; j++) {
methods.add(readMethod(in));
}
return methods;
}
private NMethod readMethod(DataInputStream in) throws IOException {
String shortId = readLongString(in);
int argCount = in.readByte();
ArgType[] argTypes = null;
for (int i = 0; i < argCount; i++) {
int index = in.readByte();
ArgType argType = readArgType(in);
if (argTypes == null) {
argTypes = new ArgType[index + 1];
}
argTypes[index] = argType;
}
ArgType retType = in.readBoolean() ? readArgType(in) : null;
boolean varArgs = in.readBoolean();
return new NMethod(shortId, argTypes, retType, varArgs);
}
private ArgType readArgType(DataInputStream in) throws IOException {
int ordinal = in.readByte();
switch (TypeEnum.values()[ordinal]) {
case WILDCARD:
int bounds = in.readByte();
return bounds == 0
? ArgType.wildcard()
: ArgType.wildcard(readArgType(in), ArgType.WildcardBound.getByNum(bounds));
case GENERIC:
String obj = classes[in.readInt()].getName();
int typeLength = in.readByte();
ArgType[] generics;
if (typeLength == 0) {
generics = null;
} else {
generics = new ArgType[typeLength];
for (int i = 0; i < typeLength; i++) {
generics[i] = readArgType(in);
}
}
return ArgType.generic(obj, generics);
case GENERIC_TYPE:
return ArgType.genericType(readString(in));
case OBJECT:
return ArgType.object(classes[in.readInt()].getName());
case ARRAY:
return ArgType.array(readArgType(in));
case PRIMITIVE:
char shortName = (char) in.readByte();
return ArgType.parse(shortName);
default:
throw new JadxRuntimeException("Unsupported Arg Type: " + ordinal);
}
}
private static void writeString(DataOutputStream out, String name) throws IOException {
byte[] bytes = name.getBytes(STRING_CHARSET);
out.writeByte(bytes.length);
out.write(bytes);
}
private static void writeLongString(DataOutputStream out, String name) throws IOException {
byte[] bytes = name.getBytes(STRING_CHARSET);
out.writeShort(bytes.length);
out.write(bytes);
}
private static String readString(DataInputStream in) throws IOException {
int len = in.readByte();
return readString(in, len);
}
private static String readLongString(DataInputStream in) throws IOException {
int len = in.readShort();
return readString(in, len);
}
private static String readString(DataInputStream in, int len) throws IOException {
byte[] bytes = new byte[len];
int count = in.read(bytes);
while (count != len) {
@@ -1,10 +1,7 @@
package jadx.core.clsp;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -13,27 +10,36 @@ import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxRuntimeException;
/**
* Classes hierarchy graph
* Classes hierarchy graph with methods additional info
*/
public class ClspGraph {
private static final Logger LOG = LoggerFactory.getLogger(ClspGraph.class);
private final Map<String, Set<String>> ancestorCache = new WeakHashMap<String, Set<String>>();
private final Map<String, Set<String>> ancestorCache = Collections.synchronizedMap(new WeakHashMap<>());
private Map<String, NClass> nameMap;
private final Set<String> missingClasses = new HashSet<>();
public void load() throws IOException, DecodeException {
ClsSet set = new ClsSet();
set.load();
set.loadFromClstFile();
addClasspath(set);
}
public void addClasspath(ClsSet set) {
if (nameMap == null) {
nameMap = new HashMap<String, NClass>(set.getClassesCount());
nameMap = new HashMap<>(set.getClassesCount());
set.addToMap(nameMap);
} else {
throw new JadxRuntimeException("Classpath already loaded");
@@ -45,47 +51,72 @@ 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));
}
}
public boolean isClsKnown(String fullName) {
return nameMap.containsKey(fullName);
}
public NClass getClsDetails(ArgType type) {
return nameMap.get(type.getObject());
}
@Nullable
public NMethod getMethodDetails(MethodInfo methodInfo) {
NClass cls = nameMap.get(methodInfo.getDeclClass().getRawName());
if (cls == null) {
return null;
}
return cls.getMethodsMap().get(methodInfo.getShortId());
}
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;
}
/**
* @return {@code clsName} instanceof {@code implClsName}
*/
public boolean isImplements(String clsName, String implClsName) {
Set<String> anc = getAncestors(clsName);
return anc.contains(implClsName);
}
public List<String> getImplementations(String clsName) {
List<String> list = new ArrayList<>();
for (String cls : nameMap.keySet()) {
if (isImplements(cls, clsName)) {
list.add(cls);
}
}
return list;
}
public String getCommonAncestor(String clsName, String implClsName) {
if (clsName.equals(implClsName)) {
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,39 +124,55 @@ 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;
}
private Set<String> getAncestors(String clsName) {
public Set<String> getAncestors(String clsName) {
Set<String> result = ancestorCache.get(clsName);
if (result != null) {
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<>();
addAncestorsNames(cls, result);
if (result.isEmpty()) {
result = Collections.emptySet();
}
ancestorCache.put(clsName, result);
return result;
}
private void addAncestorsNames(NClass cls, Set<String> result) {
result.add(cls.getName());
for (NClass p : cls.getParents()) {
addAncestorsNames(p, result);
boolean isNew = result.add(cls.getName());
if (isNew) {
for (NClass p : cls.getParents()) {
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<>(missingClasses);
Collections.sort(clsNames);
for (String cls : clsNames) {
LOG.debug(" {}", cls);
}
}
}
}
@@ -1,17 +1,20 @@
package jadx.core.clsp;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.files.InputFile;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.files.InputFile;
/**
* Utility class for convert dex or jar to jadx classes set (.jcst)
*/
@@ -27,32 +30,32 @@ public class ConvertToClsSet {
usage();
System.exit(1);
}
File output = new File(args[0]);
Path output = Paths.get(args[0]);
List<InputFile> inputFiles = new ArrayList<InputFile>(args.length - 1);
List<InputFile> inputFiles = new ArrayList<>(args.length - 1);
for (int i = 1; i < args.length; i++) {
File f = new File(args[i]);
if (f.isDirectory()) {
addFilesFromDirectory(f, inputFiles);
} else {
inputFiles.add(new InputFile(f));
InputFile.addFilesFrom(f, inputFiles, false);
}
}
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.loadFrom(root);
set.save(output);
LOG.info("Output: " + output);
LOG.info("Output: {}", output);
LOG.info("done");
}
private static void addFilesFromDirectory(File dir, List<InputFile> inputFiles) throws IOException, DecodeException {
private static void addFilesFromDirectory(File dir, List<InputFile> inputFiles) {
File[] files = dir.listFiles();
if (files == null) {
return;
@@ -60,11 +63,13 @@ public class ConvertToClsSet {
for (File file : files) {
if (file.isDirectory()) {
addFilesFromDirectory(file, inputFiles);
}
if (file.getName().endsWith(".dex")) {
inputFiles.add(new InputFile(file));
} else {
try {
InputFile.addFilesFrom(file, inputFiles, false);
} catch (Exception e) {
LOG.warn("Skip file: {}, load error: {}", file, e.getMessage());
}
}
}
}
}
@@ -1,13 +1,24 @@
package jadx.core.clsp;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import jadx.core.dex.nodes.GenericInfo;
/**
* Class node in classpath graph
*/
public class NClass {
private final String name;
private final int id;
private NClass[] parents;
private int id;
private Map<String, NMethod> methodsMap = Collections.emptyMap();
private List<GenericInfo> generics = Collections.emptyList();
public NClass(String name, int id) {
this.name = name;
@@ -22,10 +33,6 @@ public class NClass {
return id;
}
public void setId(int id) {
this.id = id;
}
public NClass[] getParents() {
return parents;
}
@@ -34,6 +41,37 @@ public class NClass {
this.parents = parents;
}
public Map<String, NMethod> getMethodsMap() {
return methodsMap;
}
public List<NMethod> getMethodsList() {
List<NMethod> list = new ArrayList<>(methodsMap.size());
list.addAll(methodsMap.values());
Collections.sort(list);
return list;
}
public void setMethodsMap(Map<String, NMethod> methodsMap) {
this.methodsMap = Objects.requireNonNull(methodsMap);
}
public void setMethods(List<NMethod> methods) {
Map<String, NMethod> map = new HashMap<>(methods.size());
for (NMethod mth : methods) {
map.put(mth.getShortId(), mth);
}
setMethodsMap(map);
}
public List<GenericInfo> getGenerics() {
return generics;
}
public void setGenerics(List<GenericInfo> generics) {
this.generics = generics;
}
@Override
public int hashCode() {
return name.hashCode();
@@ -0,0 +1,92 @@
package jadx.core.clsp;
import java.util.Arrays;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import jadx.core.dex.instructions.args.ArgType;
/**
* Generic method node in classpath graph.
*/
public class NMethod implements Comparable<NMethod> {
private final String shortId;
/**
* Array contains only generic args, others set to 'null', size can be less than total args count
*/
@Nullable
private final ArgType[] genericArgs;
@Nullable
private final ArgType retType;
private final boolean varArgs;
public NMethod(String shortId, @Nullable ArgType[] genericArgs, @Nullable ArgType retType, boolean varArgs) {
this.shortId = shortId;
this.genericArgs = genericArgs;
this.retType = retType;
this.varArgs = varArgs;
}
public String getShortId() {
return shortId;
}
@Nullable
public ArgType[] getGenericArgs() {
return genericArgs;
}
@Nullable
public ArgType getGenericArg(int i) {
ArgType[] args = this.genericArgs;
if (args != null && i < args.length) {
return args[i];
}
return null;
}
@Nullable
public ArgType getReturnType() {
return retType;
}
public boolean isVarArgs() {
return varArgs;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof NMethod)) {
return false;
}
NMethod other = (NMethod) o;
return shortId.equals(other.shortId);
}
@Override
public int hashCode() {
return shortId.hashCode();
}
@Override
public int compareTo(@NotNull NMethod other) {
return this.shortId.compareTo(other.shortId);
}
@Override
public String toString() {
return "NMethod{'" + shortId + '\''
+ ", argTypes=" + Arrays.toString(genericArgs)
+ ", retType=" + retType
+ ", varArgs=" + varArgs
+ '}';
}
}
@@ -1,5 +1,12 @@
package jadx.core.codegen;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.jetbrains.annotations.Nullable;
import jadx.core.Consts;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttributeNode;
@@ -14,11 +21,6 @@ import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.StringUtils;
import jadx.core.utils.exceptions.JadxRuntimeException;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
public class AnnotationGen {
private final ClassNode cls;
@@ -42,8 +44,12 @@ public class AnnotationGen {
}
public void addForParameter(CodeWriter code, MethodParameters paramsAnnotations, int n) {
AnnotationsList aList = paramsAnnotations.getParamList().get(n);
if (aList == null || aList.size() == 0) {
List<AnnotationsList> paramList = paramsAnnotations.getParamList();
if (n >= paramList.size()) {
return;
}
AnnotationsList aList = paramList.get(n);
if (aList == null || aList.isEmpty()) {
return;
}
for (Annotation a : aList.getAll()) {
@@ -54,17 +60,12 @@ public class AnnotationGen {
private void add(IAttributeNode node, CodeWriter code) {
AnnotationsList aList = node.get(AType.ANNOTATION_LIST);
if (aList == null || aList.size() == 0) {
if (aList == null || aList.isEmpty()) {
return;
}
for (Annotation a : aList.getAll()) {
String aCls = a.getAnnotationClass();
if (aCls.startsWith(Consts.DALVIK_ANNOTATION_PKG)) {
// skip
if (Consts.DEBUG) {
code.startLine("// " + a);
}
} else {
if (!aCls.startsWith(Consts.DALVIK_ANNOTATION_PKG)) {
code.startLine();
formatAnnotation(code, a);
}
@@ -73,34 +74,52 @@ public class AnnotationGen {
private void formatAnnotation(CodeWriter code, Annotation a) {
code.add('@');
classGen.useType(code, a.getType());
ClassNode annCls = cls.dex().resolveClass(a.getType());
if (annCls != null) {
classGen.useClass(code, annCls);
} else {
classGen.useType(code, a.getType());
}
Map<String, Object> vl = a.getValues();
if (!vl.isEmpty()) {
code.add('(');
if (vl.size() == 1 && vl.containsKey("value")) {
encodeValue(code, vl.get("value"));
} else {
for (Iterator<Entry<String, Object>> it = vl.entrySet().iterator(); it.hasNext(); ) {
Entry<String, Object> e = it.next();
code.add(e.getKey());
for (Iterator<Entry<String, Object>> it = vl.entrySet().iterator(); it.hasNext();) {
Entry<String, Object> e = it.next();
String paramName = getParamName(annCls, e.getKey());
if (paramName.equals("value") && vl.size() == 1) {
// don't add "value = " if no other parameters
} else {
code.add(paramName);
code.add(" = ");
encodeValue(code, e.getValue());
if (it.hasNext()) {
code.add(", ");
}
}
encodeValue(code, e.getValue());
if (it.hasNext()) {
code.add(", ");
}
}
code.add(')');
}
}
private String getParamName(@Nullable ClassNode annCls, String paramName) {
if (annCls != null) {
// TODO: save value type and search using signature
MethodNode mth = annCls.searchMethodByShortName(paramName);
if (mth != null) {
return mth.getAlias();
}
}
return paramName;
}
@SuppressWarnings("unchecked")
public void addThrows(MethodNode mth, CodeWriter code) {
Annotation an = mth.getAnnotation(Consts.DALVIK_THROWS);
if (an != null) {
Object exs = an.getDefaultValue();
code.add(" throws ");
for (Iterator<ArgType> it = ((List<ArgType>) exs).iterator(); it.hasNext(); ) {
for (Iterator<ArgType> it = ((List<ArgType>) exs).iterator(); it.hasNext();) {
ArgType ex = it.next();
classGen.useType(code, ex);
if (it.hasNext()) {
@@ -126,11 +145,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));
code.add(TypeGen.formatInteger((Integer) val, false));
} 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) {
@@ -138,11 +157,11 @@ public class AnnotationGen {
} else if (val instanceof Double) {
code.add(TypeGen.formatDouble((Double) val));
} else if (val instanceof Long) {
code.add(TypeGen.formatLong((Long) val));
code.add(TypeGen.formatLong((Long) val, false));
} else if (val instanceof Short) {
code.add(TypeGen.formatShort((Short) val));
code.add(TypeGen.formatShort((Short) val, false));
} else if (val instanceof Byte) {
code.add(TypeGen.formatByte((Byte) val));
code.add(TypeGen.formatByte((Byte) val, false));
} else if (val instanceof ArgType) {
classGen.useType(code, (ArgType) val);
code.add(".class");
@@ -150,9 +169,9 @@ public class AnnotationGen {
// must be a static field
FieldInfo field = (FieldInfo) val;
InsnGen.makeStaticFieldAccess(code, field, classGen);
} else if (val instanceof List) {
} else if (val instanceof Iterable) {
code.add('{');
Iterator<?> it = ((List) val).iterator();
Iterator<?> it = ((Iterable<?>) val).iterator();
while (it.hasNext()) {
Object obj = it.next();
encodeValue(code, obj);
@@ -165,7 +184,11 @@ public class AnnotationGen {
formatAnnotation(code, (Annotation) val);
} else {
// TODO: also can be method values
throw new JadxRuntimeException("Can't decode value: " + val + " (" + val.getClass() + ")");
throw new JadxRuntimeException("Can't decode value: " + val + " (" + val.getClass() + ')');
}
}
private StringUtils getStringUtils() {
return cls.dex().root().getStringUtils();
}
}
@@ -1,55 +1,70 @@
package jadx.core.codegen;
import jadx.core.Consts;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import com.android.dx.rop.code.AccessFlags;
import com.google.common.collect.Streams;
import jadx.api.JadxArgs;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.AttrNode;
import jadx.core.dex.attributes.nodes.EnumClassAttr;
import jadx.core.dex.attributes.nodes.EnumClassAttr.EnumField;
import jadx.core.dex.attributes.nodes.SourceFileAttr;
import jadx.core.dex.attributes.nodes.JadxError;
import jadx.core.dex.attributes.nodes.LineAttrNode;
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.GenericInfo;
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.CodeGenUtils;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.CodegenException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.android.dx.rop.code.AccessFlags;
import jadx.core.utils.exceptions.JadxRuntimeException;
public class ClassGen {
private static final Logger LOG = LoggerFactory.getLogger(ClassGen.class);
private final ClassNode cls;
private final ClassGen parentGen;
private final AnnotationGen annotationGen;
private final boolean fallback;
private final boolean useImports;
private final boolean showInconsistentCode;
private final Set<ClassInfo> imports = new HashSet<ClassInfo>();
private int clsDeclLine = 0;
private final Set<ClassInfo> imports = new HashSet<>();
private int clsDeclLine;
public ClassGen(ClassNode cls, ClassGen parentClsGen, boolean fallback) {
public ClassGen(ClassNode cls, JadxArgs jadxArgs) {
this(cls, null, jadxArgs.isUseImports(), jadxArgs.isFallbackMode(), jadxArgs.isShowInconsistentCode());
}
public ClassGen(ClassNode cls, ClassGen parentClsGen) {
this(cls, parentClsGen, parentClsGen.useImports, parentClsGen.fallback, parentClsGen.showInconsistentCode);
}
public ClassGen(ClassNode cls, ClassGen parentClsGen, boolean useImports, boolean fallback, boolean showBadCode) {
this.cls = cls;
this.parentGen = parentClsGen;
this.fallback = fallback;
this.useImports = useImports;
this.showInconsistentCode = showBadCode;
this.annotationGen = new AnnotationGen(cls, this);
}
@@ -69,31 +84,30 @@ public class ClassGen {
}
int importsCount = imports.size();
if (importsCount != 0) {
List<String> sortImports = new ArrayList<String>(importsCount);
for (ClassInfo ic : imports) {
sortImports.add(ic.getFullName());
}
Collections.sort(sortImports);
for (String imp : sortImports) {
clsCode.startLine("import ").add(imp).add(';');
}
List<ClassInfo> sortedImports = new ArrayList<>(imports);
sortedImports.sort(Comparator.comparing(ClassInfo::getAliasFullName));
sortedImports.forEach(classInfo -> {
clsCode.startLine("import ");
ClassNode classNode = cls.root().resolveClass(classInfo);
if (classNode != null) {
clsCode.attachAnnotation(classNode);
}
clsCode.add(classInfo.getAliasFullName());
clsCode.add(';');
});
clsCode.newLine();
sortImports.clear();
imports.clear();
}
clsCode.add(clsBody);
return clsCode;
return clsCode.finish();
}
public void addClassCode(CodeWriter code) throws CodegenException {
if (cls.contains(AFlag.DONT_GENERATE)) {
return;
}
if (cls.contains(AFlag.INCONSISTENT_CODE)) {
code.startLine("// jadx: inconsistent code");
}
CodeGenUtils.addComments(code, cls);
insertDecompilationProblems(code, cls);
addClassDeclaration(code);
addClassBody(code);
}
@@ -101,14 +115,24 @@ 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.getClassInfo().isInner()) {
af = af.remove(AccessFlags.ACC_STATIC).remove(AccessFlags.ACC_PRIVATE);
}
annotationGen.addForClass(clsCode);
insertSourceFileInfo(clsCode, cls);
clsCode.startLine(af.makeString());
insertRenameInfo(clsCode, cls);
CodeGenUtils.addSourceFileInfo(clsCode, cls);
clsCode.startLineWithNum(cls.getSourceLine());
clsCode.add(af.makeString());
if (af.isInterface()) {
if (af.isAnnotation()) {
clsCode.add('@');
@@ -119,28 +143,29 @@ public class ClassGen {
} else {
clsCode.add("class ");
}
clsCode.add(cls.getShortName());
clsCode.attachDefinition(cls);
clsCode.add(cls.getClassInfo().getAliasShortName());
addGenericMap(clsCode, cls.getGenericMap());
addGenericMap(clsCode, cls.getGenerics(), true);
clsCode.add(' ');
ClassInfo sup = cls.getSuperClass();
ArgType sup = cls.getSuperClass();
if (sup != null
&& !sup.getFullName().equals(Consts.CLASS_OBJECT)
&& !sup.getFullName().equals(Consts.CLASS_ENUM)) {
&& !sup.equals(ArgType.OBJECT)
&& !cls.isEnum()) {
clsCode.add("extends ");
useClass(clsCode, sup);
clsCode.add(' ');
}
if (cls.getInterfaces().size() > 0 && !af.isAnnotation()) {
if (!cls.getInterfaces().isEmpty() && !af.isAnnotation()) {
if (cls.getAccessFlags().isInterface()) {
clsCode.add("extends ");
} else {
clsCode.add("implements ");
}
for (Iterator<ClassInfo> it = cls.getInterfaces().iterator(); it.hasNext(); ) {
ClassInfo interf = it.next();
for (Iterator<ArgType> it = cls.getInterfaces().iterator(); it.hasNext();) {
ArgType interf = it.next();
useClass(clsCode, interf);
if (it.hasNext()) {
clsCode.add(", ");
@@ -150,34 +175,38 @@ public class ClassGen {
clsCode.add(' ');
}
}
clsCode.attachDefinition(cls);
}
public boolean addGenericMap(CodeWriter code, Map<ArgType, List<ArgType>> gmap) {
if (gmap == null || gmap.isEmpty()) {
public boolean addGenericMap(CodeWriter code, List<GenericInfo> generics, boolean classDeclaration) {
if (generics == null || generics.isEmpty()) {
return false;
}
code.add('<');
int i = 0;
for (Entry<ArgType, List<ArgType>> e : gmap.entrySet()) {
ArgType type = e.getKey();
List<ArgType> list = e.getValue();
for (GenericInfo genericInfo : generics) {
if (i != 0) {
code.add(", ");
}
ArgType type = genericInfo.getGenericType();
if (type.isGenericType()) {
code.add(type.getObject());
} else {
useClass(code, ClassInfo.fromType(type));
useClass(code, type);
}
List<ArgType> list = genericInfo.getExtendsList();
if (list != null && !list.isEmpty()) {
code.add(" extends ");
for (Iterator<ArgType> it = list.iterator(); it.hasNext(); ) {
for (Iterator<ArgType> it = list.iterator(); it.hasNext();) {
ArgType g = it.next();
if (g.isGenericType()) {
code.add(g.getObject());
} else {
useClass(code, ClassInfo.fromType(g));
useClass(code, g);
if (classDeclaration
&& !cls.getClassInfo().isInner()
&& cls.root().getArgs().isUseImports()) {
addImport(ClassInfo.fromType(cls.root(), g));
}
}
if (it.hasNext()) {
code.add(" & ");
@@ -195,93 +224,171 @@ public class ClassGen {
clsDeclLine = clsCode.getLine();
clsCode.incIndent();
addFields(clsCode);
addInnerClasses(clsCode, cls);
addMethods(clsCode);
addInnerClsAndMethods(clsCode);
clsCode.decIndent();
clsCode.startLine('}');
}
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());
}
private void addInnerClsAndMethods(CodeWriter clsCode) {
Streams.concat(cls.getInnerClasses().stream(), cls.getMethods().stream())
.filter(node -> !node.contains(AFlag.DONT_GENERATE))
.sorted(Comparator.comparingInt(LineAttrNode::getSourceLine))
.forEach(node -> {
if (node instanceof ClassNode) {
addInnerClass(clsCode, (ClassNode) node);
} else {
addMethod(clsCode, (MethodNode) node);
}
});
}
private void addInnerClass(CodeWriter code, ClassNode innerCls) {
try {
ClassGen inClGen = new ClassGen(innerCls, getParentGen());
code.newLine();
inClGen.addClassCode(code);
imports.addAll(inClGen.getImports());
} catch (Exception e) {
ErrorsCounter.classError(innerCls, "Inner class code generation error", e);
}
}
private void addMethods(CodeWriter code) {
private boolean isInnerClassesPresents() {
for (ClassNode innerCls : cls.getInnerClasses()) {
if (!innerCls.contains(AFlag.ANONYMOUS_CLASS)) {
return true;
}
}
return false;
}
private void addMethod(CodeWriter code, MethodNode mth) {
if (code.getLine() != clsDeclLine) {
code.newLine();
}
int savedIndent = code.getIndent();
try {
addMethodCode(code, mth);
} catch (Exception e) {
if (mth.getParentClass().getTopParentClass().contains(AFlag.RESTART_CODEGEN)) {
throw new JadxRuntimeException("Method generation error", e);
}
code.newLine().add("/*");
code.newLine().addMultiLine(ErrorsCounter.methodError(mth, "Method generation error", e));
Utils.appendStackTrace(code, e);
code.newLine().add("*/");
code.setIndent(savedIndent);
mth.addError("Method generation error: " + e.getMessage(), e);
}
}
private boolean isMethodsPresents() {
for (MethodNode mth : cls.getMethods()) {
if (!mth.contains(AFlag.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) + " */");
}
return true;
}
}
return false;
}
private void addMethod(CodeWriter code, MethodNode mth) throws CodegenException {
MethodGen mthGen = new MethodGen(this, mth);
public void addMethodCode(CodeWriter code, MethodNode mth) throws CodegenException {
CodeGenUtils.addComments(code, 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());
if (def != null) {
code.add(" default ");
annotationGen.encodeValue(code, def);
}
}
code.add(';');
} else {
insertDecompilationProblems(code, mth);
boolean badCode = mth.contains(AFlag.INCONSISTENT_CODE);
if (badCode) {
code.startLine("/* JADX WARNING: inconsistent code. */");
code.startLine("/* Code decompiled incorrectly, please refer to instructions dump. */");
ErrorsCounter.methodError(mth, "Inconsistent code");
if (badCode && showInconsistentCode) {
mth.remove(AFlag.INCONSISTENT_CODE);
badCode = false;
}
MethodGen mthGen;
if (badCode || fallback || mth.contains(AType.JADX_ERROR) || mth.getRegion() == null) {
mthGen = MethodGen.getFallbackMethodGen(mth);
} else {
mthGen = new MethodGen(this, mth);
}
if (mthGen.addDefinition(code)) {
code.add(' ');
}
code.add('{');
code.incIndent();
insertSourceFileInfo(code, mth);
mthGen.addInstructions(code);
code.decIndent();
code.startLine('}');
}
}
public void insertDecompilationProblems(CodeWriter code, AttrNode node) {
List<JadxError> errors = node.getAll(AType.JADX_ERROR);
if (!errors.isEmpty()) {
errors.stream().distinct().sorted().forEach(err -> {
code.startLine("/* JADX ERROR: ").add(err.getError());
Throwable cause = err.getCause();
if (cause != null) {
code.incIndent();
Utils.appendStackTrace(code, cause);
code.decIndent();
}
code.add("*/");
});
}
List<String> warns = node.getAll(AType.JADX_WARN);
if (!warns.isEmpty()) {
warns.stream().distinct().sorted()
.forEach(warn -> code.startLine("/* JADX WARNING: ").addMultiLine(warn).add(" */"));
}
}
private void addFields(CodeWriter code) throws CodegenException {
addEnumFields(code);
for (FieldNode f : cls.getFields()) {
if (f.contains(AFlag.DONT_GENERATE)) {
continue;
}
annotationGen.addForField(code, f);
code.startLine(f.getAccessFlags().makeString());
useType(code, f.getType());
code.add(' ');
code.add(f.getName());
FieldValueAttr fv = f.get(AType.FIELD_VALUE);
if (fv != null) {
code.add(" = ");
if (fv.getValue() == null) {
code.add(TypeGen.literalToString(0, f.getType()));
} else {
addField(code, f);
}
}
public void addField(CodeWriter code, FieldNode f) {
if (f.contains(AFlag.DONT_GENERATE)) {
return;
}
CodeGenUtils.addComments(code, f);
annotationGen.addForField(code, f);
if (f.getFieldInfo().isRenamed()) {
code.newLine();
CodeGenUtils.addRenamedComment(code, f, f.getName());
}
code.startLine(f.getAccessFlags().makeString());
useType(code, f.getType());
code.add(' ');
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(), cls, fallback));
} else {
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);
}
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 {
@@ -290,48 +397,58 @@ public class ClassGen {
return;
}
InsnGen igen = null;
for (Iterator<EnumField> it = enumFields.getFields().iterator(); it.hasNext(); ) {
for (Iterator<EnumField> it = enumFields.getFields().iterator(); it.hasNext();) {
EnumField f = it.next();
code.startLine(f.getName());
if (f.getArgs().size() != 0) {
code.add('(');
for (Iterator<InsnArg> aIt = f.getArgs().iterator(); aIt.hasNext(); ) {
InsnArg arg = aIt.next();
if (igen == null) {
// don't init mth gen if this is simple enum
MethodGen mthGen = new MethodGen(this, enumFields.getStaticMethod());
igen = new InsnGen(mthGen, false);
}
igen.addArg(code, arg);
if (aIt.hasNext()) {
code.add(", ");
}
code.startLine(f.getField().getAlias());
ConstructorInsn constrInsn = f.getConstrInsn();
if (constrInsn.getArgsCount() > f.getStartArg()) {
if (igen == null) {
igen = makeInsnGen(enumFields.getStaticMethod());
}
code.add(')');
MethodNode callMth = cls.dex().resolveMethod(constrInsn.getCallMth());
igen.generateMethodArguments(code, constrInsn, f.getStartArg(), callMth);
}
if (f.getCls() != null) {
new ClassGen(f.getCls(), this, fallback).addClassBody(code);
code.add(' ');
new ClassGen(f.getCls(), this).addClassBody(code);
}
if (it.hasNext()) {
code.add(',');
}
}
if (enumFields.getFields().isEmpty()) {
code.startLine();
if (isMethodsPresents() || isFieldsPresents() || isInnerClassesPresents()) {
if (enumFields.getFields().isEmpty()) {
code.startLine();
}
code.add(';');
if (isFieldsPresents()) {
code.startLine();
}
}
}
private InsnGen makeInsnGen(MethodNode mth) {
MethodGen mthGen = new MethodGen(this, mth);
return new InsnGen(mthGen, false);
}
private void addInsnBody(InsnGen insnGen, CodeWriter code, InsnNode insn) {
try {
insnGen.makeInsn(insn, code, InsnGen.Flags.BODY_ONLY_NOWRAP);
} catch (Exception e) {
ErrorsCounter.classError(cls, "Failed to generate init code", e);
}
code.add(';');
code.newLine();
}
public void useType(CodeWriter code, ArgType type) {
final PrimitiveType stype = type.getPrimitiveType();
PrimitiveType stype = type.getPrimitiveType();
if (stype == null) {
code.add(type.toString());
} else if (stype == PrimitiveType.OBJECT) {
if (type.isGenericType()) {
code.add(type.getObject());
} else {
useClass(code, ClassInfo.fromType(type));
useClass(code, type);
}
} else if (stype == PrimitiveType.ARRAY) {
useType(code, type.getArrayElement());
@@ -341,14 +458,17 @@ public class ClassGen {
}
}
public void useClass(CodeWriter code, ClassInfo classInfo) {
ClassNode classNode = cls.dex().resolveClass(classInfo);
if (classNode != null) {
code.attachAnnotation(classNode);
public void useClass(CodeWriter code, ArgType type) {
ArgType outerType = type.getOuterType();
if (outerType != null) {
useClass(code, outerType);
code.add('.');
useClass(code, type.getInnerType());
return;
}
String baseClass = useClassInternal(cls.getClassInfo(), classInfo);
ArgType[] generics = classInfo.getType().getGenericTypes();
code.add(baseClass);
useClass(code, ClassInfo.fromType(cls.root(), type));
ArgType[] generics = type.getGenericTypes();
if (generics != null) {
code.add('<');
int len = generics.length;
@@ -359,10 +479,9 @@ public class ClassGen {
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 ");
ArgType.WildcardBound bound = gt.getWildcardBound();
code.add(bound.getStr());
if (bound != ArgType.WildcardBound.UNBOUND) {
useType(code, wt);
}
} else {
@@ -373,48 +492,95 @@ public class ClassGen {
}
}
private String useClassInternal(ClassInfo useCls, ClassInfo classInfo) {
String fullName = classInfo.getFullName();
if (fallback) {
public void useClass(CodeWriter code, ClassInfo classInfo) {
ClassNode classNode = cls.dex().resolveClass(classInfo);
if (classNode != null) {
useClass(code, classNode);
} else {
addClsName(code, classInfo);
}
}
public void useClass(CodeWriter code, ClassNode classNode) {
code.attachAnnotation(classNode);
addClsName(code, classNode.getClassInfo());
}
private void addClsName(CodeWriter code, ClassInfo classInfo) {
String clsName = useClassInternal(cls.getClassInfo(), classInfo);
code.add(clsName);
}
private String useClassInternal(ClassInfo useCls, ClassInfo extClsInfo) {
String fullName = extClsInfo.getAliasFullName();
if (fallback || !useImports) {
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.getAliasShortName();
if (extClsInfo.getPackage().equals("java.lang") && extClsInfo.getParentClass() == null) {
return shortName;
}
if (isClassInnerFor(useCls, extClsInfo)) {
return shortName;
}
if (extClsInfo.isInner()) {
return expandInnerClassName(useCls, extClsInfo);
}
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.getAliasPkg().equals(useCls.getAliasPkg())) {
fullName = extClsInfo.getAliasNameWithoutPackage();
}
for (ClassInfo importCls : getImports()) {
if (!importCls.equals(extClsInfo)
&& importCls.getAliasShortName().equals(shortName)) {
if (extClsInfo.isInner()) {
String parent = useClassInternal(useCls, extClsInfo.getParentClass());
return parent + '.' + shortName;
} else {
return fullName;
}
}
}
addImport(extClsInfo);
return shortName;
}
private String expandInnerClassName(ClassInfo useCls, ClassInfo extClsInfo) {
List<ClassInfo> clsList = new ArrayList<>();
clsList.add(extClsInfo);
ClassInfo parentCls = extClsInfo.getParentClass();
boolean addImport = true;
while (parentCls != null) {
if (parentCls == useCls || isClassInnerFor(useCls, parentCls)) {
addImport = false;
break;
}
clsList.add(parentCls);
parentCls = parentCls.getParentClass();
}
Collections.reverse(clsList);
if (addImport) {
addImport(clsList.get(0));
}
return Utils.listToString(clsList, ".", ClassInfo::getAliasShortName);
}
private void addImport(ClassInfo classInfo) {
@@ -425,7 +591,7 @@ public class ClassGen {
}
}
private Set<ClassInfo> getImports() {
public Set<ClassInfo> getImports() {
if (parentGen != null) {
return parentGen.getImports();
} else {
@@ -433,36 +599,48 @@ 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();
return p.equals(parent) || isClassInnerFor(p, parent);
return Objects.equals(p, parent) || isClassInnerFor(p, parent);
}
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;
}
if (useCls.getShortName().equals(shortName)) {
String shortName = searchCls.getAliasShortName();
if (useCls.getAliasShortName().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.getFullName().equals(searchCls.getAliasFullName())) {
return true;
}
}
}
return searchCollision(dex, useCls.getParentClass(), shortName);
return searchCollision(dex, useCls.getParentClass(), searchCls);
}
private void insertSourceFileInfo(CodeWriter code, AttrNode node) {
SourceFileAttr sourceFileAttr = node.get(AType.SOURCE_FILE);
if (sourceFileAttr != null) {
code.startLine("// compiled from: ").add(sourceFileAttr.getFileName());
private void insertRenameInfo(CodeWriter code, ClassNode cls) {
ClassInfo classInfo = cls.getClassInfo();
if (classInfo.hasAlias()) {
CodeGenUtils.addRenamedComment(code, cls, classInfo.getType().getObject());
}
}
@@ -1,29 +1,61 @@
package jadx.core.codegen;
import jadx.api.IJadxArgs;
import java.util.concurrent.Callable;
import jadx.api.ICodeInfo;
import jadx.api.JadxArgs;
import jadx.core.codegen.json.JsonCodeGen;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.visitors.AbstractVisitor;
import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.JadxRuntimeException;
public class CodeGen extends AbstractVisitor {
public class CodeGen {
private final IJadxArgs args;
public static ICodeInfo generate(ClassNode cls) {
if (cls.contains(AFlag.DONT_GENERATE)) {
return CodeWriter.EMPTY;
}
JadxArgs args = cls.root().getArgs();
switch (args.getOutputFormat()) {
case JAVA:
return generateJavaCode(cls, args);
public CodeGen(IJadxArgs args) {
this.args = args;
case JSON:
return generateJson(cls);
default:
throw new JadxRuntimeException("Unknown output format");
}
}
@Override
public boolean visit(ClassNode cls) throws CodegenException {
ClassGen clsGen = new ClassGen(cls, null, isFallbackMode());
CodeWriter clsCode = clsGen.makeClass();
clsCode.finish();
cls.setCode(clsCode);
return false;
private static ICodeInfo generateJavaCode(ClassNode cls, JadxArgs args) {
ClassGen clsGen = new ClassGen(cls, args);
return wrapCodeGen(cls, clsGen::makeClass);
}
public boolean isFallbackMode() {
return args.isFallbackMode();
private static ICodeInfo generateJson(ClassNode cls) {
JsonCodeGen codeGen = new JsonCodeGen(cls);
String clsJson = wrapCodeGen(cls, codeGen::process);
return new CodeWriter(clsJson);
}
private static <R> R wrapCodeGen(ClassNode cls, Callable<R> codeGenFunc) {
try {
return codeGenFunc.call();
} catch (Exception e) {
if (cls.contains(AFlag.RESTART_CODEGEN)) {
cls.remove(AFlag.RESTART_CODEGEN);
try {
return codeGenFunc.call();
} catch (Exception ex) {
throw new JadxRuntimeException("Code generation error after restart", ex);
}
} else {
throw new JadxRuntimeException("Code generation error", e);
}
}
}
private CodeGen() {
}
}
@@ -1,37 +1,45 @@
package jadx.core.codegen;
import jadx.api.CodePosition;
import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.utils.Utils;
import java.io.File;
import java.io.PrintWriter;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class CodeWriter {
import jadx.api.CodePosition;
import jadx.api.ICodeInfo;
import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.utils.StringUtils;
import jadx.core.utils.files.FileUtils;
import jadx.core.utils.files.ZipSecurity;
public class CodeWriter implements ICodeInfo {
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 = " ";
public static final String INDENT_STR = " ";
private static final boolean ADD_LINE_NUMBERS = false;
private static final String[] INDENT_CACHE = {
"",
INDENT,
INDENT + INDENT,
INDENT + INDENT + INDENT,
INDENT + INDENT + INDENT + INDENT,
INDENT + INDENT + INDENT + INDENT + INDENT,
INDENT_STR,
INDENT_STR + INDENT_STR,
INDENT_STR + INDENT_STR + INDENT_STR,
INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR,
INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR,
};
private final StringBuilder buf = new StringBuilder();
public static final CodeWriter EMPTY = new CodeWriter().finish();
private StringBuilder buf;
@Nullable
private String code;
private String indentStr;
private int indent;
@@ -41,8 +49,19 @@ public class CodeWriter {
private Map<Integer, Integer> lineMap = Collections.emptyMap();
public CodeWriter() {
this.buf = new StringBuilder();
this.indent = 0;
this.indentStr = "";
if (ADD_LINE_NUMBERS) {
incIndent(3);
add(indentStr);
}
}
// create filled instance (just string wrapper)
public CodeWriter(String code) {
this.buf = null;
this.code = code;
}
public CodeWriter startLine() {
@@ -65,6 +84,37 @@ public class CodeWriter {
return this;
}
public CodeWriter startLineWithNum(int sourceLine) {
if (sourceLine == 0) {
startLine();
return this;
}
if (ADD_LINE_NUMBERS) {
newLine();
attachSourceLine(sourceLine);
String ln = "/* " + sourceLine + " */ ";
add(ln);
if (indentStr.length() > ln.length()) {
add(indentStr.substring(ln.length()));
}
} else {
startLine();
attachSourceLine(sourceLine);
}
return this;
}
public CodeWriter addMultiLine(String str) {
if (str.contains(NL)) {
buf.append(str.replace(NL, NL + indentStr));
line += StringUtils.countMatches(str, NL);
offset = 0;
} else {
buf.append(str);
}
return this;
}
public CodeWriter add(String str) {
buf.append(str);
offset += str.length();
@@ -88,7 +138,7 @@ public class CodeWriter {
}
line += code.line;
offset = code.offset;
buf.append(code);
buf.append(code.buf);
return this;
}
@@ -98,7 +148,7 @@ public class CodeWriter {
}
public CodeWriter addIndent() {
add(INDENT);
add(INDENT_STR);
return this;
}
@@ -119,9 +169,9 @@ public class CodeWriter {
if (curIndent < INDENT_CACHE.length) {
this.indentStr = INDENT_CACHE[curIndent];
} else {
StringBuilder s = new StringBuilder(curIndent * INDENT.length());
StringBuilder s = new StringBuilder(curIndent * INDENT_STR.length());
for (int i = 0; i < curIndent; i++) {
s.append(INDENT);
s.append(INDENT_STR);
}
this.indentStr = s.toString();
}
@@ -149,6 +199,15 @@ public class CodeWriter {
updateIndent();
}
public int getIndent() {
return indent;
}
public void setIndent(int indent) {
this.indent = indent;
updateIndent();
}
public int getLine() {
return line;
}
@@ -165,21 +224,27 @@ 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));
}
public void attachLineAnnotation(Object obj) {
attachAnnotation(obj, new CodePosition(line, 0));
}
private Object attachAnnotation(Object obj, CodePosition pos) {
if (annotations.isEmpty()) {
annotations = new HashMap<CodePosition, Object>();
annotations = new HashMap<>();
}
return annotations.put(pos, obj);
}
@Override
public Map<CodePosition, Object> getAnnotations() {
return annotations;
}
@@ -193,104 +258,81 @@ public class CodeWriter {
private void attachSourceLine(int decompiledLine, int sourceLine) {
if (lineMap.isEmpty()) {
lineMap = new TreeMap<Integer, Integer>();
lineMap = new TreeMap<>();
}
lineMap.put(decompiledLine, sourceLine);
}
@Override
public Map<Integer, Integer> getLineMapping() {
return lineMap;
}
public void finish() {
public CodeWriter finish() {
removeFirstEmptyLine();
buf.trimToSize();
Iterator<Map.Entry<CodePosition, Object>> it = annotations.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<CodePosition, Object> entry = it.next();
code = buf.toString();
buf = null;
annotations.entrySet().removeIf(entry -> {
Object v = entry.getValue();
if (v instanceof DefinitionWrapper) {
LineAttrNode l = ((DefinitionWrapper) v).getNode();
l.setDecompiledLine(entry.getKey().getLine());
it.remove();
return true;
}
return false;
});
return this;
}
private void removeFirstEmptyLine() {
int len = NL.length();
if (buf.length() > len && buf.substring(0, len).equals(NL)) {
buf.delete(0, len);
}
}
private static String removeFirstEmptyLine(String str) {
if (str.startsWith(NL)) {
return str.substring(NL.length());
}
return str;
}
public int length() {
public int bufLength() {
return buf.length();
}
public boolean isEmpty() {
return buf.length() == 0;
}
public boolean notEmpty() {
return buf.length() != 0;
@Override
public String getCodeStr() {
if (code == null) {
throw new NullPointerException("Code not set");
}
return code;
}
@Override
public String toString() {
return buf.toString();
return buf == null ? code : buf.toString();
}
public void save(File dir, String subDir, String fileName) {
if (!ZipSecurity.isValidZipEntryName(subDir) || !ZipSecurity.isValidZipEntryName(fileName)) {
return;
}
save(dir, new File(subDir, fileName).getPath());
}
public void save(File dir, String fileName) {
if (!ZipSecurity.isValidZipEntryName(fileName)) {
return;
}
save(new File(dir, fileName));
}
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();
}
PrintWriter out = null;
try {
Utils.makeDirsForFile(file);
out = new PrintWriter(file, "UTF-8");
String code = buf.toString();
code = removeFirstEmptyLine(code);
out.print(code);
File outFile = FileUtils.prepareFile(file);
try (PrintWriter out = new PrintWriter(outFile, "UTF-8")) {
out.println(code);
} catch (Exception e) {
LOG.error("Save file error", e);
} finally {
if (out != null) {
out.close();
}
}
}
@Override
public int hashCode() {
return buf.toString().hashCode();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof CodeWriter)) {
return false;
}
CodeWriter that = (CodeWriter) o;
return buf.toString().equals(that.buf.toString());
}
}
@@ -1,5 +1,10 @@
package jadx.core.codegen;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Queue;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.instructions.ArithNode;
import jadx.core.dex.instructions.IfOp;
import jadx.core.dex.instructions.InsnType;
@@ -8,56 +13,92 @@ 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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ConditionGen extends InsnGen {
private static final Logger LOG = LoggerFactory.getLogger(ConditionGen.class);
private static class CondStack {
private final Queue<IfCondition> stack = new LinkedList<>();
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 +111,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()));
ErrorsCounter.methodWarn(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 +156,19 @@ public class ConditionGen extends InsnGen {
}
private boolean isWrapNeeded(IfCondition condition) {
return !condition.isCompare();
if (condition.isCompare() || condition.contains(AFlag.DONT_WRAP)) {
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:
@@ -126,10 +176,23 @@ public class ConditionGen extends InsnGen {
case DIV:
case REM:
return false;
default:
return true;
}
} else {
switch (insnType) {
case INVOKE:
case SGET:
case IGET:
case AGET:
case CONST:
case ARRAY_LENGTH:
return false;
default:
return true;
}
} else if (insn.getType() == InsnType.INVOKE) {
return false;
}
return true;
}
}
File diff suppressed because it is too large Load Diff
@@ -1,24 +1,6 @@
package jadx.core.codegen;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.annotations.MethodParameters;
import jadx.core.dex.attributes.nodes.JadxErrorAttr;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.regions.Region;
import jadx.core.dex.trycatch.CatchAttr;
import jadx.core.dex.visitors.DepthTraversal;
import jadx.core.dex.visitors.FallbackModeVisitor;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.DecodeException;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
@@ -27,6 +9,30 @@ import org.slf4j.LoggerFactory;
import com.android.dx.rop.code.AccessFlags;
import jadx.core.Consts;
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.JumpInfo;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.IfNode;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.CodeVar;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.trycatch.CatchAttr;
import jadx.core.dex.visitors.DepthTraversal;
import jadx.core.dex.visitors.FallbackModeVisitor;
import jadx.core.utils.CodeGenUtils;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxOverflowException;
public class MethodGen {
private static final Logger LOG = LoggerFactory.getLogger(MethodGen.class);
@@ -39,7 +45,7 @@ public class MethodGen {
this.mth = mth;
this.classGen = classGen;
this.annotationGen = classGen.getAnnotationGen();
this.nameGen = new NameGen(classGen.isFallbackMode());
this.nameGen = new NameGen(mth, classGen.isFallbackMode());
}
public ClassGen getClassGen() {
@@ -56,8 +62,8 @@ public class MethodGen {
public boolean addDefinition(CodeWriter code) {
if (mth.getMethodInfo().isClassInit()) {
code.startLine("static");
code.attachDefinition(mth);
code.startLine("static");
return true;
}
if (mth.contains(AFlag.ANONYMOUS_CONSTRUCTOR)) {
@@ -79,130 +85,170 @@ public class MethodGen {
if (clsAccFlags.isAnnotation()) {
ai = ai.remove(AccessFlags.ACC_PUBLIC);
}
code.startLine(ai.makeString());
code.attachSourceLine(mth.getSourceLine());
if (classGen.addGenericMap(code, mth.getGenericMap())) {
if (mth.getMethodInfo().hasAlias() && !ai.isConstructor()) {
CodeGenUtils.addRenamedComment(code, mth, mth.getName());
}
if (mth.contains(AFlag.INCONSISTENT_CODE)) {
code.startLine("/* Code decompiled incorrectly, please refer to instructions dump. */");
}
code.startLineWithNum(mth.getSourceLine());
code.add(ai.makeString());
if (Consts.DEBUG) {
code.add(mth.isVirtual() ? "/* virtual */ " : "/* direct */ ");
}
if (classGen.addGenericMap(code, mth.getGenerics(), false)) {
code.add(' ');
}
if (mth.getAccessFlags().isConstructor()) {
if (ai.isConstructor()) {
code.attachDefinition(mth);
code.add(classGen.getClassNode().getShortName()); // constructor
} else {
classGen.useType(code, mth.getReturnType());
code.add(' ');
code.add(mth.getName());
code.attachDefinition(mth);
code.add(mth.getAlias());
}
code.add('(');
List<RegisterArg> args = mth.getArguments(false);
List<RegisterArg> args = mth.getArgRegs();
if (mth.getMethodInfo().isConstructor()
&& mth.getParentClass().contains(AType.ENUM_CLASS)) {
if (args.size() == 2) {
args.clear();
args = Collections.emptyList();
} else if (args.size() > 2) {
args = args.subList(2, args.size());
} else {
LOG.warn(ErrorsCounter.formatErrorMsg(mth,
"Incorrect number of args for enum constructor: " + args.size()
+ " (expected >= 2)"
));
mth.addComment("JADX WARN: Incorrect number of args for enum constructor: " + args.size() + " (expected >= 2)");
}
} else if (mth.contains(AFlag.SKIP_FIRST_ARG)) {
args = args.subList(1, args.size());
}
addMethodArguments(code, args);
code.add(')');
annotationGen.addThrows(mth, code);
code.attachDefinition(mth);
// add default value if in annotation class
if (mth.getParentClass().getAccessFlags().isAnnotation()) {
Object def = annotationGen.getAnnotationDefaultValue(mth.getName());
if (def != null) {
code.add(" default ");
annotationGen.encodeValue(code, def);
}
}
return true;
}
private void addMethodArguments(CodeWriter argsCode, List<RegisterArg> args) {
private void addMethodArguments(CodeWriter code, List<RegisterArg> args) {
MethodParameters paramsAnnotation = mth.get(AType.ANNOTATION_MTH_PARAMETERS);
int i = 0;
for (Iterator<RegisterArg> it = args.iterator(); it.hasNext(); ) {
RegisterArg arg = it.next();
Iterator<RegisterArg> it = args.iterator();
while (it.hasNext()) {
RegisterArg mthArg = it.next();
SSAVar ssaVar = mthArg.getSVar();
CodeVar var;
if (ssaVar == null) {
// null for abstract or interface methods
var = CodeVar.fromMthArg(mthArg);
} else {
var = ssaVar.getCodeVar();
}
// add argument annotation
if (paramsAnnotation != null) {
annotationGen.addForParameter(argsCode, paramsAnnotation, i);
annotationGen.addForParameter(code, paramsAnnotation, i);
}
if (var.isFinal()) {
code.add("final ");
}
ArgType argType;
ArgType varType = var.getType();
if (varType == null || varType == ArgType.UNKNOWN) {
// occur on decompilation errors
argType = mthArg.getInitType();
} else {
argType = varType;
}
if (!it.hasNext() && mth.getAccessFlags().isVarArgs()) {
// change last array argument to varargs
ArgType type = arg.getType();
if (type.isArray()) {
ArgType elType = type.getArrayElement();
classGen.useType(argsCode, elType);
argsCode.add(" ...");
if (argType.isArray()) {
ArgType elType = argType.getArrayElement();
classGen.useType(code, elType);
code.add("...");
} else {
LOG.warn(ErrorsCounter.formatErrorMsg(mth, "Last argument in varargs method not array"));
classGen.useType(argsCode, arg.getType());
mth.addComment("JADX INFO: Last argument in varargs method is not array: " + var);
classGen.useType(code, argType);
}
} else {
classGen.useType(argsCode, arg.getType());
classGen.useType(code, argType);
}
argsCode.add(' ');
argsCode.add(nameGen.assignArg(arg));
code.add(' ');
code.add(nameGen.assignArg(var));
i++;
if (it.hasNext()) {
argsCode.add(", ");
code.add(", ");
}
}
}
public void addInstructions(CodeWriter code) throws CodegenException {
if (mth.contains(AType.JADX_ERROR)) {
code.startLine("throw new UnsupportedOperationException(\"Method not decompiled: ");
code.add(mth.toString());
code.add("\");");
JadxErrorAttr err = mth.get(AType.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("*/");
}
makeMethodDump(code);
} else if (mth.contains(AFlag.INCONSISTENT_CODE)) {
code.startLine("/*");
if (mth.root().getArgs().isFallbackMode()) {
addFallbackMethodCode(code);
code.startLine("*/");
code.newLine();
} else if (classGen.isFallbackMode()) {
dumpInstructions(code);
} else {
Region startRegion = mth.getRegion();
if (startRegion != null) {
(new RegionGen(this)).makeRegion(code, startRegion);
} else {
addFallbackMethodCode(code);
}
addRegionInsns(code);
}
}
private void makeMethodDump(CodeWriter code) {
public void addRegionInsns(CodeWriter code) throws CodegenException {
try {
RegionGen regionGen = new RegionGen(this);
regionGen.makeRegion(code, mth.getRegion());
} catch (StackOverflowError | BootstrapMethodError e) {
mth.addError("Method code generation error", new JadxOverflowException("StackOverflow"));
classGen.insertDecompilationProblems(code, mth);
dumpInstructions(code);
} catch (Exception e) {
if (mth.getParentClass().getTopParentClass().contains(AFlag.RESTART_CODEGEN)) {
throw e;
}
mth.addError("Method code generation error", e);
classGen.insertDecompilationProblems(code, mth);
dumpInstructions(code);
}
}
public void dumpInstructions(CodeWriter code) {
code.startLine("/*");
getFallbackMethodGen(mth).addDefinition(code);
code.add(" {");
code.incIndent();
addFallbackMethodCode(code);
code.decIndent();
code.startLine('}');
code.startLine("*/");
code.startLine("throw new UnsupportedOperationException(\"Method not decompiled: ")
.add(mth.getParentClass().getClassInfo().getAliasFullName())
.add('.')
.add(mth.getAlias())
.add('(')
.add(Utils.listToString(mth.getMethodInfo().getArgumentsTypes()))
.add("):")
.add(mth.getMethodInfo().getReturnType().toString())
.add("\");");
}
public void addFallbackMethodCode(CodeWriter code) {
if (mth.getInstructions() == null) {
// load original instructions
try {
mth.unload();
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());
code.startLine("// Can't load method instructions: " + e.getMessage());
return;
}
}
@@ -211,50 +257,85 @@ public class MethodGen {
code.startLine("// Can't load method instructions.");
return;
}
code.incIndent();
if (mth.getThisArg() != null) {
code.startLine(getFallbackMethodGen(mth).nameGen.useArg(mth.getThisArg())).add(" = this;");
code.startLine(nameGen.useArg(mth.getThisArg())).add(" = this;");
}
addFallbackInsns(code, mth, insnArr, true);
code.decIndent();
}
public static void addFallbackInsns(CodeWriter code, MethodNode mth, InsnNode[] insnArr, boolean addLabels) {
InsnGen insnGen = new InsnGen(getFallbackMethodGen(mth), true);
boolean attachInsns = mth.root().getArgs().isJsonOutput();
InsnNode prevInsn = null;
for (InsnNode insn : insnArr) {
if (insn == null) {
continue;
}
if (addLabels) {
if (insn.contains(AType.JUMP)
|| insn.contains(AType.EXC_HANDLER)) {
code.decIndent();
code.startLine(getLabelName(insn.getOffset()) + ":");
code.incIndent();
}
if (addLabels && needLabel(insn, prevInsn)) {
code.decIndent();
code.startLine(getLabelName(insn.getOffset()) + ':');
code.incIndent();
}
if (insn.getType() == InsnType.NOP) {
continue;
}
try {
if (insnGen.makeInsn(insn, code)) {
CatchAttr catchAttr = insn.get(AType.CATCH_BLOCK);
if (catchAttr != null) {
code.add("\t " + catchAttr);
code.startLine();
if (attachInsns) {
code.attachLineAnnotation(insn);
}
RegisterArg resArg = insn.getResult();
if (resArg != null) {
ArgType varType = resArg.getInitType();
if (varType.isTypeKnown()) {
code.add(varType.toString()).add(' ');
}
}
insnGen.makeInsn(insn, code, InsnGen.Flags.INLINE);
CatchAttr catchAttr = insn.get(AType.CATCH_BLOCK);
if (catchAttr != null) {
code.add(" // " + catchAttr);
}
} catch (CodegenException e) {
LOG.debug("Error generate fallback instruction: ", e.getCause());
code.startLine("// error: " + insn);
}
prevInsn = insn;
}
}
private static boolean needLabel(InsnNode insn, InsnNode prevInsn) {
if (insn.contains(AType.EXC_HANDLER)) {
return true;
}
if (insn.contains(AType.JUMP)) {
// don't add label for ifs else branch
if (prevInsn != null && prevInsn.getType() == InsnType.IF) {
List<JumpInfo> jumps = insn.getAll(AType.JUMP);
if (jumps.size() == 1) {
JumpInfo jump = jumps.get(0);
if (jump.getSrc() == prevInsn.getOffset() && jump.getDest() == insn.getOffset()) {
int target = ((IfNode) prevInsn).getTarget();
return insn.getOffset() == target;
}
}
}
return true;
}
return false;
}
/**
* 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, false, true, true);
return new MethodGen(clsGen, mth);
}
public static String getLabelName(int offset) {
return "L_" + InsnUtils.formatOffset(offset);
}
}
@@ -1,62 +1,83 @@
package jadx.core.codegen;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
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.CodeVar;
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.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.StringUtils;
import jadx.core.utils.Utils;
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 Set<String> varNames = new HashSet<>();
private final MethodNode mth;
private final boolean fallback;
static {
OBJ_ALIAS = new HashMap<String, String>();
OBJ_ALIAS.put(Consts.CLASS_STRING, "str");
OBJ_ALIAS.put(Consts.CLASS_CLASS, "cls");
OBJ_ALIAS.put(Consts.CLASS_THROWABLE, "th");
OBJ_ALIAS.put(Consts.CLASS_OBJECT, "obj");
OBJ_ALIAS.put("java.util.Iterator", "it");
OBJ_ALIAS.put("java.lang.Boolean", "bool");
OBJ_ALIAS.put("java.lang.Short", "sh");
OBJ_ALIAS.put("java.lang.Integer", "num");
OBJ_ALIAS.put("java.lang.Character", "ch");
OBJ_ALIAS.put("java.lang.Byte", "b");
OBJ_ALIAS.put("java.lang.Float", "f");
OBJ_ALIAS.put("java.lang.Long", "l");
OBJ_ALIAS.put("java.lang.Double", "d");
OBJ_ALIAS = Utils.newConstStringMap(
Consts.CLASS_STRING, "str",
Consts.CLASS_CLASS, "cls",
Consts.CLASS_THROWABLE, "th",
Consts.CLASS_OBJECT, "obj",
"java.util.Iterator", "it",
"java.lang.Boolean", "bool",
"java.lang.Short", "sh",
"java.lang.Integer", "num",
"java.lang.Character", "ch",
"java.lang.Byte", "b",
"java.lang.Float", "f",
"java.lang.Long", "l",
"java.lang.Double", "d",
"java.lang.StringBuilder", "sb",
"java.lang.Exception", "exc");
}
public NameGen(boolean fallback) {
public NameGen(MethodNode mth, boolean fallback) {
this.mth = mth;
this.fallback = fallback;
addNamesUsedInClass();
}
public String assignArg(RegisterArg arg) {
String name = makeArgName(arg);
private void addNamesUsedInClass() {
ClassNode parentClass = mth.getParentClass();
for (FieldNode field : parentClass.getFields()) {
varNames.add(field.getAlias());
}
for (ClassNode innerClass : parentClass.getInnerClasses()) {
varNames.add(innerClass.getClassInfo().getAliasShortName());
}
// add all root package names to avoid collisions with full class names
varNames.addAll(mth.root().getCacheStorage().getRootPkgs());
}
public String assignArg(CodeVar var) {
String name = makeArgName(var);
if (fallback) {
return name;
}
name = getUniqueVarName(name);
SSAVar sVar = arg.getSVar();
if (sVar != null) {
sVar.setName(name);
}
var.setName(name);
return name;
}
@@ -71,7 +92,16 @@ public class NameGen {
}
public String useArg(RegisterArg arg) {
String name = makeArgName(arg);
String name = arg.getName();
if (name == null || fallback) {
return getFallbackName(arg);
}
return name;
}
// TODO: avoid name collision with variables names
public String getLoopLabel(LoopLabelAttr attr) {
String name = "loop" + attr.getLoop().getId();
varNames.add(name);
return name;
}
@@ -87,58 +117,93 @@ public class NameGen {
return r;
}
private String makeArgName(RegisterArg arg) {
String name = arg.getName();
private String makeArgName(CodeVar var) {
if (fallback) {
String base = "r" + arg.getRegNum();
if (name != null) {
return base + "_" + name;
}
return base;
return getFallbackName(var);
}
String varName;
if (name != null) {
if ("this".equals(name)) {
return name;
}
varName = name;
} else {
varName = makeNameForType(arg.getType());
if (var.isThis()) {
return RegisterArg.THIS_ARG_NAME;
}
String name = var.getName();
String varName = name != null ? name : guessName(var);
if (NameMapper.isReserved(varName)) {
return varName + "R";
varName = varName + 'R';
}
if (!NameMapper.isValidAndPrintable(varName)) {
varName = getFallbackName(var);
}
if (Consts.DEBUG) {
varName += '_' + getFallbackName(var);
}
return varName;
}
private static String makeNameForType(ArgType type) {
private String getFallbackName(CodeVar var) {
List<SSAVar> ssaVars = var.getSsaVars();
if (ssaVars.isEmpty()) {
return "v";
}
return getFallbackName(ssaVars.get(0).getAssign());
}
private String getFallbackName(RegisterArg arg) {
return "r" + arg.getRegNum();
}
private String guessName(CodeVar var) {
List<SSAVar> ssaVars = var.getSsaVars();
if (ssaVars != null && !ssaVars.isEmpty()) {
// TODO: use all vars for better name generation
SSAVar ssaVar = ssaVars.get(0);
if (ssaVar != null && ssaVar.getName() == null) {
RegisterArg assignArg = ssaVar.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(var.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);
}
if (type.isArray()) {
return makeNameForType(type.getArrayRootElement()) + "Arr";
}
return makeNameForObject(type);
}
private static String makeNameForPrimitive(ArgType type) {
return type.getPrimitiveType().getShortName().toLowerCase();
}
private static String makeNameForObject(ArgType type) {
private String makeNameForObject(ArgType type) {
if (type.isGenericType()) {
return StringUtils.escape(type.getObject().toLowerCase());
}
if (type.isObject()) {
String alias = getAliasForObject(type.getObject());
if (alias != null) {
return alias;
}
ClassInfo clsInfo = ClassInfo.fromType(type);
String shortName = clsInfo.getShortName();
ClassInfo extClsInfo = ClassInfo.fromType(mth.root(), type);
String shortName = extClsInfo.getShortName();
String vName = fromName(shortName);
if (vName != null) {
return vName;
}
if (shortName != null) {
return StringUtils.escape(shortName.toLowerCase());
}
}
return Utils.escape(type.toString());
return StringUtils.escape(type.toString());
}
private static String fromName(String name) {
@@ -159,35 +224,15 @@ public class NameGen {
return null;
}
public static void guessName(RegisterArg arg) {
SSAVar sVar = arg.getSVar();
if (sVar == null || sVar.getName() != null) {
return;
}
RegisterArg assignArg = sVar.getAssign();
InsnNode assignInsn = assignArg.getParentInsn();
String name = makeNameFromInsn(assignInsn);
if (name != null && !NameMapper.isReserved(name)) {
assignArg.setName(name);
}
}
public static String getAliasForObject(String name) {
private static String getAliasForObject(String name) {
return OBJ_ALIAS.get(name);
}
private static String makeNameFromInsn(InsnNode insn) {
private String makeNameFromInsn(InsnNode insn) {
switch (insn.getType()) {
case INVOKE:
InvokeNode inv = (InvokeNode) insn;
String name = inv.getCallMth().getName();
if (name.startsWith("get") || name.startsWith("set")) {
return fromName(name.substring(3));
}
if ("iterator".equals(name)) {
return "it";
}
return name;
return makeNameFromInvoke(inv.getCallMth());
case CONSTRUCTOR:
ConstructorInsn co = (ConstructorInsn) insn;
@@ -215,4 +260,25 @@ public class NameGen {
}
return null;
}
private String makeNameFromInvoke(MethodInfo callMth) {
String name = callMth.getName();
if (name.startsWith("get") || name.startsWith("set")) {
return fromName(name.substring(3));
}
if ("iterator".equals(name)) {
return "it";
}
ArgType declType = callMth.getDeclClass().getType();
if ("toString".equals(name)) {
return makeNameForType(declType);
}
if ("forName".equals(name) && declType.equals(ArgType.CLASS)) {
return OBJ_ALIAS.get(Consts.CLASS_CLASS);
}
if (name.startsWith("to")) {
return fromName(name.substring(2));
}
return name;
}
}
@@ -1,35 +1,47 @@
package jadx.core.codegen;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
import jadx.core.dex.attributes.nodes.ForceReturnAttr;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.instructions.SwitchNode;
import jadx.core.dex.instructions.args.CodeVar;
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.BlockUtils;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.RegionUtils;
import jadx.core.utils.exceptions.CodegenException;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.utils.exceptions.JadxRuntimeException;
public class RegionGen extends InsnGen {
private static final Logger LOG = LoggerFactory.getLogger(RegionGen.class);
@@ -52,6 +64,8 @@ public class RegionGen extends InsnGen {
makeSwitch((SwitchRegion) cont, code);
} else if (cont instanceof LoopRegion) {
makeLoop((LoopRegion) cont, code);
} else if (cont instanceof TryCatchRegion) {
makeTryCatch((TryCatchRegion) cont, code);
} else if (cont instanceof SynchronizedRegion) {
makeSynchronizedRegion((SynchronizedRegion) cont, code);
}
@@ -64,7 +78,7 @@ public class RegionGen extends InsnGen {
private void declareVars(CodeWriter code, IContainer cont) {
DeclareVariablesAttr declVars = cont.get(AType.DECLARE_VARIABLES);
if (declVars != null) {
for (RegisterArg v : declVars.getVars()) {
for (CodeVar v : declVars.getVars()) {
code.startLine();
declareVar(code, v);
code.add(';');
@@ -73,14 +87,9 @@ public class RegionGen extends InsnGen {
}
private void makeSimpleRegion(CodeWriter code, Region region) throws CodegenException {
CatchAttr tc = region.get(AType.CATCH_BLOCK);
if (tc != null) {
makeTryCatch(region, tc.getTryBlock(), code);
} else {
declareVars(code, region);
for (IContainer c : region.getSubBlocks()) {
makeRegion(code, c);
}
declareVars(code, region);
for (IContainer c : region.getSubBlocks()) {
makeRegion(code, c);
}
}
@@ -91,8 +100,14 @@ public class RegionGen extends InsnGen {
}
private void makeSimpleBlock(IBlock block, CodeWriter code) throws CodegenException {
if (block.contains(AFlag.DONT_GENERATE)) {
return;
}
for (InsnNode insn : block.getInstructions()) {
makeInsn(insn, code);
if (!insn.contains(AFlag.DONT_GENERATE)) {
makeInsn(insn, code);
}
}
ForceReturnAttr retAttr = block.get(AType.FORCE_RETURN);
if (retAttr != null) {
@@ -101,29 +116,49 @@ public class RegionGen extends InsnGen {
}
private void makeIf(IfRegion region, CodeWriter code, boolean newLine) throws CodegenException {
if (region.getTernRegion() != null) {
makeSimpleBlock(region.getTernRegion().getBlock(), code);
return;
}
if (newLine) {
code.startLine();
code.startLineWithNum(region.getSourceLine());
} else {
code.attachSourceLine(region.getSourceLine());
}
code.attachSourceLine(region.getSourceLine());
if (attachInsns) {
List<BlockNode> conditionBlocks = region.getConditionBlocks();
if (!conditionBlocks.isEmpty()) {
BlockNode blockNode = conditionBlocks.get(0);
InsnNode lastInsn = BlockUtils.getLastInsn(blockNode);
if (lastInsn != null) {
code.attachLineAnnotation(lastInsn);
}
}
}
boolean comment = region.contains(AFlag.COMMENT_OUT);
if (comment) {
code.add("// ");
}
code.add("if (");
new ConditionGen(this).add(code, region.getCondition());
code.add(") {");
makeRegionIndent(code, region.getThenRegion());
code.startLine('}');
if (comment) {
code.startLine("// }");
} else {
code.startLine('}');
}
IContainer els = region.getElseRegion();
if (els != null && RegionUtils.notEmpty(els)) {
if (RegionUtils.notEmpty(els)) {
code.add(" else ");
if (connectElseIf(code, els)) {
return;
}
code.add('{');
makeRegionIndent(code, els);
code.startLine('}');
if (comment) {
code.startLine("// }");
} else {
code.startLine('}');
}
}
}
@@ -131,17 +166,16 @@ public class RegionGen extends InsnGen {
* Connect if-else-if block
*/
private boolean connectElseIf(CodeWriter code, IContainer els) throws CodegenException {
if (!els.contains(AFlag.ELSE_IF_CHAIN)) {
return false;
}
if (!(els instanceof Region)) {
return false;
}
List<IContainer> subBlocks = ((Region) els).getSubBlocks();
if (subBlocks.size() == 1
&& subBlocks.get(0) instanceof IfRegion) {
makeIf((IfRegion) subBlocks.get(0), code, false);
return true;
if (els.contains(AFlag.ELSE_IF_CHAIN) && els instanceof Region) {
List<IContainer> subBlocks = ((Region) els).getSubBlocks();
if (subBlocks.size() == 1) {
IContainer elseBlock = subBlocks.get(0);
if (elseBlock instanceof IfRegion) {
declareVars(code, elseBlock);
makeIf((IfRegion) elseBlock, code, false);
return true;
}
}
}
return false;
}
@@ -151,8 +185,7 @@ public class RegionGen extends InsnGen {
if (header != null) {
List<InsnNode> headerInsns = header.getInstructions();
if (headerInsns.size() > 1) {
// write not inlined instructions from header
mth.add(AFlag.INCONSISTENT_CODE);
ErrorsCounter.methodWarn(mth, "Found not inlined instructions from loop header");
int last = headerInsns.size() - 1;
for (int i = 0; i < last; i++) {
InsnNode insn = headerInsns.get(i);
@@ -160,6 +193,10 @@ public class RegionGen extends InsnGen {
}
}
}
LoopLabelAttr labelAttr = region.getInfo().getStart().get(AType.LOOP_LABEL);
if (labelAttr != null) {
code.startLine(mgen.getNameGen().getLoopLabel(labelAttr)).add(':');
}
IfCondition condition = region.getCondition();
if (condition == null) {
@@ -169,16 +206,45 @@ 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());
code.startLine("} while (");
code.startLineWithNum(region.getConditionSourceLine());
code.add("} while (");
conditionGen.add(code, condition);
code.add(");");
} else {
code.startLine("while (");
code.startLineWithNum(region.getConditionSourceLine());
code.add("while (");
conditionGen.add(code, condition);
code.add(") {");
makeRegionIndent(code, region.getBody());
@@ -196,10 +262,11 @@ public class RegionGen extends InsnGen {
}
private CodeWriter makeSwitch(SwitchRegion sw, CodeWriter code) throws CodegenException {
SwitchNode insn = (SwitchNode) sw.getHeader().getInstructions().get(0);
SwitchNode insn = (SwitchNode) BlockUtils.getLastInsn(sw.getHeader());
Objects.requireNonNull(insn, "Switch insn not found in header");
InsnArg arg = insn.getArg(0);
code.startLine("switch (");
addArg(code, arg);
addArg(code, arg, false);
code.add(") {");
code.incIndent();
@@ -209,77 +276,90 @@ public class RegionGen extends InsnGen {
IContainer c = sw.getCases().get(i);
for (Object k : keys) {
code.startLine("case ");
if (k instanceof IndexInsnNode) {
staticField(code, (FieldInfo) ((IndexInsnNode) k).getIndex());
if (k instanceof FieldNode) {
FieldNode fn = (FieldNode) k;
if (fn.getParentClass().isEnum()) {
code.add(fn.getAlias());
} else {
staticField(code, fn.getFieldInfo());
// print original value, sometimes replace with incorrect field
FieldInitAttr valueAttr = fn.get(AType.FIELD_INIT);
if (valueAttr != null && valueAttr.getValue() != null) {
code.add(" /*").add(valueAttr.getValue().toString()).add("*/");
}
}
} else if (k instanceof Integer) {
code.add(TypeGen.literalToString((Integer) k, arg.getType(), mth, fallback));
} else {
code.add(TypeGen.literalToString((Integer) k, arg.getType()));
throw new JadxRuntimeException("Unexpected key in switch: " + (k != null ? k.getClass() : null));
}
code.add(':');
}
makeCaseBlock(c, code);
makeRegionIndent(code, c);
}
if (sw.getDefaultCase() != null) {
code.startLine("default:");
makeCaseBlock(sw.getDefaultCase(), code);
makeRegionIndent(code, sw.getDefaultCase());
}
code.decIndent();
code.startLine('}');
return code;
}
private void makeCaseBlock(IContainer c, CodeWriter code) throws CodegenException {
boolean addBreak = true;
if (RegionUtils.notEmpty(c)) {
makeRegionIndent(code, c);
if (!RegionUtils.hasExitEdge(c)) {
addBreak = false;
}
}
if (addBreak) {
code.startLine().addIndent().add("break;");
}
}
private void makeTryCatch(IContainer region, TryCatchBlock tryCatchBlock, CodeWriter code)
throws CodegenException {
private void makeTryCatch(TryCatchRegion region, CodeWriter code) throws CodegenException {
code.startLine("try {");
region.remove(AType.CATCH_BLOCK);
makeRegionIndent(code, region);
makeRegionIndent(code, region.getTryRegion());
// TODO: move search of 'allHandler' to 'TryCatchRegion'
ExceptionHandler allHandler = null;
for (ExceptionHandler handler : tryCatchBlock.getHandlers()) {
if (!handler.isCatchAll()) {
makeCatchBlock(code, handler);
} else {
for (Map.Entry<ExceptionHandler, IContainer> entry : region.getCatchRegions().entrySet()) {
ExceptionHandler handler = entry.getKey();
if (handler.isCatchAll()) {
if (allHandler != null) {
LOG.warn("Several 'all' handlers in try/catch block in " + mth);
LOG.warn("Several 'all' handlers in try/catch block in {}", mth);
}
allHandler = handler;
} else {
makeCatchBlock(code, handler);
}
}
if (allHandler != null) {
makeCatchBlock(code, allHandler);
}
if (tryCatchBlock.getFinalRegion() != null) {
IContainer finallyRegion = region.getFinallyRegion();
if (finallyRegion != null) {
code.startLine("} finally {");
makeRegionIndent(code, tryCatchBlock.getFinalRegion());
makeRegionIndent(code, finallyRegion);
}
code.startLine('}');
}
private void makeCatchBlock(CodeWriter code, ExceptionHandler handler)
throws CodegenException {
private void makeCatchBlock(CodeWriter code, ExceptionHandler handler) throws CodegenException {
IContainer region = handler.getHandlerRegion();
if (region != null) {
code.startLine("} catch (");
if (handler.isCatchAll()) {
code.add("Throwable");
} else {
useClass(code, handler.getCatchType());
}
code.add(' ');
code.add(mgen.getNameGen().assignNamedArg(handler.getArg()));
code.add(") {");
makeRegionIndent(code, region);
if (region == null) {
return;
}
code.startLine("} catch (");
if (handler.isCatchAll()) {
code.add("Throwable");
} else {
Iterator<ClassInfo> it = handler.getCatchTypes().iterator();
if (it.hasNext()) {
useClass(code, it.next());
}
while (it.hasNext()) {
code.add(" | ");
useClass(code, it.next());
}
}
code.add(' ');
InsnArg arg = handler.getArg();
if (arg instanceof RegisterArg) {
RegisterArg reg = (RegisterArg) arg;
code.add(mgen.getNameGen().assignArg(reg.getSVar().getCodeVar()));
} else if (arg instanceof NamedArg) {
code.add(mgen.getNameGen().assignNamedArg((NamedArg) arg));
}
code.add(") {");
makeRegionIndent(code, region);
}
}
@@ -1,15 +1,26 @@
package jadx.core.codegen;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.deobf.NameMapper;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.LiteralArg;
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 {
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());
}
@@ -19,18 +30,39 @@ public class TypeGen {
return stype.getShortName();
}
/**
* Convert literal arg to string (preferred method)
*/
public static String literalToString(LiteralArg arg, IDexNode dexNode, boolean fallback) {
return literalToString(arg.getLiteral(), arg.getType(),
dexNode.root().getStringUtils(),
fallback,
arg.contains(AFlag.EXPLICIT_PRIMITIVE_TYPE));
}
/**
* Convert literal value to string according to value type
*
* @throws JadxRuntimeException for incorrect type or literal value
*/
public static String literalToString(long lit, ArgType type) {
public static String literalToString(long lit, ArgType type, IDexNode dexNode, boolean fallback) {
return literalToString(lit, type, dexNode.root().getStringUtils(), fallback, false);
}
public static String literalToString(long lit, ArgType type, StringUtils stringUtils, boolean fallback, boolean cast) {
if (type == null || !type.isTypeKnown()) {
String n = Long.toString(lit);
if (Math.abs(lit) > 100) {
n += "; // 0x" + Long.toHexString(lit)
+ " float:" + Float.intBitsToFloat((int) lit)
+ " double:" + Double.longBitsToDouble(lit);
if (fallback && Math.abs(lit) > 100) {
StringBuilder sb = new StringBuilder();
sb.append(n).append("(0x").append(Long.toHexString(lit));
if (type == null || type.contains(PrimitiveType.FLOAT)) {
sb.append(", float:").append(Float.intBitsToFloat((int) lit));
}
if (type == null || type.contains(PrimitiveType.DOUBLE)) {
sb.append(", double:").append(Double.longBitsToDouble(lit));
}
sb.append(')');
return sb.toString();
}
return n;
}
@@ -39,15 +71,19 @@ public class TypeGen {
case BOOLEAN:
return lit == 0 ? "false" : "true";
case CHAR:
return StringUtils.unescapeChar((char) lit);
char ch = (char) lit;
if (!NameMapper.isPrintableChar(ch)) {
return Integer.toString(ch);
}
return stringUtils.unescapeChar(ch);
case BYTE:
return formatByte((byte) lit);
return formatByte(lit, cast);
case SHORT:
return formatShort((short) lit);
return formatShort(lit, cast);
case INT:
return formatInteger((int) lit);
return formatInteger(lit, cast);
case LONG:
return formatLong(lit);
return formatLong(lit, cast);
case FLOAT:
return formatFloat(Float.intBitsToFloat((int) lit));
case DOUBLE:
@@ -56,7 +92,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";
@@ -65,38 +102,94 @@ public class TypeGen {
}
}
public static String formatShort(short s) {
return "(short) " + wrapNegNum(s < 0, Short.toString(s));
public static String formatShort(long l, boolean cast) {
if (l == Short.MAX_VALUE) {
return "Short.MAX_VALUE";
}
if (l == Short.MIN_VALUE) {
return "Short.MIN_VALUE";
}
String str = Long.toString(l);
return cast ? "(short) " + str : str;
}
public static String formatByte(byte b) {
return "(byte) " + wrapNegNum(b < 0, Byte.toString(b));
public static String formatByte(long l, boolean cast) {
if (l == Byte.MAX_VALUE) {
return "Byte.MAX_VALUE";
}
if (l == Byte.MIN_VALUE) {
return "Byte.MIN_VALUE";
}
String str = Long.toString(l);
return cast ? "(byte) " + str : str;
}
public static String formatInteger(int i) {
return wrapNegNum(i < 0, Integer.toString(i));
public static String formatInteger(long l, boolean cast) {
if (l == Integer.MAX_VALUE) {
return "Integer.MAX_VALUE";
}
if (l == Integer.MIN_VALUE) {
return "Integer.MIN_VALUE";
}
String str = Long.toString(l);
return cast ? "(int) " + str : str;
}
public static String formatLong(long l, boolean cast) {
if (l == Long.MAX_VALUE) {
return "Long.MAX_VALUE";
}
if (l == Long.MIN_VALUE) {
return "Long.MIN_VALUE";
}
String str = Long.toString(l);
if (cast || Math.abs(l) >= Integer.MAX_VALUE) {
return 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);
}
private static String wrapNegNum(boolean lz, String str) {
// if (lz)
// return "(" + str + ")";
// else
return str;
if (f == Float.NEGATIVE_INFINITY) {
return "Float.NEGATIVE_INFINITY";
}
if (f == Float.POSITIVE_INFINITY) {
return "Float.POSITIVE_INFINITY";
}
if (f == Float.MIN_VALUE) {
return "Float.MIN_VALUE";
}
if (f == Float.MAX_VALUE) {
return "Float.MAX_VALUE";
}
if (f == Float.MIN_NORMAL) {
return "Float.MIN_NORMAL";
}
return Float.toString(f) + 'f';
}
}
@@ -0,0 +1,224 @@
package jadx.core.codegen.json;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.jetbrains.annotations.Nullable;
import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import jadx.api.CodePosition;
import jadx.api.JadxArgs;
import jadx.core.codegen.ClassGen;
import jadx.core.codegen.CodeWriter;
import jadx.core.codegen.MethodGen;
import jadx.core.codegen.json.cls.JsonClass;
import jadx.core.codegen.json.cls.JsonCodeLine;
import jadx.core.codegen.json.cls.JsonField;
import jadx.core.codegen.json.cls.JsonMethod;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.CodeGenUtils;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
public class JsonCodeGen {
private static final Gson GSON = new GsonBuilder()
.setPrettyPrinting()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_DASHES)
.disableHtmlEscaping()
.create();
private final ClassNode cls;
private final JadxArgs args;
private final RootNode root;
public JsonCodeGen(ClassNode cls) {
this.cls = cls;
this.root = cls.root();
this.args = root.getArgs();
}
public String process() {
JsonClass jsonCls = processCls(cls, null);
return GSON.toJson(jsonCls);
}
private JsonClass processCls(ClassNode cls, @Nullable ClassGen parentCodeGen) {
ClassGen classGen;
if (parentCodeGen == null) {
classGen = new ClassGen(cls, args);
} else {
classGen = new ClassGen(cls, parentCodeGen);
}
ClassInfo classInfo = cls.getClassInfo();
JsonClass jsonCls = new JsonClass();
jsonCls.setPkg(classInfo.getAliasPkg());
jsonCls.setDex(cls.dex().getDexFile().getName());
jsonCls.setName(classInfo.getFullName());
if (classInfo.hasAlias()) {
jsonCls.setAlias(classInfo.getAliasFullName());
}
jsonCls.setType(getClassTypeStr(cls));
jsonCls.setAccessFlags(cls.getAccessFlags().rawValue());
if (!Objects.equals(cls.getSuperClass(), ArgType.OBJECT)) {
jsonCls.setSuperClass(getTypeAlias(cls.getSuperClass()));
}
if (!cls.getInterfaces().isEmpty()) {
jsonCls.setInterfaces(Utils.collectionMap(cls.getInterfaces(), this::getTypeAlias));
}
CodeWriter cw = new CodeWriter();
CodeGenUtils.addComments(cw, cls);
classGen.insertDecompilationProblems(cw, cls);
classGen.addClassDeclaration(cw);
jsonCls.setDeclaration(cw.finish().toString());
addFields(cls, jsonCls, classGen);
addMethods(cls, jsonCls, classGen);
addInnerClasses(cls, jsonCls, classGen);
if (!cls.getClassInfo().isInner()) {
List<String> imports = Utils.collectionMap(classGen.getImports(), ClassInfo::getAliasFullName);
Collections.sort(imports);
jsonCls.setImports(imports);
}
return jsonCls;
}
private void addInnerClasses(ClassNode cls, JsonClass jsonCls, ClassGen classGen) {
List<ClassNode> innerClasses = cls.getInnerClasses();
if (innerClasses.isEmpty()) {
return;
}
jsonCls.setInnerClasses(new ArrayList<>(innerClasses.size()));
for (ClassNode innerCls : innerClasses) {
if (innerCls.contains(AFlag.DONT_GENERATE)) {
continue;
}
JsonClass innerJsonCls = processCls(innerCls, classGen);
jsonCls.getInnerClasses().add(innerJsonCls);
}
}
private void addFields(ClassNode cls, JsonClass jsonCls, ClassGen classGen) {
jsonCls.setFields(new ArrayList<>());
for (FieldNode field : cls.getFields()) {
if (field.contains(AFlag.DONT_GENERATE)) {
continue;
}
JsonField jsonField = new JsonField();
jsonField.setName(field.getName());
if (field.getFieldInfo().hasAlias()) {
jsonField.setAlias(field.getAlias());
}
CodeWriter cw = new CodeWriter();
classGen.addField(cw, field);
jsonField.setDeclaration(cw.finish().toString());
jsonField.setAccessFlags(field.getAccessFlags().rawValue());
jsonCls.getFields().add(jsonField);
}
}
private void addMethods(ClassNode cls, JsonClass jsonCls, ClassGen classGen) {
jsonCls.setMethods(new ArrayList<>());
for (MethodNode mth : cls.getMethods()) {
if (mth.contains(AFlag.DONT_GENERATE)) {
continue;
}
JsonMethod jsonMth = new JsonMethod();
jsonMth.setName(mth.getName());
if (mth.getMethodInfo().hasAlias()) {
jsonMth.setAlias(mth.getAlias());
}
jsonMth.setSignature(mth.getMethodInfo().getShortId());
jsonMth.setReturnType(getTypeAlias(mth.getReturnType()));
jsonMth.setArguments(Utils.collectionMap(mth.getMethodInfo().getArgumentsTypes(), this::getTypeAlias));
MethodGen mthGen = new MethodGen(classGen, mth);
CodeWriter cw = new CodeWriter();
mthGen.addDefinition(cw);
jsonMth.setDeclaration(cw.finish().toString());
jsonMth.setAccessFlags(mth.getAccessFlags().rawValue());
jsonMth.setLines(fillMthCode(mth, mthGen));
jsonMth.setOffset("0x" + Long.toHexString(mth.getMethodCodeOffset()));
jsonCls.getMethods().add(jsonMth);
}
}
private List<JsonCodeLine> fillMthCode(MethodNode mth, MethodGen mthGen) {
if (mth.isNoCode()) {
return Collections.emptyList();
}
CodeWriter code = new CodeWriter();
try {
mthGen.addInstructions(code);
} catch (Exception e) {
throw new JadxRuntimeException("Method generation error", e);
}
code.finish();
String codeStr = code.toString();
if (codeStr.isEmpty()) {
return Collections.emptyList();
}
String[] lines = codeStr.split(CodeWriter.NL);
Map<Integer, Integer> lineMapping = code.getLineMapping();
Map<CodePosition, Object> annotations = code.getAnnotations();
long mthCodeOffset = mth.getMethodCodeOffset() + 16;
int linesCount = lines.length;
List<JsonCodeLine> codeLines = new ArrayList<>(linesCount);
for (int i = 0; i < linesCount; i++) {
String codeLine = lines[i];
int line = i + 2;
JsonCodeLine jsonCodeLine = new JsonCodeLine();
jsonCodeLine.setCode(codeLine);
jsonCodeLine.setSourceLine(lineMapping.get(line));
Object obj = annotations.get(new CodePosition(line, 0));
if (obj instanceof InsnNode) {
long offset = ((InsnNode) obj).getOffset();
jsonCodeLine.setOffset("0x" + Long.toHexString(mthCodeOffset + offset * 2));
}
codeLines.add(jsonCodeLine);
}
return codeLines;
}
private String getTypeAlias(ArgType clsType) {
if (Objects.equals(clsType, ArgType.OBJECT)) {
return ArgType.OBJECT.getObject();
}
if (clsType.isObject()) {
ClassInfo classInfo = ClassInfo.fromType(root, clsType);
return classInfo.getAliasFullName();
}
return clsType.toString();
}
private String getClassTypeStr(ClassNode cls) {
if (cls.isEnum()) {
return "enum";
}
if (cls.getAccessFlags().isInterface()) {
return "interface";
}
return "class";
}
}
@@ -0,0 +1,107 @@
package jadx.core.codegen.json;
import java.io.File;
import java.io.FileWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import jadx.api.JadxArgs;
import jadx.core.codegen.json.mapping.JsonClsMapping;
import jadx.core.codegen.json.mapping.JsonFieldMapping;
import jadx.core.codegen.json.mapping.JsonMapping;
import jadx.core.codegen.json.mapping.JsonMthMapping;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.MethodInfo;
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.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.FileUtils;
public class JsonMappingGen {
private static final Logger LOG = LoggerFactory.getLogger(JsonMappingGen.class);
private static final Gson GSON = new GsonBuilder()
.setPrettyPrinting()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_DASHES)
.disableHtmlEscaping()
.create();
public static void dump(RootNode root) {
JsonMapping mapping = new JsonMapping();
fillMapping(mapping, root);
JadxArgs args = root.getArgs();
File outDirSrc = args.getOutDirSrc().getAbsoluteFile();
File mappingFile = new File(outDirSrc, "mapping.json");
FileUtils.makeDirsForFile(mappingFile);
try (Writer writer = new FileWriter(mappingFile)) {
GSON.toJson(mapping, writer);
LOG.info("Save mappings to {}", mappingFile.getAbsolutePath());
} catch (Exception e) {
throw new JadxRuntimeException("Failed to save mapping json", e);
}
}
private static void fillMapping(JsonMapping mapping, RootNode root) {
List<ClassNode> classes = root.getClasses(true);
mapping.setClasses(new ArrayList<>(classes.size()));
for (ClassNode cls : classes) {
ClassInfo classInfo = cls.getClassInfo();
JsonClsMapping jsonCls = new JsonClsMapping();
jsonCls.setName(classInfo.getRawName());
jsonCls.setAlias(classInfo.getAliasFullName());
jsonCls.setInner(classInfo.isInner());
jsonCls.setJson(cls.getTopParentClass().getClassInfo().getAliasFullPath() + ".json");
if (classInfo.isInner()) {
jsonCls.setTopClass(cls.getTopParentClass().getClassInfo().getFullName());
}
addFields(cls, jsonCls);
addMethods(cls, jsonCls);
mapping.getClasses().add(jsonCls);
}
}
private static void addMethods(ClassNode cls, JsonClsMapping jsonCls) {
List<MethodNode> methods = cls.getMethods();
if (methods.isEmpty()) {
return;
}
jsonCls.setMethods(new ArrayList<>(methods.size()));
for (MethodNode method : methods) {
JsonMthMapping jsonMethod = new JsonMthMapping();
MethodInfo methodInfo = method.getMethodInfo();
jsonMethod.setSignature(methodInfo.getShortId());
jsonMethod.setName(methodInfo.getName());
jsonMethod.setAlias(methodInfo.getAlias());
jsonMethod.setOffset("0x" + Long.toHexString(method.getMethodCodeOffset()));
jsonCls.getMethods().add(jsonMethod);
}
}
private static void addFields(ClassNode cls, JsonClsMapping jsonCls) {
List<FieldNode> fields = cls.getFields();
if (fields.isEmpty()) {
return;
}
jsonCls.setFields(new ArrayList<>(fields.size()));
for (FieldNode field : fields) {
JsonFieldMapping jsonField = new JsonFieldMapping();
jsonField.setName(field.getName());
jsonField.setAlias(field.getAlias());
jsonCls.getFields().add(jsonField);
}
}
private JsonMappingGen() {
}
}
@@ -0,0 +1,94 @@
package jadx.core.codegen.json.cls;
import java.util.List;
import com.google.gson.annotations.SerializedName;
public class JsonClass extends JsonNode {
@SerializedName("package")
private String pkg;
private String type; // class, interface, enum
@SerializedName("extends")
private String superClass;
@SerializedName("implements")
private List<String> interfaces;
private String dex;
private List<JsonField> fields;
private List<JsonMethod> methods;
private List<JsonClass> innerClasses;
private List<String> imports;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getSuperClass() {
return superClass;
}
public void setSuperClass(String superClass) {
this.superClass = superClass;
}
public List<String> getInterfaces() {
return interfaces;
}
public void setInterfaces(List<String> interfaces) {
this.interfaces = interfaces;
}
public List<JsonField> getFields() {
return fields;
}
public void setFields(List<JsonField> fields) {
this.fields = fields;
}
public List<JsonMethod> getMethods() {
return methods;
}
public void setMethods(List<JsonMethod> methods) {
this.methods = methods;
}
public List<JsonClass> getInnerClasses() {
return innerClasses;
}
public void setInnerClasses(List<JsonClass> innerClasses) {
this.innerClasses = innerClasses;
}
public String getPkg() {
return pkg;
}
public void setPkg(String pkg) {
this.pkg = pkg;
}
public String getDex() {
return dex;
}
public void setDex(String dex) {
this.dex = dex;
}
public List<String> getImports() {
return imports;
}
public void setImports(List<String> imports) {
this.imports = imports;
}
}
@@ -0,0 +1,33 @@
package jadx.core.codegen.json.cls;
import org.jetbrains.annotations.Nullable;
public class JsonCodeLine {
private String code;
private String offset;
private Integer sourceLine;
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getOffset() {
return offset;
}
public void setOffset(String offset) {
this.offset = offset;
}
public Integer getSourceLine() {
return sourceLine;
}
public void setSourceLine(@Nullable Integer sourceLine) {
this.sourceLine = sourceLine;
}
}
@@ -0,0 +1,5 @@
package jadx.core.codegen.json.cls;
public class JsonField extends JsonNode {
String type;
}
@@ -0,0 +1,51 @@
package jadx.core.codegen.json.cls;
import java.util.List;
public class JsonMethod extends JsonNode {
private String signature;
private String returnType;
private List<String> arguments;
private List<JsonCodeLine> lines;
private String offset;
public String getSignature() {
return signature;
}
public void setSignature(String signature) {
this.signature = signature;
}
public String getReturnType() {
return returnType;
}
public void setReturnType(String returnType) {
this.returnType = returnType;
}
public List<String> getArguments() {
return arguments;
}
public void setArguments(List<String> arguments) {
this.arguments = arguments;
}
public List<JsonCodeLine> getLines() {
return lines;
}
public void setLines(List<JsonCodeLine> lines) {
this.lines = lines;
}
public String getOffset() {
return offset;
}
public void setOffset(String offset) {
this.offset = offset;
}
}
@@ -0,0 +1,40 @@
package jadx.core.codegen.json.cls;
public class JsonNode {
private String name;
private String alias;
private String declaration;
private int accessFlags;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAlias() {
return alias;
}
public void setAlias(String alias) {
this.alias = alias;
}
public String getDeclaration() {
return declaration;
}
public void setDeclaration(String declaration) {
this.declaration = declaration;
}
public int getAccessFlags() {
return accessFlags;
}
public void setAccessFlags(int accessFlags) {
this.accessFlags = accessFlags;
}
}
@@ -0,0 +1,71 @@
package jadx.core.codegen.json.mapping;
import java.util.List;
public class JsonClsMapping {
private String name;
private String alias;
private String json;
private boolean inner;
private String topClass;
private List<JsonFieldMapping> fields;
private List<JsonMthMapping> methods;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAlias() {
return alias;
}
public void setAlias(String alias) {
this.alias = alias;
}
public String getJson() {
return json;
}
public void setJson(String json) {
this.json = json;
}
public boolean isInner() {
return inner;
}
public void setInner(boolean inner) {
this.inner = inner;
}
public String getTopClass() {
return topClass;
}
public void setTopClass(String topClass) {
this.topClass = topClass;
}
public List<JsonFieldMapping> getFields() {
return fields;
}
public void setFields(List<JsonFieldMapping> fields) {
this.fields = fields;
}
public List<JsonMthMapping> getMethods() {
return methods;
}
public void setMethods(List<JsonMthMapping> methods) {
this.methods = methods;
}
}
@@ -0,0 +1,22 @@
package jadx.core.codegen.json.mapping;
public class JsonFieldMapping {
private String name;
private String alias;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAlias() {
return alias;
}
public void setAlias(String alias) {
this.alias = alias;
}
}
@@ -0,0 +1,15 @@
package jadx.core.codegen.json.mapping;
import java.util.List;
public class JsonMapping {
private List<JsonClsMapping> classes;
public List<JsonClsMapping> getClasses() {
return classes;
}
public void setClasses(List<JsonClsMapping> classes) {
this.classes = classes;
}
}
@@ -0,0 +1,40 @@
package jadx.core.codegen.json.mapping;
public class JsonMthMapping {
private String signature;
private String name;
private String alias;
private String offset;
public String getSignature() {
return signature;
}
public void setSignature(String signature) {
this.signature = signature;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAlias() {
return alias;
}
public void setAlias(String alias) {
this.alias = alias;
}
public String getOffset() {
return offset;
}
public void setOffset(String offset) {
this.offset = offset;
}
}
@@ -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,172 @@
package jadx.core.deobf;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
import static java.nio.charset.StandardCharsets.UTF_8;
class DeobfPresets {
private static final Logger LOG = LoggerFactory.getLogger(DeobfPresets.class);
private static final Charset MAP_FILE_CHARSET = UTF_8;
private final Deobfuscator deobfuscator;
private final Path deobfMapFile;
private final Map<String, String> clsPresetMap = new HashMap<>();
private final Map<String, String> fldPresetMap = new HashMap<>();
private final Map<String, String> mthPresetMap = new HashMap<>();
public DeobfPresets(Deobfuscator deobfuscator, Path deobfMapFile) {
this.deobfuscator = deobfuscator;
this.deobfMapFile = deobfMapFile;
}
/**
* Loads deobfuscator presets
*/
public void load() {
if (!Files.exists(deobfMapFile)) {
return;
}
LOG.info("Loading obfuscation map from: {}", deobfMapFile.toAbsolutePath());
try {
List<String> lines = Files.readAllLines(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.toAbsolutePath(), 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 (Files.exists(deobfMapFile)) {
if (forceSave) {
dumpMapping();
} else {
LOG.warn("Deobfuscation map file '{}' exists. Use command line option '--deobf-rewrite-cfg' to rewrite it",
deobfMapFile.toAbsolutePath());
}
} else {
dumpMapping();
}
} catch (IOException e) {
LOG.error("Failed to load deobfuscation map file '{}'", deobfMapFile.toAbsolutePath(), e);
}
}
/**
* Saves DefaultDeobfuscator presets
*/
private void dumpMapping() throws IOException {
List<String> list = new ArrayList<>();
// 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().makeRawFullName(), deobfClsInfo.getAlias()));
}
}
for (FieldInfo fld : deobfuscator.getFldMap().keySet()) {
list.add(String.format("f %s = %s", fld.getRawFullId(), fld.getAlias()));
}
for (MethodInfo mth : deobfuscator.getMthMap().keySet()) {
list.add(String.format("m %s = %s", mth.getRawFullId(), mth.getAlias()));
}
Collections.sort(list);
Files.write(deobfMapFile, list, MAP_FILE_CHARSET);
if (LOG.isDebugEnabled()) {
LOG.debug("Deobfuscation map file saved as: {}", deobfMapFile);
}
}
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.makeRawFullName());
}
public String getForFld(FieldInfo fld) {
return fldPresetMap.get(fld.getRawFullId());
}
public String getForMth(MethodInfo mth) {
return mthPresetMap.get(mth.getRawFullId());
}
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,614 @@
package jadx.core.deobf;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs;
import jadx.core.dex.attributes.AFlag;
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;
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 JadxArgs args;
@NotNull
private final List<DexNode> dexNodes;
private final DeobfPresets deobfPresets;
private final Map<ClassInfo, DeobfClsInfo> clsMap = new LinkedHashMap<>();
private final Map<FieldInfo, String> fldMap = new HashMap<>();
private final Map<MethodInfo, String> mthMap = new HashMap<>();
private final Map<MethodInfo, OverridedMethodsNode> ovrdMap = new HashMap<>();
private final List<OverridedMethodsNode> ovrd = new ArrayList<>();
private final PackageNode rootPackage = new PackageNode("");
private final Set<String> pkgSet = new TreeSet<>();
private final Set<String> reservedClsNames = new HashSet<>();
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(JadxArgs args, @NotNull List<DexNode> dexNodes, Path deobfMapFile) {
this.args = args;
this.dexNodes = dexNodes;
this.minLength = args.getDeobfuscationMinLength();
this.maxLength = args.getDeobfuscationMaxLength();
this.useSourceNameAsAlias = args.isUseSourceNameAsClassAlias();
this.deobfPresets = new DeobfPresets(this, deobfMapFile);
}
public void execute() {
if (!args.isDeobfuscationForceSave()) {
deobfPresets.load();
initIndexes();
}
process();
}
public void savePresets() {
deobfPresets.save(args.isDeobfuscationForceSave());
}
public void clear() {
deobfPresets.clear();
clsMap.clear();
fldMap.clear();
mthMap.clear();
ovrd.clear();
ovrdMap.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()) {
Collections.addAll(reservedClsNames, cls.getPackage().split("\\."));
}
}
for (DexNode dexNode : dexNodes) {
for (ClassNode cls : dexNode.getClasses()) {
preProcessClass(cls);
}
}
}
private void process() {
preProcess();
if (DEBUG) {
dumpAlias();
}
for (DexNode dexNode : dexNodes) {
for (ClassNode cls : dexNode.getClasses()) {
processClass(cls);
}
}
postProcess();
}
private void postProcess() {
int id = 1;
for (OverridedMethodsNode o : ovrd) {
boolean aliasFromPreset = false;
String aliasToUse = null;
for (MethodInfo mth : o.getMethods()) {
if (mth.isAliasFromPreset()) {
aliasToUse = mth.getAlias();
aliasFromPreset = true;
}
}
for (MethodInfo mth : o.getMethods()) {
if (aliasToUse == null) {
if (mth.hasAlias() && !mth.isAliasFromPreset()) {
mth.setAlias(String.format("mo%d%s", id, prepareNamePart(mth.getName())));
}
aliasToUse = mth.getAlias();
}
mth.setAlias(aliasToUse);
mth.setAliasFromPreset(aliasFromPreset);
}
id++;
}
}
private void resolveOverriding(MethodNode mth) {
Set<ClassNode> clsParents = new LinkedHashSet<>();
collectClassHierarchy(mth.getParentClass(), clsParents);
String mthSignature = mth.getMethodInfo().makeSignature(false);
Set<MethodInfo> overrideSet = new LinkedHashSet<>();
for (ClassNode classNode : clsParents) {
MethodInfo methodInfo = getMthOverride(classNode.getMethods(), mthSignature);
if (methodInfo != null) {
overrideSet.add(methodInfo);
}
}
if (overrideSet.isEmpty()) {
return;
}
OverridedMethodsNode overrideNode = getOverrideMethodsNode(overrideSet);
if (overrideNode == null) {
overrideNode = new OverridedMethodsNode(overrideSet);
ovrd.add(overrideNode);
}
for (MethodInfo overrideMth : overrideSet) {
if (!ovrdMap.containsKey(overrideMth)) {
ovrdMap.put(overrideMth, overrideNode);
overrideNode.add(overrideMth);
}
}
}
private OverridedMethodsNode getOverrideMethodsNode(Set<MethodInfo> overrideSet) {
for (MethodInfo overrideMth : overrideSet) {
OverridedMethodsNode node = ovrdMap.get(overrideMth);
if (node != null) {
return node;
}
}
return null;
}
private MethodInfo getMthOverride(List<MethodNode> methods, String mthSignature) {
for (MethodNode m : methods) {
MethodInfo mthInfo = m.getMethodInfo();
if (mthInfo.getShortId().startsWith(mthSignature)) {
return mthInfo;
}
}
return null;
}
private void collectClassHierarchy(ClassNode cls, Set<ClassNode> collected) {
boolean added = collected.add(cls);
if (added) {
ArgType superClass = cls.getSuperClass();
if (superClass != null) {
ClassNode superNode = cls.dex().resolveClass(superClass);
if (superNode != null) {
collectClassHierarchy(superNode, collected);
}
}
for (ArgType argType : cls.getInterfaces()) {
ClassNode interfaceNode = cls.dex().resolveClass(argType);
if (interfaceNode != null) {
collectClassHierarchy(interfaceNode, collected);
}
}
}
}
private void processClass(ClassNode cls) {
if (isR(cls.getParentClass())) {
return;
}
ClassInfo clsInfo = cls.getClassInfo();
DeobfClsInfo deobfClsInfo = clsMap.get(clsInfo);
if (deobfClsInfo != null) {
clsInfo.changeShortName(deobfClsInfo.getAlias());
PackageNode pkgNode = deobfClsInfo.getPkg();
if (!clsInfo.isInner() && pkgNode.hasAnyAlias()) {
clsInfo.changePkg(pkgNode.getFullAlias());
}
} else if (!clsInfo.isInner()) {
// check if package renamed
PackageNode pkgNode = getPackageNode(clsInfo.getPackage(), false);
if (pkgNode != null && pkgNode.hasAnyAlias()) {
clsInfo.changePkg(pkgNode.getFullAlias());
}
}
for (FieldNode field : cls.getFields()) {
if (field.contains(AFlag.DONT_RENAME)) {
continue;
}
renameField(field);
}
for (MethodNode mth : cls.getMethods()) {
renameMethod(mth);
}
for (ClassNode innerCls : cls.getInnerClasses()) {
processClass(innerCls);
}
}
private void renameField(FieldNode field) {
FieldInfo fieldInfo = field.getFieldInfo();
String alias = getFieldAlias(field);
if (alias != null) {
fieldInfo.setAlias(alias);
}
}
public void forceRenameField(FieldNode field) {
field.getFieldInfo().setAlias(makeFieldAlias(field));
}
private void renameMethod(MethodNode mth) {
String alias = getMethodAlias(mth);
if (alias != null) {
mth.getMethodInfo().setAlias(alias);
}
if (mth.isVirtual()) {
resolveOverriding(mth);
}
}
public void forceRenameMethod(MethodNode mth) {
mth.getMethodInfo().setAlias(makeMethodAlias(mth));
if (mth.isVirtual()) {
resolveOverriding(mth);
}
}
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 preProcessClass(ClassNode cls) {
ClassInfo classInfo = cls.getClassInfo();
String pkgFullName = classInfo.getPackage();
PackageNode pkg = getPackageNode(pkgFullName, true);
processPackageFull(pkg, pkgFullName);
String alias = deobfPresets.getForCls(classInfo);
if (alias != null) {
clsMap.put(classInfo, new DeobfClsInfo(this, cls, pkg, alias));
} else {
if (!clsMap.containsKey(classInfo)) {
String clsShortName = classInfo.getShortName();
if (shouldRename(clsShortName) || reservedClsNames.contains(clsShortName)) {
makeClsAlias(cls);
}
}
}
for (ClassNode innerCls : cls.getInnerClasses()) {
preProcessClass(innerCls);
}
}
public String getClsAlias(ClassNode cls) {
DeobfClsInfo deobfClsInfo = clsMap.get(cls.getClassInfo());
if (deobfClsInfo != null) {
return deobfClsInfo.getAlias();
}
return makeClsAlias(cls);
}
public String getPkgAlias(ClassNode cls) {
ClassInfo classInfo = cls.getClassInfo();
PackageNode pkg = null;
DeobfClsInfo deobfClsInfo = clsMap.get(classInfo);
if (deobfClsInfo != null) {
pkg = deobfClsInfo.getPkg();
} else {
String fullPkgName = classInfo.getPackage();
pkg = getPackageNode(fullPkgName, true);
processPackageFull(pkg, fullPkgName);
}
if (pkg.hasAnyAlias()) {
return pkg.getFullAlias();
} else {
return pkg.getFullName();
}
}
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++, prepareNamePart(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;
}
if (cls.getClassInfo().isInner()) {
return null;
}
String name = sourceFileAttr.getFileName();
if (name.endsWith(".java")) {
name = name.substring(0, name.length() - ".java".length());
} else if (name.endsWith(".kt")) {
name = name.substring(0, name.length() - ".kt".length());
}
if (!NameMapper.isValidAndPrintable(name)) {
return null;
}
for (DeobfClsInfo deobfClsInfo : clsMap.values()) {
if (deobfClsInfo.getAlias().equals(name)) {
return null;
}
}
ClassNode otherCls = cls.root().searchClassByName(cls.getPackage() + '.' + name);
if (otherCls != null) {
return null;
}
cls.remove(AType.SOURCE_FILE);
return name;
}
@Nullable
private 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
private String getMethodAlias(MethodNode mth) {
MethodInfo methodInfo = mth.getMethodInfo();
if (methodInfo.isClassInit() || methodInfo.isConstructor()) {
return null;
}
String alias = mthMap.get(methodInfo);
if (alias != null) {
return alias;
}
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++, prepareNamePart(field.getName()));
fldMap.put(field.getFieldInfo(), alias);
return alias;
}
public String makeMethodAlias(MethodNode mth) {
String alias = String.format("m%d%s", mthIndex++, prepareNamePart(mth.getName()));
mthMap.put(mth.getMethodInfo(), alias);
return alias;
}
private void processPackageFull(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()) {
processPackageFull(parentPkg, parentPkg.getFullName());
}
parentPkg = parentPkg.getParentPackage();
}
if (!pkg.hasAlias()) {
String pkgName = pkg.getName();
if ((args.isDeobfuscationOn() && shouldRename(pkgName))
|| (args.isRenameValid() && !NameMapper.isValidIdentifier(pkgName))
|| (args.isRenamePrintable() && !NameMapper.isAllCharsPrintable(pkgName))) {
String pkgAlias = String.format("p%03d%s", pkgIndex++, prepareNamePart(pkg.getName()));
pkg.setAlias(pkgAlias);
}
}
}
private boolean shouldRename(String s) {
int len = s.length();
return len < minLength || len > maxLength;
}
private String prepareNamePart(String name) {
if (name.length() > maxLength) {
return 'x' + Integer.toHexString(name.hashCode());
}
return NameMapper.removeInvalidCharsMiddle(name);
}
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) {
PackageNode pkg = getPackageNode(packageName, false);
if (pkg != null) {
return pkg.getFullAlias();
}
return packageName;
}
private String getClassName(ClassInfo clsInfo) {
DeobfClsInfo deobfClsInfo = clsMap.get(clsInfo);
if (deobfClsInfo != null) {
return deobfClsInfo.makeNameWithoutPkg();
}
return getNameWithoutPackage(clsInfo);
}
private String getClassFullName(ClassNode cls) {
ClassInfo clsInfo = cls.getClassInfo();
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;
}
private static boolean isR(ClassNode cls) {
if (!cls.getClassInfo().getShortName().equals("R")) {
return false;
}
if (!cls.getMethods().isEmpty() || !cls.getFields().isEmpty()) {
return false;
}
for (ClassNode inner : cls.getInnerClasses()) {
for (MethodNode m : inner.getMethods()) {
if (!m.getMethodInfo().isConstructor() && !m.getMethodInfo().isClassInit()) {
return false;
}
}
for (FieldNode field : cls.getFields()) {
ArgType type = field.getType();
if (type != ArgType.INT && (!type.isArray() || type.getArrayElement() != ArgType.INT)) {
return false;
}
}
}
return true;
}
}
@@ -3,11 +3,20 @@ package jadx.core.deobf;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Pattern;
import static jadx.core.utils.StringUtils.notEmpty;
public class NameMapper {
private static final Set<String> RESERVED_NAMES = new HashSet<String>(
Arrays.asList(new String[]{
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<>(
Arrays.asList(
"abstract",
"assert",
"boolean",
@@ -60,12 +69,97 @@ public class NameMapper {
"try",
"void",
"volatile",
"while",
})
);
"while"));
public static boolean isReserved(String str) {
return RESERVED_NAMES.contains(str);
}
public static boolean isValidIdentifier(String str) {
return notEmpty(str)
&& !isReserved(str)
&& VALID_JAVA_IDENTIFIER.matcher(str).matches();
}
public static boolean isValidFullIdentifier(String str) {
return notEmpty(str)
&& !isReserved(str)
&& VALID_JAVA_FULL_IDENTIFIER.matcher(str).matches();
}
public static boolean isValidAndPrintable(String str) {
return isValidIdentifier(str) && isAllCharsPrintable(str);
}
public static boolean isValidIdentifierStart(int codePoint) {
return Character.isJavaIdentifierStart(codePoint);
}
public static boolean isValidIdentifierPart(int codePoint) {
return Character.isJavaIdentifierPart(codePoint);
}
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;
}
/**
* Return modified string with removed:
* <p>
* <ul>
* <li>not printable chars (including unicode)
* <li>chars not valid for java identifier part
* </ul>
* <p>
* Note: this 'middle' method must be used with prefixed string:
* <p>
* <ul>
* <li>can leave invalid chars for java identifier start (i.e numbers)
* <li>result not checked for reserved words
* </ul>
* <p>
*/
public static String removeInvalidCharsMiddle(String name) {
if (isValidIdentifier(name) && isAllCharsPrintable(name)) {
return name;
}
int len = name.length();
StringBuilder sb = new StringBuilder(len);
for (int i = 0; i < len; i++) {
int codePoint = name.codePointAt(i);
if (isPrintableChar(codePoint) && isValidIdentifierPart(codePoint)) {
sb.append((char) codePoint);
}
}
return sb.toString();
}
/**
* Return string with removed invalid chars, see {@link #removeInvalidCharsMiddle}
* <p>
* Prepend prefix if first char is not valid as java identifier start char.
*/
public static String removeInvalidChars(String name, String prefix) {
String result = removeInvalidCharsMiddle(name);
if (!result.isEmpty()) {
int codePoint = result.codePointAt(0);
if (!isValidIdentifierStart(codePoint)) {
return prefix + result;
}
}
return result;
}
private NameMapper() {
}
}
@@ -0,0 +1,26 @@
package jadx.core.deobf;
import java.util.Set;
import jadx.core.dex.info.MethodInfo;
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,145 @@
package jadx.core.deobf;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.List;
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) {
Deque<PackageNode> pp = getParentPackages();
StringBuilder result = new StringBuilder();
result.append(pp.pop().getName());
while (!pp.isEmpty()) {
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 boolean hasAnyAlias() {
if (hasAlias()) {
return true;
}
if (parentPackage != this) {
return parentPackage.hasAnyAlias();
}
return false;
}
public String getFullAlias() {
if (cachedPackageFullAlias == null) {
Deque<PackageNode> pp = getParentPackages();
StringBuilder result = new StringBuilder();
if (!pp.isEmpty()) {
result.append(pp.pop().getAlias());
while (!pp.isEmpty()) {
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<>();
}
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 Deque<PackageNode> getParentPackages() {
Deque<PackageNode> pp = new ArrayDeque<>();
PackageNode currentPkg = this;
PackageNode parentPkg = currentPkg.getParentPackage();
while (currentPkg != parentPkg) {
pp.push(currentPkg);
currentPkg = parentPkg;
parentPkg = currentPkg.getParentPackage();
}
return pp;
}
@Override
public String toString() {
return packageAlias;
}
}
@@ -1,6 +1,7 @@
package jadx.core.dex.attributes;
public enum AFlag {
MTH_ENTER_BLOCK,
TRY_ENTER,
TRY_LEAVE,
@@ -10,18 +11,61 @@ public enum AFlag {
SYNTHETIC,
RETURN, // block contains only return instruction
ORIG_RETURN,
DECLARE_VAR,
DONT_WRAP,
DONT_INLINE,
DONT_GENERATE, // process as usual, but don't output to generated code
COMMENT_OUT, // process as usual, but comment insn in generated code
REMOVE, // can be completely removed
DONT_SHRINK,
DONT_GENERATE,
SKIP,
HIDDEN, // instruction used inside other instruction but not listed in args
RESTART_CODEGEN,
DONT_RENAME, // do not rename during deobfuscation
ADDED_TO_REGION,
FINALLY_INSNS,
SKIP_FIRST_ARG,
SKIP_ARG, // skip argument in invoke call
ANONYMOUS_CONSTRUCTOR,
ANONYMOUS_CLASS,
THIS,
/**
* RegisterArg attribute for method arguments
*/
METHOD_ARGUMENT,
/**
* Type of RegisterArg or SSAVar can't be changed
*/
IMMUTABLE_TYPE,
/**
* Force inline instruction with inline assign
*/
FORCE_ASSIGN_INLINE,
CUSTOM_DECLARE, // variable for this register don't need declaration
DECLARE_VAR,
ELSE_IF_CHAIN,
WRAPPED,
ARITH_ONEARG,
FALL_THROUGH,
EXPLICIT_GENERICS,
/**
* Use constants with explicit type: cast '(byte) 1' or type letter '7L'
*/
EXPLICIT_PRIMITIVE_TYPE,
EXPLICIT_CAST,
INCONSISTENT_CODE, // warning about incorrect decompilation
}
@@ -1,45 +1,90 @@
package jadx.core.dex.attributes;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
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.JadxErrorAttr;
import jadx.core.dex.attributes.nodes.IgnoreEdgeAttr;
import jadx.core.dex.attributes.nodes.JadxError;
import jadx.core.dex.attributes.nodes.JumpInfo;
import jadx.core.dex.attributes.nodes.LocalVarsDebugInfoAttr;
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.RegDebugInfoAttr;
import jadx.core.dex.attributes.nodes.RenameReasonAttr;
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
import jadx.core.dex.attributes.nodes.SourceFileAttr;
import jadx.core.dex.nodes.parser.FieldValueAttr;
import jadx.core.dex.nodes.parser.FieldInitAttr;
import jadx.core.dex.trycatch.CatchAttr;
import jadx.core.dex.trycatch.ExcHandlerAttr;
import jadx.core.dex.trycatch.SplitterBlockAttr;
/**
* Attribute types enumeration,
* uses generic type for omit cast after in 'AttributeStorage.get' method
* uses generic type for omit cast after 'AttributeStorage.get' method
*
* @param <T> attribute class implementation
*/
public class AType<T extends IAttribute> {
public static final AType<AttrList<JumpInfo>> JUMP = new AType<AttrList<JumpInfo>>();
public static final AType<AttrList<LoopInfo>> LOOP = new AType<AttrList<LoopInfo>>();
// class, method, field
public static final AType<AnnotationsList> ANNOTATION_LIST = new AType<>();
public static final AType<RenameReasonAttr> RENAME_REASON = new AType<>();
public static final AType<ExcHandlerAttr> EXC_HANDLER = new AType<ExcHandlerAttr>();
public static final AType<CatchAttr> CATCH_BLOCK = new AType<CatchAttr>();
public static final AType<SplitterBlockAttr> SPLITTER_BLOCK = new AType<SplitterBlockAttr>();
public static final AType<ForceReturnAttr> FORCE_RETURN = new AType<ForceReturnAttr>();
public static final AType<FieldValueAttr> FIELD_VALUE = new AType<FieldValueAttr>();
public static final AType<FieldReplaceAttr> FIELD_REPLACE = new AType<FieldReplaceAttr>();
public static final AType<JadxErrorAttr> JADX_ERROR = new AType<JadxErrorAttr>();
public static final AType<MethodInlineAttr> METHOD_INLINE = new AType<MethodInlineAttr>();
public static final AType<EnumClassAttr> ENUM_CLASS = new AType<EnumClassAttr>();
public static final AType<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>();
// class, method
public static final AType<AttrList<JadxError>> JADX_ERROR = new AType<>(); // code failed to decompile completely
public static final AType<AttrList<String>> JADX_WARN = new AType<>(); // mark code as inconsistent (code can be viewed)
public static final AType<AttrList<String>> COMMENTS = new AType<>(); // any additional info about decompilation
// class
public static final AType<SourceFileAttr> SOURCE_FILE = new AType<>();
public static final AType<EnumClassAttr> ENUM_CLASS = new AType<>();
public static final AType<EnumMapAttr> ENUM_MAP = new AType<>();
// field
public static final AType<FieldInitAttr> FIELD_INIT = new AType<>();
public static final AType<FieldReplaceAttr> FIELD_REPLACE = new AType<>();
// method
public static final AType<LocalVarsDebugInfoAttr> LOCAL_VARS_DEBUG_INFO = new AType<>();
public static final AType<MethodInlineAttr> METHOD_INLINE = new AType<>();
public static final AType<MethodParameters> ANNOTATION_MTH_PARAMETERS = new AType<>();
public static final AType<SkipMethodArgsAttr> SKIP_MTH_ARGS = new AType<>();
// region
public static final AType<DeclareVariablesAttr> DECLARE_VARIABLES = new AType<>();
// block
public static final AType<PhiListAttr> PHI_LIST = new AType<>();
public static final AType<IgnoreEdgeAttr> IGNORE_EDGE = new AType<>();
public static final AType<ForceReturnAttr> FORCE_RETURN = new AType<>();
public static final AType<CatchAttr> CATCH_BLOCK = new AType<>();
public static final AType<SplitterBlockAttr> SPLITTER_BLOCK = new AType<>();
public static final AType<AttrList<LoopInfo>> LOOP = new AType<>();
public static final AType<AttrList<EdgeInsnAttr>> EDGE_INSN = new AType<>();
// block or insn
public static final AType<ExcHandlerAttr> EXC_HANDLER = new AType<>();
// instruction
public static final AType<LoopLabelAttr> LOOP_LABEL = new AType<>();
public static final AType<AttrList<JumpInfo>> JUMP = new AType<>();
// register
public static final AType<RegDebugInfoAttr> REG_DEBUG_INFO = new AType<>();
public static final Set<AType<?>> SKIP_ON_UNLOAD = new HashSet<>(Arrays.asList(
FIELD_REPLACE,
METHOD_INLINE,
SKIP_MTH_ARGS));
}
@@ -1,14 +1,14 @@
package jadx.core.dex.attributes;
import jadx.core.utils.Utils;
import java.util.LinkedList;
import java.util.ArrayList;
import java.util.List;
import jadx.core.utils.Utils;
public class AttrList<T> implements IAttribute {
private final AType<AttrList<T>> type;
private final List<T> list = new LinkedList<T>();
private final List<T> list = new ArrayList<>();
public AttrList(AType<AttrList<T>> type) {
this.type = type;
@@ -25,6 +25,6 @@ public class AttrList<T> implements IAttribute {
@Override
public String toString() {
return Utils.listToString(list);
return Utils.listToString(list, "\n");
}
}
@@ -1,9 +1,9 @@
package jadx.core.dex.attributes;
import jadx.core.dex.attributes.annotations.Annotation;
import java.util.List;
import jadx.core.dex.attributes.annotations.Annotation;
public abstract class AttrNode implements IAttributeNode {
private static final AttributeStorage EMPTY_ATTR_STORAGE = new EmptyAttrStorage();
@@ -12,25 +12,28 @@ public abstract class AttrNode implements IAttributeNode {
@Override
public void add(AFlag flag) {
getStorage().add(flag);
initStorage().add(flag);
}
@Override
public void addAttr(IAttribute attr) {
getStorage().add(attr);
initStorage().add(attr);
}
@Override
public <T> void addAttr(AType<AttrList<T>> type, T obj) {
getStorage().add(type, obj);
initStorage().add(type, obj);
}
@Override
public void copyAttributesFrom(AttrNode attrNode) {
getStorage().addAll(attrNode.storage);
AttributeStorage copyFrom = attrNode.storage;
if (!copyFrom.isEmpty()) {
initStorage().addAll(copyFrom);
}
}
AttributeStorage getStorage() {
private AttributeStorage initStorage() {
AttributeStorage store = storage;
if (store == EMPTY_ATTR_STORAGE) {
store = new AttributeStorage();
@@ -39,6 +42,12 @@ public abstract class AttrNode implements IAttributeNode {
return store;
}
private void unloadIfEmpty() {
if (storage.isEmpty() && storage != EMPTY_ATTR_STORAGE) {
storage = EMPTY_ATTR_STORAGE;
}
}
@Override
public boolean contains(AFlag flag) {
return storage.contains(flag);
@@ -67,21 +76,36 @@ public abstract class AttrNode implements IAttributeNode {
@Override
public void remove(AFlag flag) {
storage.remove(flag);
unloadIfEmpty();
}
@Override
public <T extends IAttribute> void remove(AType<T> type) {
storage.remove(type);
unloadIfEmpty();
}
@Override
public void removeAttr(IAttribute attr) {
storage.remove(attr);
unloadIfEmpty();
}
@Override
public void clearAttributes() {
storage.clear();
unloadIfEmpty();
}
/**
* Remove all attribute with exceptions from {@link AType#SKIP_ON_UNLOAD}
*/
public void unloadAttributes() {
if (storage == EMPTY_ATTR_STORAGE) {
return;
}
storage.unloadAttributes();
unloadIfEmpty();
}
@Override
@@ -93,4 +117,8 @@ public abstract class AttrNode implements IAttributeNode {
public String getAttributesString() {
return storage.toString();
}
public boolean isAttrStorageEmpty() {
return storage.isEmpty();
}
}
@@ -1,17 +1,17 @@
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.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import jadx.core.dex.attributes.annotations.Annotation;
import jadx.core.dex.attributes.annotations.AnnotationsList;
import jadx.core.utils.Utils;
/**
* Storage for different attribute types:
* 1. flags - boolean attribute (set or not)
@@ -20,11 +20,11 @@ import java.util.Set;
public class AttributeStorage {
private final Set<AFlag> flags;
private final Map<AType<?>, IAttribute> attributes;
private Map<AType<?>, IAttribute> attributes;
public AttributeStorage() {
flags = EnumSet.noneOf(AFlag.class);
attributes = new HashMap<AType<?>, IAttribute>(2);
attributes = Collections.emptyMap();
}
public void add(AFlag flag) {
@@ -32,13 +32,13 @@ public class AttributeStorage {
}
public void add(IAttribute attr) {
attributes.put(attr.getType(), attr);
writeAttributes().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);
list = new AttrList<>(type);
add(list);
}
list.getList().add(obj);
@@ -46,7 +46,7 @@ public class AttributeStorage {
public void addAll(AttributeStorage otherList) {
flags.addAll(otherList.flags);
attributes.putAll(otherList.attributes);
writeAttributes().putAll(otherList.attributes);
}
public boolean contains(AFlag flag) {
@@ -72,7 +72,7 @@ public class AttributeStorage {
if (attrList == null) {
return Collections.emptyList();
}
return attrList.getList();
return Collections.unmodifiableList(attrList.getList());
}
public void remove(AFlag flag) {
@@ -80,20 +80,41 @@ public class AttributeStorage {
}
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) {
if (!attributes.isEmpty()) {
attributes.remove(type);
}
}
public void remove(IAttribute attr) {
if (!attributes.isEmpty()) {
AType<? extends IAttribute> type = attr.getType();
IAttribute a = attributes.get(type);
if (a == attr) {
attributes.remove(type);
}
}
}
private Map<AType<?>, IAttribute> writeAttributes() {
if (attributes.isEmpty()) {
attributes = new IdentityHashMap<>(5);
}
return attributes;
}
public void clear() {
flags.clear();
attributes.clear();
if (!attributes.isEmpty()) {
attributes.clear();
}
}
public synchronized void unloadAttributes() {
if (attributes.isEmpty()) {
return;
}
Set<AType<?>> skipOnUnload = AType.SKIP_ON_UNLOAD;
attributes.keySet().removeIf(attrType -> !skipOnUnload.contains(attrType));
}
public List<String> getAttributeStrings() {
@@ -101,7 +122,7 @@ public class AttributeStorage {
if (size == 0) {
return Collections.emptyList();
}
List<String> list = new ArrayList<String>(size);
List<String> list = new ArrayList<>(size);
for (AFlag a : flags) {
list.add(a.toString());
}
@@ -111,12 +132,17 @@ public class AttributeStorage {
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) + "}";
list.sort(String::compareTo);
return "A[" + Utils.listToString(list) + ']';
}
}
@@ -1,11 +1,11 @@
package jadx.core.dex.attributes;
import jadx.core.dex.attributes.annotations.Annotation;
import java.util.Collections;
import java.util.List;
public class EmptyAttrStorage extends AttributeStorage {
import jadx.core.dex.attributes.annotations.Annotation;
public final class EmptyAttrStorage extends AttributeStorage {
@Override
public boolean contains(AFlag flag) {
@@ -34,22 +34,36 @@ public class EmptyAttrStorage extends AttributeStorage {
@Override
public void clear() {
// ignore
}
@Override
public void remove(AFlag flag) {
// ignore
}
@Override
public <T extends IAttribute> void remove(AType<T> type) {
// ignore
}
@Override
public void remove(IAttribute attr) {
// ignore
}
@Override
public List<String> getAttributeStrings() {
return Collections.emptyList();
}
@Override
public boolean isEmpty() {
return true;
}
@Override
public String toString() {
return "";
}
}
@@ -1,7 +1,5 @@
package jadx.core.dex.attributes;
public interface IAttribute {
AType<?> getType();
AType<? extends IAttribute> getType();
}
@@ -1,9 +1,9 @@
package jadx.core.dex.attributes;
import jadx.core.dex.attributes.annotations.Annotation;
import java.util.List;
import jadx.core.dex.attributes.annotations.Annotation;
public interface IAttributeNode {
void add(AFlag flag);
@@ -1,12 +1,12 @@
package jadx.core.dex.attributes.annotations;
import jadx.core.dex.instructions.args.ArgType;
import java.util.Map;
import jadx.core.dex.instructions.args.ArgType;
public class Annotation {
public static enum Visibility {
public enum Visibility {
BUILD, RUNTIME, SYSTEM
}
@@ -42,7 +42,6 @@ public class Annotation {
@Override
public String toString() {
return "Annotation[" + visibility + ", " + atype + ", " + values + "]";
return "Annotation[" + visibility + ", " + atype + ", " + values + ']';
}
}

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