Compare commits

..

243 Commits

Author SHA1 Message Date
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 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
Skylot c705f8cbff Merge branch 'master' into type-inference-wip 2019-01-23 11:09:47 +03:00
Skylot d8b39c2698 Merge branch 'master' into type-inference-wip 2019-01-16 22:29:39 +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
Skylot 43de744c88 fix: don't reject type update for generics 2018-12-26 21:41:36 +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 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 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 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
734 changed files with 23506 additions and 8518 deletions
+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,23 @@
---
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)
- [ ] search existing issues by exception message
(for example `JadxRuntimeException: Can't find immediate dominator for block`)
- [ ] check [Troubleshooting Q&A](https://github.com/skylot/jadx/wiki/Troubleshooting-Q&A) section on wiki
**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 :)
+2
View File
@@ -10,6 +10,7 @@ out/
*.ipr
*.iws
.attach_pid*
*.hprof
**/.DS_Store
@@ -29,3 +30,4 @@ jadx-output/
*.dump
*.log
*.cfg
*.orig
+23 -16
View File
@@ -1,25 +1,32 @@
image: java:8
variables:
GRADLE_OPTS: "-Dorg.gradle.daemon=false"
TERM: "dumb"
GRADLE_OPTS: "-Dorg.gradle.daemon=false"
TERM: "dumb"
before_script:
- chmod +x gradlew
- chmod +x gradlew
stages:
- build
- test
- check
build:
stage: build
before_script:
- export JADX_LAST_TAG="$(git describe --abbrev=0 --tags)"
- export JADX_VERSION="${JADX_LAST_TAG:1}-$(git rev-parse --short HEAD)"
java-8:
stage: test
image: openjdk:8
script: ./gradlew clean build
java-11:
stage: test
image: openjdk:11
script: ./gradlew clean build
check:
stage: check
image: openjdk:8
script:
- ./gradlew -g /cache/.gradle clean build jacocoTestReport
- ./gradlew -g /cache/.gradle clean sonarqube -Dsonar.host.url=$SONAR_HOST -Dsonar.organization=$SONAR_ORG -Dsonar.login=$SONAR_TOKEN
- ./gradlew -g /cache/.gradle clean dist
- 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.organization=$SONAR_ORG -Dsonar.login=$SONAR_TOKEN -Dsonar.branch.name=dev
- ./gradlew clean dist
artifacts:
paths:
- build/jadx*.zip
- build/jadx*.exe
- build/jadx*.zip
+21
View File
@@ -1,9 +1,23 @@
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"
@@ -12,3 +26,10 @@ publish:
- path: 'build/*.zip'
- path: 'build/*.exe'
success:
- path: '@semantic-release/github'
successComment: false
fail:
- path: '@semantic-release/github'
failComment: false
+17 -31
View File
@@ -12,8 +12,7 @@ before_install:
- chmod +x gradlew
# override install to skip 'gradle assemble'
install:
- true
install: true
env:
global:
@@ -21,34 +20,21 @@ env:
- JADX_LAST_TAG=$(git describe --abbrev=0 --tags)
- JADX_VERSION="${JADX_LAST_TAG:1}-b$TRAVIS_BUILD_NUMBER-$(git rev-parse --short HEAD)"
matrix:
jdk:
- openjdk8
- oraclejdk8
- openjdk11
script: ./gradlew clean build
jobs:
include:
- env: JDK=oracle-8
jdk: oraclejdk8
- env: JDK=openjdk11
jdk: openjdk11
- stage: deploy-unstable
jdk: openjdk8
if: branch = master AND repo = env(MAIN_REPO) AND type = push
script: bash scripts/travis-master.sh
script:
- java -version
- ./gradlew clean build
deploy:
- provider: script
skip_cleanup: true
on:
branch: master
tags: false
condition: $JDK = oracle-8
script: bash scripts/travis-master.sh
- provider: script
skip_cleanup: true
on:
branch: release
tags: false
condition: $JDK = oracle-8
script: bash scripts/travis-release.sh
notifications:
email:
- skylot@gmail.com
- stage: deploy-release
jdk: openjdk8
if: branch = release AND repo = env(MAIN_REPO) AND type = push
script: bash scripts/travis-release.sh
+36 -22
View File
@@ -42,6 +42,10 @@ JDK 8 or higher must be installed:
Scripts for run jadx will be placed in `build/jadx/bin`
and also packed to `build/jadx-<version>.zip`
### macOS
You can install using brew:
brew install jadx
### Run
Run **jadx** on itself:
@@ -54,31 +58,41 @@ Run **jadx** on itself:
### Usage
```
jadx[-gui] [options] <input file> (.dex, .apk, .jar or .class)
jadx[-gui] [options] <input file> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc)
options:
-d, --output-dir - output directory
-ds, --output-dir-src - output directory for sources
-dr, --output-dir-res - output directory for resources
-j, --threads-count - processing threads count
-r, --no-res - do not decode resources
-s, --no-src - do not decompile source code
-e, --export-gradle - save as android gradle project
--show-bad-code - show inconsistent code (incorrectly decompiled)
--no-imports - disable use of imports, always write entire package name
--no-replace-consts - don't replace constant value with matching constant field
--escape-unicode - escape non latin characters in strings (with \u)
--deobf - activate deobfuscation
--deobf-min - min length of name
--deobf-max - max length of name
--deobf-rewrite-cfg - force to save deobfuscation map
--deobf-use-sourcename - use source file name as class name alias
--cfg - save methods control flow graph to dot file
--raw-cfg - save methods control flow graph (use raw instructions)
-f, --fallback - make simple dump (using goto instead of 'if', 'for', etc)
-v, --verbose - verbose output
-h, --help - print this help
-d, --output-dir - output directory
-ds, --output-dir-src - output directory for sources
-dr, --output-dir-res - output directory for resources
-j, --threads-count - processing threads count
-r, --no-res - do not decode resources
-s, --no-src - do not decompile source code
--single-class - decompile a single class
--output-format - can be 'java' or 'json' (default: java)
-e, --export-gradle - save as android gradle project
--show-bad-code - show inconsistent code (incorrectly decompiled)
--no-imports - disable use of imports, always write entire package name
--no-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'
--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
--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
```
These options also worked on jadx-gui running from command line and override options from preferences dialog
+121 -79
View File
@@ -1,6 +1,9 @@
import com.diffplug.spotless.LineEnding
plugins {
id 'org.sonarqube' version '2.7'
id 'com.github.ben-manes.versions' version '0.20.0'
id 'org.sonarqube' version '2.7'
id 'com.github.ben-manes.versions' version '0.21.0'
id "com.diffplug.gradle.spotless" version "3.21.1"
}
ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev"
@@ -8,120 +11,159 @@ version = jadxVersion
println("jadx version: ${jadxVersion}")
allprojects {
apply plugin: 'java'
apply plugin: 'groovy'
apply plugin: 'jacoco'
apply plugin: 'java'
apply plugin: 'jacoco'
apply plugin: 'checkstyle'
version = jadxVersion
version = jadxVersion
tasks.withType(JavaCompile) {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
tasks.withType(JavaCompile) {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
if (!"$it".contains(':jadx-samples:')) {
options.compilerArgs << '-Xlint' << '-Xlint:unchecked' << '-Xlint:deprecation'
}
}
if (!"$it".contains(':jadx-samples:')) {
options.compilerArgs << '-Xlint' << '-Xlint:unchecked' << '-Xlint:deprecation'
}
}
compileJava {
options.encoding = "UTF-8"
}
compileJava {
options.encoding = "UTF-8"
}
jar {
version = jadxVersion
manifest {
mainAttributes('jadx-version': jadxVersion)
}
}
jar {
version = jadxVersion
manifest {
mainAttributes('jadx-version': jadxVersion)
}
}
dependencies {
compile 'org.slf4j:slf4j-api:1.7.25'
dependencies {
compile 'org.slf4j:slf4j-api:1.7.26'
testCompile 'ch.qos.logback:logback-classic:1.2.3'
testCompile 'junit:junit:4.12'
testCompile 'org.hamcrest:hamcrest-library:2.1'
testCompile 'org.mockito:mockito-core:2.23.4'
testCompile 'org.spockframework:spock-core:1.1-groovy-2.4'
}
testCompile 'ch.qos.logback:logback-classic:1.2.3'
testCompile 'org.hamcrest:hamcrest-library:2.1'
testCompile 'org.mockito:mockito-core:2.25.1'
repositories {
mavenLocal()
mavenCentral()
jcenter()
google()
}
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.4.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.4.1'
jacoco {
toolVersion = "0.8.2"
}
jacocoTestReport {
reports {
xml.enabled = true
html.enabled = true
}
}
testCompile 'org.eclipse.jdt.core.compiler:ecj:4.6.1'
}
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/**'
}
}
sonarqube {
properties {
property 'sonar.exclusions', '**/jadx/samples/**/*,**/test-app/**/*'
property 'sonar.coverage.exclusions', '**/jadx/gui/**/*'
}
properties {
property 'sonar.exclusions', '**/jadx/samples/**/*,**/test-app/**/*'
property 'sonar.coverage.exclusions', '**/jadx/gui/**/*'
}
}
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(LineEnding.UNIX)
encoding("UTF-8")
trimTrailingWhitespace()
endWithNewline()
}
format 'misc', {
target '**/*.gradle', '**/*.md', '**/*.xml', '**/.gitignore', '**/.properties'
targetExclude "jadx-test-app/test-app/**", ".gradle/**", ".idea/**"
lineEndings(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')
}
}
}
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
}
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 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'
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'
group 'jadx'
description = 'Build jadx distribution zip'
}
task samples(dependsOn: 'jadx-samples:samples') {
group 'jadx'
group 'jadx'
}
task testAppCheck(dependsOn: 'jadx-test-app:testAppCheck') {
group 'jadx'
group 'jadx'
}
task cleanBuildDir(type: Delete) {
group 'jadx'
delete buildDir
group 'jadx'
delete buildDir
}
build.dependsOn(dist, samples)
test.dependsOn(samples)
clean.dependsOn(cleanBuildDir)
+124
View File
@@ -0,0 +1,124 @@
<?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"/>
<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 -1
View File
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
+6 -1
View File
@@ -19,5 +19,10 @@ applicationDistribution.with {
}
startScripts {
defaultJvmOpts = ['-Xms128M', '-Xmx4g']
defaultJvmOpts = ['-Xms128M', '-Xmx4g', '-XX:+UseG1GC']
doLast {
def str = windowsScript.text
str = str.replaceAll('set JAVA_EXE=%JAVA_HOME%/bin/java.exe', 'set JAVA_EXE="%JAVA_HOME%/bin/java.exe"')
windowsScript.text = str
}
}
@@ -3,7 +3,9 @@ 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;
@@ -66,9 +68,9 @@ public class JCommanderWrapper<T> {
maxNamesLen = len;
}
}
JadxCLIArgs args = new JadxCLIArgs();
Field[] fields = args.getClass().getDeclaredFields();
for (Field f : fields) {
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) {
@@ -85,12 +87,37 @@ public class JCommanderWrapper<T> {
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;
}
private void addDefaultValue(JadxCLIArgs args, Field f, StringBuilder opt) {
Class<?> fieldType = f.getType();
if (fieldType == int.class) {
try {
int val = f.getInt(args);
opt.append(" (default: ").append(val).append(")");
opt.append(" (default: ").append(val).append(')');
} catch (Exception e) {
// ignore
}
}
if (fieldType == String.class) {
try {
String val = (String) f.get(args);
if (val != null) {
opt.append(" (default: ").append(val).append(')');
}
} catch (Exception e) {
// ignore
}
+3 -1
View File
@@ -3,6 +3,7 @@ package jadx.cli;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs;
import jadx.api.JadxDecompiler;
import jadx.core.utils.exceptions.JadxArgsValidateException;
@@ -25,7 +26,8 @@ public class JadxCLI {
}
static int processAndSave(JadxCLIArgs inputArgs) {
JadxDecompiler jadx = new JadxDecompiler(inputArgs.toJadxArgs());
JadxArgs args = inputArgs.toJadxArgs();
JadxDecompiler jadx = new JadxDecompiler(args);
try {
jadx.load();
} catch (JadxArgsValidateException e) {
+133 -33
View File
@@ -1,92 +1,126 @@
package jadx.cli;
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 ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
import com.beust.jcommander.Parameter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.beust.jcommander.IStringConverter;
import com.beust.jcommander.Parameter;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
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;
public class JadxCLIArgs {
@Parameter(description = "<input file> (.apk, .dex, .jar or .class)")
@Parameter(description = "<input file> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc)")
protected List<String> files = new ArrayList<>(1);
@Parameter(names = {"-d", "--output-dir"}, description = "output directory")
@Parameter(names = { "-d", "--output-dir" }, description = "output directory")
protected String outDir;
@Parameter(names = {"-ds", "--output-dir-src"}, description = "output directory for sources")
@Parameter(names = { "-ds", "--output-dir-src" }, description = "output directory for sources")
protected String outDirSrc;
@Parameter(names = {"-dr", "--output-dir-res"}, description = "output directory for resources")
@Parameter(names = { "-dr", "--output-dir-res" }, description = "output directory for resources")
protected String outDirRes;
@Parameter(names = {"-r", "--no-res"}, description = "do not decode resources")
@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")
@Parameter(names = { "-s", "--no-src" }, description = "do not decompile source code")
protected boolean skipSources = false;
@Parameter(names = {"-e", "--export-gradle"}, description = "save as android gradle project")
@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")
@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)")
@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")
@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)")
@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")
@Parameter(names = { "--respect-bytecode-access-modifiers" }, description = "don't change original access modifiers")
protected boolean respectBytecodeAccessModifiers = false;
@Parameter(names = {"--deobf"}, description = "activate deobfuscation")
@Parameter(names = { "--deobf" }, description = "activate deobfuscation")
protected boolean deobfuscationOn = false;
@Parameter(names = {"--deobf-min"}, description = "min length of name, renamed if shorter")
@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")
@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")
@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 = true;
@Parameter(names = { "--deobf-use-sourcename" }, description = "use source file name as class name alias")
protected boolean deobfuscationUseSourceNameAsAlias = false;
@Parameter(names = {"--cfg"}, description = "save methods control flow graph to dot file")
@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 = {"-f", "--fallback"}, description = "make simple dump (using goto instead of 'if', 'for', etc)")
@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")
@Parameter(names = { "-v", "--verbose" }, description = "verbose output")
protected boolean verbose = false;
@Parameter(names = {"--version"}, description = "print jadx version")
@Parameter(names = { "--version" }, description = "print jadx version")
protected boolean printVersion = false;
@Parameter(names = {"-h", "--help"}, description = "print this help", help = true)
@Parameter(names = { "-h", "--help" }, description = "print this help", help = true)
protected boolean printHelp = false;
public boolean processArgs(String[] args) {
@@ -99,7 +133,7 @@ public class JadxCLIArgs {
* Used to merge saved options and options passed in command line.
*/
public boolean overrideProvided(String[] args) {
JCommanderWrapper<JadxCLIArgs> jcw = new JCommanderWrapper<>(new JadxCLIArgs());
JCommanderWrapper<JadxCLIArgs> jcw = new JCommanderWrapper<>(newInstance());
if (!jcw.parse(args)) {
return false;
}
@@ -107,7 +141,11 @@ public class JadxCLIArgs {
return process(jcw);
}
private boolean process(JCommanderWrapper jcw) {
protected JadxCLIArgs newInstance() {
return new JadxCLIArgs();
}
private boolean process(JCommanderWrapper<JadxCLIArgs> jcw) {
if (printHelp) {
jcw.printUsage();
return false;
@@ -143,8 +181,12 @@ public class JadxCLIArgs {
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));
}
args.setSkipResources(skipResources);
args.setFallbackMode(fallbackMode);
args.setShowInconsistentCode(showInconsistentCode);
@@ -160,6 +202,12 @@ public class JadxCLIArgs {
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;
}
@@ -203,6 +251,14 @@ public class JadxCLIArgs {
return useImports;
}
public boolean isDebugInfo() {
return debugInfo;
}
public boolean isInlineAnonymousClasses() {
return inlineAnonymousClasses;
}
public boolean isDeobfuscationOn() {
return deobfuscationOn;
}
@@ -223,10 +279,6 @@ public class JadxCLIArgs {
return deobfuscationUseSourceNameAsAlias;
}
public boolean escapeUnicode() {
return escapeUnicode;
}
public boolean isEscapeUnicode() {
return escapeUnicode;
}
@@ -250,4 +302,52 @@ public class JadxCLIArgs {
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) {
String values = Arrays.stream(RenameEnum.values())
.map(v -> '\'' + v.name().toLowerCase(Locale.ROOT) + '\'')
.collect(Collectors.joining(", "));
throw new IllegalArgumentException(
'\'' + s + "' is unknown for parameter " + paramName
+ ", possible values are " + values);
}
}
return set;
}
}
}
+11 -13
View File
@@ -1,16 +1,14 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
<encoder>
<pattern>%-5level - %msg%n</pattern>
</encoder>
</appender>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
<encoder>
<pattern>%-5level - %msg%n</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="STDOUT"/>
</root>
<root level="DEBUG">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
@@ -1,11 +1,11 @@
package jadx.cli;
import org.junit.Test;
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;
import static org.junit.Assert.assertThat;
public class JadxCLIArgsTest {
@@ -33,11 +33,14 @@ public class JadxCLIArgsTest {
@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;
@@ -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());
}
}
+10 -7
View File
@@ -4,14 +4,17 @@ dependencies {
runtime files(jadxClasspath)
compile files('lib/dx-1.16.jar')
compile 'commons-io:commons-io:2.6'
compile 'org.ow2.asm:asm:7.0'
compile 'org.jetbrains:annotations:16.0.3'
compile 'uk.com.robust-it:cloning:1.9.11'
testCompile 'org.smali:smali:2.2.5'
testCompile 'org.smali:baksmali:2.2.5'
compile 'org.ow2.asm:asm:7.1'
compile 'org.jetbrains:annotations:17.0.0'
compile 'uk.com.robust-it:cloning:1.9.12'
compile 'com.google.code.gson:gson:2.8.5'
compile 'org.smali:baksmali:2.2.7'
compile('org.smali:smali:2.2.7') {
exclude group: 'com.google.guava'
}
compile 'com.google.guava:guava:27.1-jre'
testCompile 'org.apache.commons:commons-lang3:3.8.1'
}
Binary file not shown.
@@ -57,6 +57,14 @@ public final class CodePosition {
@Override
public String toString() {
return line + ":" + offset + (node != null ? " " + node : "");
StringBuilder sb = new StringBuilder();
sb.append(line);
if (offset != 0) {
sb.append(':').append(offset);
}
if (node != null) {
sb.append(' ').append(node);
}
return sb.toString();
}
}
+143 -38
View File
@@ -2,7 +2,11 @@ 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;
public class JadxArgs {
@@ -27,12 +31,19 @@ public class JadxArgs {
private boolean showInconsistentCode = false;
private boolean useImports = true;
private boolean debugInfo = true;
private boolean inlineAnonymousClasses = true;
private boolean isSkipResources = false;
private boolean isSkipSources = false;
private boolean skipResources = false;
private boolean skipSources = false;
private boolean isDeobfuscationOn = false;
private boolean isDeobfuscationForceSave = 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;
@@ -43,6 +54,20 @@ public class JadxArgs {
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
}
@@ -57,6 +82,10 @@ public class JadxArgs {
return inputFiles;
}
public void setInputFile(File inputFile) {
this.inputFiles = Collections.singletonList(inputFile);
}
public void setInputFiles(List<File> inputFiles) {
this.inputFiles = inputFiles;
}
@@ -133,36 +162,60 @@ public class JadxArgs {
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 isSkipResources;
return skipResources;
}
public void setSkipResources(boolean skipResources) {
isSkipResources = skipResources;
this.skipResources = skipResources;
}
public boolean isSkipSources() {
return isSkipSources;
return skipSources;
}
public void setSkipSources(boolean skipSources) {
isSkipSources = skipSources;
this.skipSources = skipSources;
}
public Predicate<String> getClassFilter() {
return classFilter;
}
public void setClassFilter(Predicate<String> classFilter) {
this.classFilter = classFilter;
}
public boolean isDeobfuscationOn() {
return isDeobfuscationOn;
return deobfuscationOn;
}
public void setDeobfuscationOn(boolean deobfuscationOn) {
isDeobfuscationOn = deobfuscationOn;
this.deobfuscationOn = deobfuscationOn;
}
public boolean isDeobfuscationForceSave() {
return isDeobfuscationForceSave;
return deobfuscationForceSave;
}
public void setDeobfuscationForceSave(boolean deobfuscationForceSave) {
isDeobfuscationForceSave = deobfuscationForceSave;
this.deobfuscationForceSave = deobfuscationForceSave;
}
public boolean isUseSourceNameAsClassAlias() {
@@ -221,32 +274,84 @@ public class JadxArgs {
this.exportAsGradleProject = exportAsGradleProject;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("JadxArgs{");
sb.append("inputFiles=").append(inputFiles);
sb.append(", outDir=").append(outDir);
sb.append(", outDirSrc=").append(outDirSrc);
sb.append(", outDirRes=").append(outDirRes);
sb.append(", threadsCount=").append(threadsCount);
sb.append(", cfgOutput=").append(cfgOutput);
sb.append(", rawCFGOutput=").append(rawCFGOutput);
sb.append(", fallbackMode=").append(fallbackMode);
sb.append(", showInconsistentCode=").append(showInconsistentCode);
sb.append(", useImports=").append(useImports);
sb.append(", isSkipResources=").append(isSkipResources);
sb.append(", isSkipSources=").append(isSkipSources);
sb.append(", isDeobfuscationOn=").append(isDeobfuscationOn);
sb.append(", isDeobfuscationForceSave=").append(isDeobfuscationForceSave);
sb.append(", useSourceNameAsClassAlias=").append(useSourceNameAsClassAlias);
sb.append(", deobfuscationMinLength=").append(deobfuscationMinLength);
sb.append(", deobfuscationMaxLength=").append(deobfuscationMaxLength);
sb.append(", escapeUnicode=").append(escapeUnicode);
sb.append(", replaceConsts=").append(replaceConsts);
sb.append(", respectBytecodeAccModifiers=").append(respectBytecodeAccModifiers);
sb.append(", exportAsGradleProject=").append(exportAsGradleProject);
sb.append('}');
return sb.toString();
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;
}
@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
+ '}';
}
}
@@ -53,13 +53,18 @@ public class JadxArgsValidator {
} 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));
}
args.setOutDir(outDir);
setFromOut(args);
checkDir(args.getOutDir());
checkDir(args.getOutDirSrc());
checkDir(args.getOutDirRes());
checkDir(args.getOutDir(), "Output");
checkDir(args.getOutDirSrc(), "Source output");
checkDir(args.getOutDirRes(), "Resources output");
}
@NotNull
@@ -72,22 +77,13 @@ public class JadxArgsValidator {
if (pos != -1) {
outDirName = name.substring(0, pos);
} else {
outDirName = name + "-" + JadxArgs.DEFAULT_OUT_DIR;
outDirName = name + '-' + JadxArgs.DEFAULT_OUT_DIR;
}
LOG.info("output directory: {}", outDirName);
outDir = new File(outDirName);
return outDir;
}
private static void setFromOut(JadxArgs args) {
if (args.getOutDirSrc() == null) {
args.setOutDirSrc(new File(args.getOutDir(), JadxArgs.DEFAULT_SRC_DIR));
}
if (args.getOutDirRes() == null) {
args.setOutDirRes(new File(args.getOutDir(), JadxArgs.DEFAULT_RES_DIR));
}
}
private static void checkFile(File file) {
if (!file.exists()) {
throw new JadxArgsValidateException("File not found " + file.getAbsolutePath());
@@ -97,9 +93,9 @@ public class JadxArgsValidator {
}
}
private static void checkDir(File dir) {
private static void checkDir(File dir, String desc) {
if (dir != null && dir.exists() && !dir.isDirectory()) {
throw new JadxArgsValidateException("Output directory exists as file " + dir);
throw new JadxArgsValidateException(desc + " directory exists as file " + dir);
}
}
@@ -1,6 +1,8 @@
package jadx.api;
import java.io.File;
import java.io.StringWriter;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@@ -11,13 +13,20 @@ 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.jf.baksmali.Adaptors.ClassDefinition;
import org.jf.baksmali.BaksmaliOptions;
import org.jf.dexlib2.DexFileFactory;
import org.jf.dexlib2.Opcodes;
import org.jf.dexlib2.dexbacked.DexBackedClassDef;
import org.jf.dexlib2.dexbacked.DexBackedDexFile;
import org.jf.util.IndentingWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.Jadx;
import jadx.core.ProcessClass;
import jadx.core.codegen.CodeGen;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
@@ -26,6 +35,7 @@ import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.dex.visitors.SaveCode;
import jadx.core.export.ExportGradleProject;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.InputFile;
import jadx.core.xmlgen.BinaryXMLParser;
@@ -33,7 +43,9 @@ import jadx.core.xmlgen.ResourcesSaver;
/**
* Jadx API usage example:
* <pre><code>
*
* <pre>
* <code>
* JadxArgs args = new JadxArgs();
* args.getInputFiles().add(new File("test.apk"));
* args.setOutDir(new File("jadx-test-output"));
@@ -41,14 +53,18 @@ import jadx.core.xmlgen.ResourcesSaver;
* JadxDecompiler jadx = new JadxDecompiler(args);
* jadx.load();
* jadx.save();
* </code></pre>
* </code>
* </pre>
* <p>
* Instead of 'save()' you can iterate over decompiled classes:
* <pre><code>
*
* <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);
@@ -59,7 +75,6 @@ public final class JadxDecompiler {
private RootNode root;
private List<IDexTreeVisitor> passes;
private CodeGen codeGen;
private List<JavaClass> classes;
private List<ResourceFile> resources;
@@ -97,7 +112,6 @@ public final class JadxDecompiler {
void init() {
this.passes = Jadx.getPassesList(args);
this.codeGen = new CodeGen();
}
void reset() {
@@ -106,7 +120,6 @@ public final class JadxDecompiler {
xmlParser = null;
root = null;
passes = null;
codeGen = null;
}
public static String getVersion() {
@@ -191,14 +204,18 @@ public final class JadxDecompiler {
}
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 {
cls.decompile();
SaveCode.save(outDir, args, cls.getClassNode());
SaveCode.save(outDir, cls.getClassNode());
} catch (Exception e) {
LOG.error("Error saving class: {}", cls.getFullName(), e);
}
@@ -215,9 +232,11 @@ public final class JadxDecompiler {
List<JavaClass> clsList = new ArrayList<>(classNodeList.size());
classesMap.clear();
for (ClassNode classNode : classNodeList) {
JavaClass javaClass = new JavaClass(classNode, this);
clsList.add(javaClass);
classesMap.put(classNode, javaClass);
if (!classNode.contains(AFlag.DONT_GENERATE)) {
JavaClass javaClass = new JavaClass(classNode, this);
clsList.add(javaClass);
classesMap.put(classNode, javaClass);
}
}
classes = Collections.unmodifiableList(clsList);
}
@@ -251,7 +270,7 @@ public final class JadxDecompiler {
}
Collections.sort(packages);
for (JavaPackage pkg : packages) {
pkg.getClasses().sort(Comparator.comparing(JavaClass::getName));
pkg.getClasses().sort(Comparator.comparing(JavaClass::getName, String.CASE_INSENSITIVE_ORDER));
}
return Collections.unmodifiableList(packages);
}
@@ -289,13 +308,41 @@ public final class JadxDecompiler {
}
void processClass(ClassNode cls) {
ProcessClass.process(cls, passes, codeGen);
ProcessClass.process(cls, passes, true);
}
void generateSmali(ClassNode cls) {
Path path = cls.dex().getDexFile().getPath();
String className = Utils.makeQualifiedObjectName(cls.getClassInfo().getType().getObject());
try {
DexBackedDexFile dexFile = DexFileFactory.loadDexFile(path.toFile(), Opcodes.getDefault());
boolean decompiled = false;
for (DexBackedClassDef classDef : dexFile.getClasses()) {
if (classDef.getType().equals(className)) {
ClassDefinition classDefinition = new ClassDefinition(new BaksmaliOptions(), classDef);
StringWriter sw = new StringWriter();
classDefinition.writeTo(new IndentingWriter(sw));
cls.setSmali(sw.toString());
decompiled = true;
break;
}
}
if (!decompiled) {
LOG.error("Failed to find smali class {}", className);
}
} catch (Exception e) {
LOG.error("Error generating smali", e);
}
}
RootNode getRoot() {
return root;
}
List<IDexTreeVisitor> getPasses() {
return passes;
}
synchronized BinaryXMLParser getXmlParser() {
if (xmlParser == null) {
xmlParser = new BinaryXMLParser(root);
@@ -64,11 +64,21 @@ public final class JavaClass implements JavaNode {
}
}
public synchronized String getSmali() {
if (decompiler == null) {
return null;
}
if (cls.getSmali() == null) {
decompiler.generateSmali(cls);
}
return cls.getSmali();
}
public synchronized void unload() {
cls.unload();
}
ClassNode getClassNode() {
public ClassNode getClassNode() {
return cls;
}
@@ -21,7 +21,7 @@ public final class JavaField implements JavaNode {
@Override
public String getFullName() {
return parent.getFullName() + "." + getName();
return parent.getFullName() + '.' + getName();
}
@Override
@@ -39,7 +39,7 @@ public final class JavaField implements JavaNode {
}
public ArgType getType() {
return field.getType();
return ArgType.tryToResolveClassAlias(field.dex(), field.getType());
}
public int getDecompiledLine() {
@@ -1,9 +1,13 @@
package jadx.api;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
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.MethodNode;
public final class JavaMethod implements JavaNode {
@@ -40,11 +44,27 @@ public final class JavaMethod implements JavaNode {
}
public List<ArgType> getArguments() {
return mth.getMethodInfo().getArgumentsTypes();
if (mth.getMethodInfo().getArgumentsTypes().isEmpty()) {
return Collections.emptyList();
}
List<RegisterArg> arguments = mth.getArguments(false);
Stream<ArgType> argTypeStream;
if (arguments == null || arguments.isEmpty() || mth.isNoCode()) {
argTypeStream = mth.getMethodInfo().getArgumentsTypes().stream();
} else {
argTypeStream = arguments.stream().map(RegisterArg::getType);
}
return argTypeStream
.map(type -> ArgType.tryToResolveClassAlias(mth.dex(), type))
.collect(Collectors.toList());
}
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() {
@@ -70,6 +70,6 @@ public class ResourceFile {
@Override
public String toString() {
return "ResourceFile{name='" + name + '\'' + ", type=" + type + "}";
return "ResourceFile{name='" + name + '\'' + ", type=" + type + '}';
}
}
@@ -84,13 +84,13 @@ public final class ResourcesLoader {
LOG.error("Decode error", e);
CodeWriter cw = new CodeWriter();
cw.add("Error decode ").add(rf.getType().toString().toLowerCase());
cw.startLine(Utils.getStackTrace(e.getCause()));
Utils.appendStackTrace(cw, e.getCause());
return ResContainer.textResource(rf.getName(), cw);
}
}
private static ResContainer loadContent(JadxDecompiler jadxRef, ResourceFile rf,
InputStream inputStream) throws IOException {
InputStream inputStream) throws IOException {
switch (rf.getType()) {
case MANIFEST:
case XML:
@@ -98,7 +98,7 @@ public final class ResourcesLoader {
return ResContainer.textResource(rf.getName(), content);
case ARSC:
return new ResTableParser().decodeFiles(inputStream);
return new ResTableParser(jadxRef.getRoot()).decodeFiles(inputStream);
case IMG:
return decodeImage(rf, inputStream);
+40 -30
View File
@@ -1,5 +1,6 @@
package jadx.core;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
@@ -11,9 +12,8 @@ import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs;
import jadx.core.dex.visitors.ClassModifier;
import jadx.core.dex.visitors.CodeShrinker;
import jadx.core.dex.visitors.ConstInlineVisitor;
import jadx.core.dex.visitors.DebugInfoVisitor;
import jadx.core.dex.visitors.ConstructorVisitor;
import jadx.core.dex.visitors.DependencyCollector;
import jadx.core.dex.visitors.DotGraphVisitor;
import jadx.core.dex.visitors.EnumVisitor;
@@ -21,27 +21,31 @@ 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.BlockFinallyExtract;
import jadx.core.dex.visitors.blocksmaker.BlockFinish;
import jadx.core.dex.visitors.blocksmaker.BlockProcessor;
import jadx.core.dex.visitors.blocksmaker.BlockSplitter;
import jadx.core.dex.visitors.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.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.regions.variables.ProcessVariables;
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
import jadx.core.dex.visitors.ssa.SSATransform;
import jadx.core.dex.visitors.typeinference.FinishTypeInference;
import jadx.core.dex.visitors.typeinference.TypeInference;
import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor;
public class Jadx {
private static final Logger LOG = LoggerFactory.getLogger(Jadx.class);
@@ -60,29 +64,32 @@ public class Jadx {
if (args.isFallbackMode()) {
passes.add(new FallbackModeVisitor());
} else {
if (args.isDebugInfo()) {
passes.add(new DebugInfoParseVisitor());
}
passes.add(new BlockSplitter());
passes.add(new BlockProcessor());
passes.add(new BlockExceptionHandler());
passes.add(new BlockFinallyExtract());
passes.add(new BlockFinish());
passes.add(new SSATransform());
passes.add(new DebugInfoVisitor());
passes.add(new TypeInference());
if (args.isRawCFGOutput()) {
passes.add(DotGraphVisitor.dumpRaw());
}
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 FinishTypeInference());
passes.add(new EliminatePhiNodes());
passes.add(new TypeInferenceVisitor());
if (args.isDebugInfo()) {
passes.add(new DebugInfoApplyVisitor());
}
passes.add(new ModVisitor());
passes.add(new CodeShrinker());
passes.add(new CodeShrinkVisitor());
passes.add(new ReSugarCode());
if (args.isCfgOutput()) {
passes.add(DotGraphVisitor.dump());
}
@@ -90,26 +97,27 @@ public class Jadx {
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 CheckRegions());
passes.add(new MethodInlineVisitor());
passes.add(new ExtractFieldInit());
passes.add(new FixAccessModifiers());
passes.add(new ProcessAnonymous());
passes.add(new ClassModifier());
passes.add(new MethodInlineVisitor());
passes.add(new EnumVisitor());
passes.add(new PrepareForCodeGen());
passes.add(new LoopRegionVisitor());
passes.add(new ProcessVariables());
passes.add(new ProcessVariables());
passes.add(new PrepareForCodeGen());
if (args.isCfgOutput()) {
passes.add(DotGraphVisitor.dumpRegions());
}
passes.add(new DependencyCollector());
passes.add(new RenameVisitor());
}
return passes;
@@ -121,10 +129,12 @@ public class Jadx {
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;
}
}
}
}
@@ -2,8 +2,6 @@ package jadx.core;
import java.util.List;
import org.jetbrains.annotations.Nullable;
import jadx.core.codegen.CodeGen;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.visitors.DepthTraversal;
@@ -19,8 +17,8 @@ public final class ProcessClass {
private ProcessClass() {
}
public static void process(ClassNode cls, List<IDexTreeVisitor> passes, @Nullable CodeGen codeGen) {
if (codeGen == null && cls.getState() == PROCESSED) {
public static void process(ClassNode cls, List<IDexTreeVisitor> passes, boolean generateCode) {
if (!generateCode && cls.getState() == PROCESSED) {
return;
}
synchronized (getSyncObj(cls)) {
@@ -33,9 +31,9 @@ public final class ProcessClass {
}
cls.setState(PROCESSED);
}
if (cls.getState() == PROCESSED && codeGen != null) {
if (cls.getState() == PROCESSED && generateCode) {
processDependencies(cls, passes);
codeGen.visit(cls);
CodeGen.generate(cls);
}
} catch (Exception e) {
ErrorsCounter.classError(cls, e.getClass().getSimpleName(), e);
@@ -48,6 +46,6 @@ public final class ProcessClass {
}
private static void processDependencies(ClassNode cls, List<IDexTreeVisitor> passes) {
cls.getDependencies().forEach(depCls -> process(depCls, passes, null));
cls.getDependencies().forEach(depCls -> process(depCls, passes, false));
}
}
@@ -5,10 +5,12 @@ 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.HashMap;
import java.util.List;
@@ -21,15 +23,15 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.ClassNode;
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;
import static jadx.core.utils.files.FileUtils.close;
/**
* Classes list for import into classpath graph
*/
@@ -41,10 +43,16 @@ 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) {
@@ -54,11 +62,13 @@ public class ClsSet {
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.setMethods(loadMethods(cls, nClass));
} else {
names.put(clsRawName, null);
}
@@ -78,6 +88,47 @@ public class ClsSet {
}
}
private NMethod[] loadMethods(ClassNode cls, NClass nClass) {
List<NMethod> methods = new ArrayList<>();
for (MethodNode m : cls.getMethods()) {
if (!m.getAccessFlags().isPublic()
&& !m.getAccessFlags().isProtected()) {
continue;
}
List<ArgType> args = new ArrayList<>();
boolean genericArg = false;
for (RegisterArg r : m.getArguments(false)) {
ArgType argType = r.getType();
if (argType.isGeneric() || argType.isGenericType()) {
args.add(argType);
genericArg = true;
} else {
args.add(null);
}
}
ArgType retType = m.getReturnType();
if (!retType.isGeneric() && !retType.isGenericType()) {
retType = null;
}
boolean varArgs = m.getAccessFlags().isVarArgs();
if (genericArg || retType != null || varArgs) {
methods.add(new NMethod(
m.getMethodInfo().getShortId(),
args.isEmpty()
? new ArgType[0]
: args.toArray(new ArgType[args.size()]),
retType,
varArgs));
}
}
return methods.toArray(new NMethod[methods.size()]);
}
public static NClass[] makeParentsArray(ClassNode cls, Map<String, NClass> names) {
List<NClass> parents = new ArrayList<>(1 + cls.getInterfaces().size());
ArgType superClass = cls.getSuperClass();
@@ -93,54 +144,142 @@ public class ClsSet {
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)) {
NClass cls = names.get(fullName);
if (cls == null) {
LOG.debug("Class not found: {}", fullName);
}
return id;
return cls;
}
void save(File output) throws IOException {
FileUtils.makeDirsForFile(output);
try (BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(output))) {
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);
} finally {
close(out);
}
} else {
throw new JadxRuntimeException("Unknown file format: " + outputName);
}
} 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 {
try (DataOutputStream out = new DataOutputStream(output)) {
out.writeBytes(JADX_CLS_SET_HEADER);
out.writeByte(VERSION);
DataOutputStream out = new DataOutputStream(output);
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());
NMethod[] methods = cls.getMethods();
out.writeByte(methods.length);
for (NMethod method : methods) {
writeMethod(out, method, names);
}
}
}
private static void writeMethod(DataOutputStream out, NMethod method, Map<String, NClass> names) throws IOException {
int argCount = 0;
ArgType[] argTypes = method.getArgType();
for (ArgType arg : argTypes) {
if (arg != null) {
argCount++;
}
}
writeLongString(out, method.getShortId());
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(true);
writeArgType(out, method.getReturnType(), names);
} else {
out.writeBoolean(false);
}
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());
int bounds = argType.getWildcardBounds();
out.writeByte(bounds);
if (bounds != 0) {
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);
}
}
@@ -197,18 +336,111 @@ public class ClsSet {
parents[j] = classes[in.readInt()];
}
classes[i].setParents(parents);
int mCount = in.readByte();
NMethod[] methods = new NMethod[mCount];
for (int j = 0; j < mCount; j++) {
methods[j] = readMethod(in);
}
classes[i].setMethods(methods);
}
}
}
private void writeString(DataOutputStream out, String name) throws IOException {
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), 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:
int shortName = in.readByte();
switch (shortName) {
case 'Z':
return ArgType.BOOLEAN;
case 'C':
return ArgType.CHAR;
case 'B':
return ArgType.BYTE;
case 'S':
return ArgType.SHORT;
case 'I':
return ArgType.INT;
case 'F':
return ArgType.FLOAT;
case 'J':
return ArgType.LONG;
case 'D':
return ArgType.DOUBLE;
default:
return ArgType.VOID;
}
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) {
@@ -23,7 +23,7 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
public class ClspGraph {
private static final Logger LOG = LoggerFactory.getLogger(ClspGraph.class);
private final Map<String, Set<String>> ancestorCache = Collections.synchronizedMap(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<>();
@@ -58,6 +58,10 @@ public class ClspGraph {
}
}
public boolean isClsKnown(String fullName) {
return nameMap.containsKey(fullName);
}
private NClass addClass(ClassNode cls) {
String rawName = cls.getRawName();
NClass nClass = new NClass(rawName, -1);
@@ -65,11 +69,24 @@ public class ClspGraph {
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;
@@ -100,7 +117,7 @@ public class ClspGraph {
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;
@@ -2,6 +2,8 @@ package jadx.core.clsp;
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;
@@ -28,7 +30,7 @@ public class ConvertToClsSet {
usage();
System.exit(1);
}
File output = new File(args[0]);
Path output = Paths.get(args[0]);
List<InputFile> inputFiles = new ArrayList<>(args.length - 1);
for (int i = 1; i < args.length; i++) {
@@ -36,7 +38,7 @@ public class ConvertToClsSet {
if (f.isDirectory()) {
addFilesFromDirectory(f, inputFiles);
} else {
InputFile.addFilesFrom(f, inputFiles);
InputFile.addFilesFrom(f, inputFiles, false);
}
}
for (InputFile inputFile : inputFiles) {
@@ -63,7 +65,7 @@ public class ConvertToClsSet {
addFilesFromDirectory(file, inputFiles);
} else {
try {
InputFile.addFilesFrom(file, inputFiles);
InputFile.addFilesFrom(file, inputFiles, false);
} catch (Exception e) {
LOG.warn("Skip file: {}, load error: {}", file, e.getMessage());
}
@@ -7,7 +7,8 @@ public class NClass {
private final String name;
private NClass[] parents;
private int id;
private NMethod[] methods;
private final int id;
public NClass(String name, int id) {
this.name = name;
@@ -22,10 +23,6 @@ public class NClass {
return id;
}
public void setId(int id) {
this.id = id;
}
public NClass[] getParents() {
return parents;
}
@@ -55,4 +52,12 @@ public class NClass {
public String toString() {
return name;
}
public void setMethods(NMethod[] methods) {
this.methods = methods;
}
public NMethod[] getMethods() {
return methods;
}
}
@@ -0,0 +1,37 @@
package jadx.core.clsp;
import jadx.core.dex.instructions.args.ArgType;
/**
* Generic method node in classpath graph.
*/
public class NMethod {
private final String shortId;
private final ArgType[] argType;
private final ArgType retType;
private final boolean varArgs;
public NMethod(String shortId, ArgType[] argType, ArgType retType, boolean varArgs) {
this.shortId = shortId;
this.argType = argType;
this.retType = retType;
this.varArgs = varArgs;
}
public String getShortId() {
return shortId;
}
public ArgType[] getArgType() {
return argType;
}
public ArgType getReturnType() {
return retType;
}
public boolean isVarArgs() {
return varArgs;
}
}
@@ -5,6 +5,8 @@ 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;
@@ -63,12 +65,7 @@ public class AnnotationGen {
}
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);
}
@@ -77,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()) {
@@ -156,7 +171,7 @@ public class AnnotationGen {
InsnGen.makeStaticFieldAccess(code, field, classGen);
} else if (val instanceof Iterable) {
code.add('{');
Iterator<?> it = ((Iterable) val).iterator();
Iterator<?> it = ((Iterable<?>) val).iterator();
while (it.hasNext()) {
Object obj = it.next();
encodeValue(code, obj);
@@ -169,7 +184,7 @@ 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() + ')');
}
}
@@ -18,9 +18,7 @@ import jadx.core.dex.attributes.AttrNode;
import jadx.core.dex.attributes.nodes.EnumClassAttr;
import jadx.core.dex.attributes.nodes.EnumClassAttr.EnumField;
import jadx.core.dex.attributes.nodes.JadxError;
import jadx.core.dex.attributes.nodes.JadxWarn;
import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.dex.attributes.nodes.SourceFileAttr;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.instructions.args.ArgType;
@@ -33,10 +31,11 @@ import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.parser.FieldInitAttr;
import jadx.core.dex.nodes.parser.FieldInitAttr.InitType;
import jadx.core.utils.CodegenUtils;
import jadx.core.utils.CodeGenUtils;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.JadxRuntimeException;
public class ClassGen {
@@ -84,28 +83,28 @@ public class ClassGen {
int importsCount = imports.size();
if (importsCount != 0) {
List<ClassInfo> sortedImports = new ArrayList<>(imports);
sortedImports.sort(Comparator.comparing(classInfo -> classInfo.getAlias().getFullName()));
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.getAlias().getFullName());
clsCode.add(classInfo.getAliasFullName());
clsCode.add(';');
});
clsCode.newLine();
imports.clear();
}
clsCode.add(clsBody);
return clsCode;
return clsCode.finish();
}
public void addClassCode(CodeWriter code) throws CodegenException {
if (cls.contains(AFlag.DONT_GENERATE)) {
return;
}
CodegenUtils.addComments(code, cls);
CodeGenUtils.addComments(code, cls);
insertDecompilationProblems(code, cls);
addClassDeclaration(code);
addClassBody(code);
@@ -123,13 +122,13 @@ public class ClassGen {
}
// 'static' and 'private' modifier not allowed for top classes (not inner)
if (!cls.getAlias().isInner()) {
if (!cls.getClassInfo().isInner()) {
af = af.remove(AccessFlags.ACC_STATIC).remove(AccessFlags.ACC_PRIVATE);
}
annotationGen.addForClass(clsCode);
insertSourceFileInfo(clsCode, cls);
insertRenameInfo(clsCode, cls);
CodeGenUtils.addSourceFileInfo(clsCode, cls);
clsCode.startLine(af.makeString());
if (af.isInterface()) {
if (af.isAnnotation()) {
@@ -142,9 +141,9 @@ public class ClassGen {
clsCode.add("class ");
}
clsCode.attachDefinition(cls);
clsCode.add(cls.getShortName());
clsCode.add(cls.getClassInfo().getAliasShortName());
addGenericMap(clsCode, cls.getGenericMap());
addGenericMap(clsCode, cls.getGenericMap(), true);
clsCode.add(' ');
ArgType sup = cls.getSuperClass();
@@ -162,7 +161,7 @@ public class ClassGen {
} else {
clsCode.add("implements ");
}
for (Iterator<ArgType> it = cls.getInterfaces().iterator(); it.hasNext(); ) {
for (Iterator<ArgType> it = cls.getInterfaces().iterator(); it.hasNext();) {
ArgType interf = it.next();
useClass(clsCode, interf);
if (it.hasNext()) {
@@ -175,7 +174,7 @@ public class ClassGen {
}
}
public boolean addGenericMap(CodeWriter code, Map<ArgType, List<ArgType>> gmap) {
public boolean addGenericMap(CodeWriter code, Map<ArgType, List<ArgType>> gmap, boolean classDeclaration) {
if (gmap == null || gmap.isEmpty()) {
return false;
}
@@ -194,12 +193,17 @@ public class ClassGen {
}
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, g);
if (classDeclaration
&& !cls.getClassInfo().isInner()
&& cls.root().getArgs().isUseImports()) {
addImport(ClassInfo.fromType(cls.root(), g));
}
}
if (it.hasNext()) {
code.add(" & ");
@@ -225,8 +229,7 @@ public class ClassGen {
private void addInnerClasses(CodeWriter code, ClassNode cls) throws CodegenException {
for (ClassNode innerCls : cls.getInnerClasses()) {
if (innerCls.contains(AFlag.DONT_GENERATE)
|| innerCls.contains(AFlag.ANONYMOUS_CLASS)) {
if (innerCls.contains(AFlag.DONT_GENERATE)) {
continue;
}
ClassGen inClGen = new ClassGen(innerCls, getParentGen());
@@ -258,11 +261,15 @@ public class ClassGen {
try {
addMethod(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().add(ErrorsCounter.methodError(mth, "Method generation error", e));
code.newLine().add(Utils.getStackTrace(e));
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);
}
}
}
@@ -282,29 +289,21 @@ public class ClassGen {
return false;
}
private void addMethod(CodeWriter code, MethodNode mth) throws CodegenException {
public void addMethod(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 {
CodegenUtils.addComments(code, mth);
insertDecompilationProblems(code, mth);
boolean badCode = mth.contains(AFlag.INCONSISTENT_CODE);
if (badCode && showInconsistentCode) {
code.startLine("/* Code decompiled incorrectly, please refer to instructions dump. */");
mth.remove(AFlag.INCONSISTENT_CODE);
badCode = false;
}
MethodGen mthGen;
if (badCode || mth.contains(AType.JADX_ERROR) || fallback) {
if (badCode || fallback || mth.contains(AType.JADX_ERROR) || mth.getRegion() == null) {
mthGen = MethodGen.getFallbackMethodGen(mth);
} else {
mthGen = new MethodGen(this, mth);
@@ -314,22 +313,16 @@ public class ClassGen {
}
code.add('{');
code.incIndent();
insertSourceFileInfo(code, mth);
if (fallback) {
mthGen.addFallbackMethodCode(code);
} else {
mthGen.addInstructions(code);
}
mthGen.addInstructions(code);
code.decIndent();
code.startLine('}');
}
}
private void insertDecompilationProblems(CodeWriter code, AttrNode node) {
public void insertDecompilationProblems(CodeWriter code, AttrNode node) {
List<JadxError> errors = node.getAll(AType.JADX_ERROR);
List<JadxWarn> warns = node.getAll(AType.JADX_WARN);
if (!errors.isEmpty()) {
errors.forEach(err -> {
errors.stream().distinct().sorted().forEach(err -> {
code.startLine("/* JADX ERROR: ").add(err.getError());
Throwable cause = err.getCause();
if (cause != null) {
@@ -340,44 +333,51 @@ public class ClassGen {
code.add("*/");
});
}
List<String> warns = node.getAll(AType.JADX_WARN);
if (!warns.isEmpty()) {
warns.forEach(warn -> code.startLine("/* JADX WARNING: ").addMultiLine(warn.getWarn()).add(" */"));
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;
}
CodegenUtils.addComments(code, f);
annotationGen.addForField(code, f);
addField(code, f);
}
}
if (f.getFieldInfo().isRenamed()) {
code.startLine("/* renamed from: ").add(f.getName()).add(" */");
}
code.startLine(f.getAccessFlags().makeString());
useType(code, f.getType());
code.add(' ');
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));
} 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());
}
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.add(';');
}
private boolean isFieldsPresents() {
@@ -395,7 +395,7 @@ 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.getField().getAlias());
ConstructorInsn constrInsn = f.getConstrInsn();
@@ -457,7 +457,7 @@ public class ClassGen {
}
public void useClass(CodeWriter code, ArgType type) {
useClass(code, ClassInfo.extCls(cls.root(), type));
useClass(code, ClassInfo.fromType(cls.root(), type));
ArgType[] generics = type.getGenericTypes();
if (generics != null) {
code.add('<');
@@ -486,18 +486,28 @@ public class ClassGen {
public void useClass(CodeWriter code, ClassInfo classInfo) {
ClassNode classNode = cls.dex().resolveClass(classInfo);
if (classNode != null) {
code.attachAnnotation(classNode);
useClass(code, classNode);
} else {
addClsName(code, classInfo);
}
String baseClass = useClassInternal(cls.getAlias(), classInfo.getAlias());
code.add(baseClass);
}
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.getFullName();
String fullName = extClsInfo.getAliasFullName();
if (fallback || !useImports) {
return fullName;
}
String shortName = extClsInfo.getShortName();
String shortName = extClsInfo.getAliasShortName();
if (extClsInfo.getPackage().equals("java.lang") && extClsInfo.getParentClass() == null) {
return shortName;
}
@@ -523,15 +533,15 @@ public class ClassGen {
if (extClsInfo.isDefaultPackage()) {
return shortName;
}
if (extClsInfo.getPackage().equals(useCls.getPackage())) {
fullName = extClsInfo.getNameWithoutPackage();
if (extClsInfo.getAliasPkg().equals(useCls.getAliasPkg())) {
fullName = extClsInfo.getAliasNameWithoutPackage();
}
for (ClassInfo importCls : getImports()) {
if (!importCls.equals(extClsInfo)
&& importCls.getShortName().equals(shortName)) {
&& importCls.getAliasShortName().equals(shortName)) {
if (extClsInfo.isInner()) {
String parent = useClassInternal(useCls, extClsInfo.getParentClass().getAlias());
return parent + "." + shortName;
String parent = useClassInternal(useCls, extClsInfo.getParentClass());
return parent + '.' + shortName;
} else {
return fullName;
}
@@ -543,13 +553,13 @@ public class ClassGen {
private void addImport(ClassInfo classInfo) {
if (parentGen != null) {
parentGen.addImport(classInfo.getAlias());
parentGen.addImport(classInfo);
} else {
imports.add(classInfo);
}
}
private Set<ClassInfo> getImports() {
public Set<ClassInfo> getImports() {
if (parentGen != null) {
return parentGen.getImports();
} else {
@@ -579,15 +589,15 @@ public class ClassGen {
if (useCls == null) {
return false;
}
String shortName = searchCls.getShortName();
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)
&& !inner.getAlias().equals(searchCls)) {
&& !inner.getFullName().equals(searchCls.getAliasFullName())) {
return true;
}
}
@@ -595,17 +605,10 @@ public class ClassGen {
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()).add(" */");
}
}
private void insertRenameInfo(CodeWriter code, ClassNode cls) {
ClassInfo classInfo = cls.getClassInfo();
if (classInfo.isRenamed()) {
code.startLine("/* renamed from: ").add(classInfo.getType().getObject()).add(" */");
if (classInfo.hasAlias()) {
CodeGenUtils.addRenamedComment(code, cls, classInfo.getType().getObject());
}
}
@@ -1,15 +1,61 @@
package jadx.core.codegen;
import java.util.concurrent.Callable;
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.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.JadxRuntimeException;
public class CodeGen {
public boolean visit(ClassNode cls) throws CodegenException {
ClassGen clsGen = new ClassGen(cls, cls.root().getArgs());
CodeWriter clsCode = clsGen.makeClass();
clsCode.finish();
cls.setCode(clsCode);
return false;
public static void generate(ClassNode cls) {
if (cls.contains(AFlag.DONT_GENERATE)) {
cls.setCode(CodeWriter.EMPTY);
} else {
JadxArgs args = cls.root().getArgs();
switch (args.getOutputFormat()) {
case JAVA:
generateJavaCode(cls, args);
break;
case JSON:
generateJson(cls);
break;
}
}
}
private static void generateJavaCode(ClassNode cls, JadxArgs args) {
ClassGen clsGen = new ClassGen(cls, args);
CodeWriter code = wrapCodeGen(cls, clsGen::makeClass);
cls.setCode(code);
}
private static void generateJson(ClassNode cls) {
JsonCodeGen codeGen = new JsonCodeGen(cls);
String clsJson = wrapCodeGen(cls, codeGen::process);
cls.setCode(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() {
}
}
@@ -4,7 +4,6 @@ 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;
@@ -24,6 +23,8 @@ public class CodeWriter {
public static final String NL = System.getProperty("line.separator");
public static final String INDENT_STR = " ";
public static final CodeWriter EMPTY = new CodeWriter().finish();
private static final boolean ADD_LINE_NUMBERS = false;
private static final String[] INDENT_CACHE = {
@@ -35,7 +36,7 @@ public class CodeWriter {
INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR,
};
private StringBuilder buf = new StringBuilder();
private StringBuilder buf;
@Nullable
private String code;
private String indentStr;
@@ -47,6 +48,7 @@ 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) {
@@ -54,6 +56,12 @@ public class CodeWriter {
}
}
// create filled instance (just string wrapper)
public CodeWriter(String code) {
this.buf = null;
this.code = code;
}
public CodeWriter startLine() {
addLine();
addLineIndent();
@@ -95,10 +103,12 @@ public class CodeWriter {
}
public CodeWriter addMultiLine(String str) {
buf.append(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;
}
@@ -221,6 +231,10 @@ public class CodeWriter {
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<>();
@@ -250,27 +264,27 @@ public class CodeWriter {
return lineMap;
}
public void finish() {
public CodeWriter finish() {
removeFirstEmptyLine();
buf.trimToSize();
code = buf.toString();
buf = null;
Iterator<Map.Entry<CodePosition, Object>> it = annotations.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<CodePosition, Object> entry = it.next();
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.substring(0, len).equals(NL)) {
if (buf.length() > len && buf.substring(0, len).equals(NL)) {
buf.delete(0, len);
}
}
@@ -4,6 +4,7 @@ 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;
@@ -155,7 +156,7 @@ public class ConditionGen extends InsnGen {
}
private boolean isWrapNeeded(IfCondition condition) {
if (condition.isCompare()) {
if (condition.isCompare() || condition.contains(AFlag.DONT_WRAP)) {
return false;
}
return condition.getMode() != Mode.NOT;
@@ -1,7 +1,6 @@
package jadx.core.codegen;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.List;
@@ -11,6 +10,8 @@ import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.Consts;
import jadx.core.deobf.NameMapper;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
@@ -34,12 +35,14 @@ import jadx.core.dex.instructions.InvokeType;
import jadx.core.dex.instructions.NewArrayNode;
import jadx.core.dex.instructions.SwitchNode;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.CodeVar;
import jadx.core.dex.instructions.args.FieldArg;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.InsnWrapArg;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.Named;
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.instructions.mods.TernaryInsn;
import jadx.core.dex.nodes.ClassNode;
@@ -47,7 +50,6 @@ import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.RegionUtils;
import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.JadxRuntimeException;
@@ -61,6 +63,7 @@ public class InsnGen {
protected final MethodNode mth;
protected final RootNode root;
protected final boolean fallback;
protected final boolean attachInsns;
protected enum Flags {
BODY_ONLY,
@@ -71,8 +74,9 @@ public class InsnGen {
public InsnGen(MethodGen mgen, boolean fallback) {
this.mgen = mgen;
this.mth = mgen.getMethodNode();
this.root = mth.dex().root();
this.root = mth.root();
this.fallback = fallback;
this.attachInsns = root.getArgs().isJsonOutput();
}
private boolean isFallback() {
@@ -123,16 +127,20 @@ public class InsnGen {
}
public void declareVar(CodeWriter code, RegisterArg arg) {
if (arg.getSVar().contains(AFlag.FINAL)) {
declareVar(code, arg.getSVar().getCodeVar());
}
public void declareVar(CodeWriter code, CodeVar codeVar) {
if (codeVar.isFinal()) {
code.add("final ");
}
useType(code, arg.getType());
useType(code, codeVar.getType());
code.add(' ');
code.add(mgen.getNameGen().assignArg(arg));
code.add(mgen.getNameGen().assignArg(codeVar));
}
private String lit(LiteralArg arg) {
return TypeGen.literalToString(arg.getLiteral(), arg.getType(), mth);
return TypeGen.literalToString(arg.getLiteral(), arg.getType(), mth, fallback);
}
private void instanceField(CodeWriter code, FieldInfo field, InsnArg arg) throws CodegenException {
@@ -201,33 +209,41 @@ public class InsnGen {
mgen.getClassGen().useType(code, type);
}
public boolean makeInsn(InsnNode insn, CodeWriter code) throws CodegenException {
return makeInsn(insn, code, null);
public void makeInsn(InsnNode insn, CodeWriter code) throws CodegenException {
makeInsn(insn, code, null);
}
protected boolean makeInsn(InsnNode insn, CodeWriter code, Flags flag) throws CodegenException {
private static final Set<Flags> EMPTY_FLAGS = EnumSet.noneOf(Flags.class);
private static final Set<Flags> BODY_ONLY_FLAG = EnumSet.of(Flags.BODY_ONLY);
private static final Set<Flags> BODY_ONLY_NOWRAP_FLAGS = EnumSet.of(Flags.BODY_ONLY_NOWRAP);
protected void makeInsn(InsnNode insn, CodeWriter code, Flags flag) throws CodegenException {
try {
Set<Flags> state = EnumSet.noneOf(Flags.class);
if (flag == Flags.BODY_ONLY || flag == Flags.BODY_ONLY_NOWRAP) {
state.add(flag);
makeInsnBody(code, insn, state);
makeInsnBody(code, insn, flag == Flags.BODY_ONLY ? BODY_ONLY_FLAG : BODY_ONLY_NOWRAP_FLAGS);
} else {
if (flag != Flags.INLINE) {
code.startLineWithNum(insn.getSourceLine());
if (attachInsns) {
code.attachLineAnnotation(insn);
}
}
if (insn.getResult() != null && !insn.contains(AFlag.ARITH_ONEARG)) {
assignVar(code, insn);
code.add(" = ");
if (insn.getResult() != null) {
SSAVar var = insn.getResult().getSVar();
if ((var == null || var.getUseCount() != 0 || insn.getType() != InsnType.CONSTRUCTOR)
&& !insn.contains(AFlag.ARITH_ONEARG)) {
assignVar(code, insn);
code.add(" = ");
}
}
makeInsnBody(code, insn, state);
makeInsnBody(code, insn, EMPTY_FLAGS);
if (flag != Flags.INLINE) {
code.add(';');
}
}
} catch (Exception th) {
throw new CodegenException(mth, "Error generate insn: " + insn, th);
} catch (Exception e) {
throw new CodegenException(mth, "Error generate insn: " + insn, e);
}
return true;
}
private void makeInsnBody(CodeWriter code, InsnNode insn, Set<Flags> state) throws CodegenException {
@@ -277,7 +293,8 @@ public class InsnGen {
break;
case NOT:
oneArgInsn(code, insn, state, '~');
char op = insn.getArg(0).getType() == ArgType.BOOLEAN ? '!' : '~';
oneArgInsn(code, insn, state, op);
break;
case RETURN:
@@ -363,6 +380,17 @@ public class InsnGen {
filledNewArray((FilledNewArrayNode) insn, code);
break;
case FILL_ARRAY:
FillArrayNode arrayNode = (FillArrayNode) insn;
if (fallback) {
String arrStr = arrayNode.dataToString();
addArg(code, insn.getArg(0));
code.add(" = {").add(arrStr.substring(1, arrStr.length() - 1)).add("} // fill-array");
} else {
fillArray(code, arrayNode);
}
break;
case AGET:
addArg(code, insn.getArg(0));
code.add('[');
@@ -406,7 +434,7 @@ public class InsnGen {
if (wrap) {
code.add('(');
}
for (Iterator<InsnArg> it = insn.getArguments().iterator(); it.hasNext(); ) {
for (Iterator<InsnArg> it = insn.getArguments().iterator(); it.hasNext();) {
addArg(code, it.next());
if (it.hasNext()) {
code.add(" + ");
@@ -481,40 +509,20 @@ public class InsnGen {
code.startLine('}');
break;
case FILL_ARRAY:
fallbackOnlyInsn(insn);
FillArrayNode arrayNode = (FillArrayNode) insn;
Object data = arrayNode.getData();
String arrStr;
if (data instanceof int[]) {
arrStr = Arrays.toString((int[]) data);
} else if (data instanceof short[]) {
arrStr = Arrays.toString((short[]) data);
} else if (data instanceof byte[]) {
arrStr = Arrays.toString((byte[]) data);
} else if (data instanceof long[]) {
arrStr = Arrays.toString((long[]) data);
} else {
arrStr = "?";
}
code.add('{').add(arrStr.substring(1, arrStr.length() - 1)).add('}');
break;
case NEW_INSTANCE:
// only fallback - make new instance in constructor invoke
fallbackOnlyInsn(insn);
code.add("new ").add(insn.getResult().getType().toString());
code.add("new ").add(insn.getResult().getInitType().toString());
break;
case PHI:
case MERGE:
fallbackOnlyInsn(insn);
code.add(insn.getType().toString()).add("(");
code.add(insn.getType().toString()).add('(');
for (InsnArg insnArg : insn.getArguments()) {
addArg(code, insnArg);
code.add(' ');
}
code.add(")");
code.add(')');
break;
default:
@@ -522,6 +530,26 @@ public class InsnGen {
}
}
/**
* In most cases must be combined with new array instructions.
* Use one by one array fill (can be replaced with System.arrayCopy)
*/
private void fillArray(CodeWriter code, FillArrayNode arrayNode) throws CodegenException {
code.add("// fill-array-data instruction");
code.startLine();
List<LiteralArg> args = arrayNode.getLiteralArgs(arrayNode.getElementType());
InsnArg arrArg = arrayNode.getArg(0);
int len = args.size();
for (int i = 0; i < len; i++) {
if (i != 0) {
code.add(';');
code.startLine();
}
addArg(code, arrArg);
code.add('[').add(Integer.toString(i)).add("] = ").add(lit(args.get(i)));
}
}
private void oneArgInsn(CodeWriter code, InsnNode insn, Set<Flags> state, char op) throws CodegenException {
boolean wrap = state.contains(Flags.BODY_ONLY);
if (wrap) {
@@ -536,13 +564,19 @@ public class InsnGen {
private void fallbackOnlyInsn(InsnNode insn) throws CodegenException {
if (!fallback) {
throw new CodegenException(insn.getType() + " can be used only in fallback mode");
String msg = insn.getType() + " instruction can be used only in fallback mode";
CodegenException e = new CodegenException(msg);
mth.addError(msg, e);
mth.getParentClass().getTopParentClass().add(AFlag.RESTART_CODEGEN);
throw e;
}
}
private void filledNewArray(FilledNewArrayNode insn, CodeWriter code) throws CodegenException {
code.add("new ");
useType(code, insn.getArrayType());
if (!insn.contains(AFlag.DECLARE_VAR)) {
code.add("new ");
useType(code, insn.getArrayType());
}
code.add('{');
int c = insn.getArgsCount();
for (int i = 0; i < c; i++) {
@@ -557,8 +591,8 @@ public class InsnGen {
private void makeConstructor(ConstructorInsn insn, CodeWriter code)
throws CodegenException {
ClassNode cls = mth.dex().resolveClass(insn.getClassType());
if (cls != null && cls.contains(AFlag.ANONYMOUS_CLASS) && !fallback) {
inlineAnonymousConstr(code, cls, insn);
if (cls != null && cls.isAnonymous() && !fallback) {
inlineAnonymousConstructor(code, cls, insn);
return;
}
if (insn.isSelf()) {
@@ -571,33 +605,50 @@ public class InsnGen {
} else {
code.add("new ");
useClass(code, insn.getClassType());
ArgType argType = insn.getResult().getSVar().getCodeVar().getType();
if (argType.isGeneric()) {
code.add('<');
if (insn.contains(AFlag.EXPLICIT_GENERICS)) {
boolean first = true;
for (ArgType type : argType.getGenericTypes()) {
if (!first) {
code.add(',');
}
mgen.getClassGen().useType(code, type);
first = false;
}
}
code.add('>');
}
}
MethodNode callMth = mth.dex().resolveMethod(insn.getCallMth());
generateMethodArguments(code, insn, 0, callMth);
}
private void inlineAnonymousConstr(CodeWriter code, ClassNode cls, ConstructorInsn insn) throws CodegenException {
// anonymous class construction
if (cls.contains(AFlag.DONT_GENERATE)) {
code.add("/* anonymous class already generated */");
ErrorsCounter.methodWarn(mth, "Anonymous class already generated: " + cls);
return;
private void inlineAnonymousConstructor(CodeWriter code, ClassNode cls, ConstructorInsn insn) throws CodegenException {
if (this.mth.getParentClass() == cls) {
cls.remove(AFlag.ANONYMOUS_CLASS);
cls.remove(AFlag.DONT_GENERATE);
mth.getParentClass().getTopParentClass().add(AFlag.RESTART_CODEGEN);
throw new CodegenException("Anonymous inner class unlimited recursion detected."
+ " Convert class to inner: " + cls.getClassInfo().getFullName());
}
cls.add(AFlag.DONT_GENERATE);
ArgType parent;
if (cls.getInterfaces().size() == 1) {
parent = cls.getInterfaces().get(0);
} else {
parent = cls.getSuperClass();
}
cls.add(AFlag.DONT_GENERATE);
MethodNode defCtr = cls.getDefaultConstructor();
if (defCtr != null) {
if (RegionUtils.notEmpty(defCtr.getRegion())) {
defCtr.add(AFlag.ANONYMOUS_CONSTRUCTOR);
} else {
defCtr.add(AFlag.DONT_GENERATE);
// hide empty anonymous constructors
for (MethodNode ctor : cls.getMethods()) {
if (ctor.contains(AFlag.ANONYMOUS_CONSTRUCTOR)
&& RegionUtils.isEmpty(ctor.getRegion())) {
ctor.add(AFlag.DONT_GENERATE);
}
}
code.add("new ");
if (parent == null) {
code.add("Object");
@@ -615,11 +666,8 @@ public class InsnGen {
// inline method
MethodNode callMthNode = mth.root().deepResolveMethod(callMth);
if (callMthNode != null) {
if (inlineMethod(callMthNode, insn, code)) {
return;
}
callMth = callMthNode.getMethodInfo();
if (callMthNode != null && inlineMethod(callMthNode, insn, code)) {
return;
}
int k = 0;
@@ -637,13 +685,18 @@ public class InsnGen {
break;
case SUPER:
ClassInfo superCallCls = getClassForSuperCall(code, callMth);
if (superCallCls != null) {
useClass(code, superCallCls);
code.add('.');
}
// use 'super' instead 'this' in 0 arg
code.add("super").add('.');
k++;
break;
case STATIC:
ClassInfo insnCls = mth.getParentClass().getAlias();
ClassInfo insnCls = mth.getParentClass().getClassInfo();
ClassInfo declClass = callMth.getDeclClass();
if (!insnCls.equals(declClass)) {
useClass(code, declClass);
@@ -653,13 +706,45 @@ public class InsnGen {
}
if (callMthNode != null) {
code.attachAnnotation(callMthNode);
code.add(callMthNode.getAlias());
} else {
code.add(callMth.getAlias());
}
code.add(callMth.getAlias());
generateMethodArguments(code, insn, k, callMthNode);
}
@Nullable
private ClassInfo getClassForSuperCall(CodeWriter code, MethodInfo callMth) {
ClassNode useCls = mth.getParentClass();
ClassInfo insnCls = useCls.getClassInfo();
ClassInfo declClass = callMth.getDeclClass();
if (insnCls.equals(declClass)) {
return null;
}
ClassNode topClass = useCls.getTopParentClass();
if (topClass.getClassInfo().equals(declClass)) {
return declClass;
}
// search call class
ClassNode nextParent = useCls;
do {
ClassInfo nextClsInfo = nextParent.getClassInfo();
if (nextClsInfo.equals(declClass)
|| ArgType.isInstanceOf(mth.root(), nextClsInfo.getType(), declClass.getType())) {
if (nextParent == useCls) {
return null;
}
return nextClsInfo;
}
nextParent = nextParent.getParentClass();
} while (nextParent != null && nextParent != topClass);
// search failed, just return parent class
return useCls.getParentClass().getClassInfo();
}
void generateMethodArguments(CodeWriter code, InsnNode insn, int startArgNum,
@Nullable MethodNode callMth) throws CodegenException {
@Nullable MethodNode callMth) throws CodegenException {
int k = startArgNum;
if (callMth != null && callMth.contains(AFlag.SKIP_FIRST_ARG)) {
k++;
@@ -707,14 +792,43 @@ public class InsnGen {
* Add additional cast for overloaded method argument.
*/
private boolean processOverloadedArg(CodeWriter code, MethodNode callMth, InsnArg arg, int origPos) {
ArgType origType = callMth.getMethodInfo().getArgumentsTypes().get(origPos);
if (!arg.getType().equals(origType)) {
code.add('(');
useType(code, origType);
code.add(") ");
return true;
ArgType origType;
List<RegisterArg> arguments = callMth.getArguments(false);
if (arguments == null || arguments.isEmpty()) {
mth.addComment("JADX INFO: used method not loaded: " + callMth + ", types can be incorrect");
origType = callMth.getMethodInfo().getArgumentsTypes().get(origPos);
} else {
origType = arguments.get(origPos).getInitType();
if (origType.isGenericType() && !callMth.getParentClass().equals(mth.getParentClass())) {
// cancel cast
return false;
}
}
return false;
ArgType argType = arg.getType();
if (argType.equals(origType)
// null cast to object
&& (!arg.isLiteral() || ((LiteralArg) arg).getLiteral() != 0
|| (!argType.isArray() && !argType.isObject()))) {
return false;
}
if (origType.isGeneric()) {
if (argType.isObject()) {
if (!argType.isGeneric() && arg.isInsnWrap()) {
((InsnWrapArg) arg).getWrapInsn().getResult().setType(
ArgType.generic(argType.getObject(), origType.getGenericTypes()));
}
if (origType.getObject().equals(argType.getObject())) {
return false;
}
}
if (arg.isInsnWrap()) {
((InsnWrapArg) arg).getWrapInsn().add(AFlag.EXPLICIT_GENERICS);
}
}
code.add('(');
useType(code, origType);
code.add(") ");
return true;
}
/**
@@ -748,6 +862,9 @@ public class InsnGen {
return false;
}
InsnNode inl = mia.getInsn();
if (Consts.DEBUG) {
code.add("/* inline method: ").add(callMthNode.toString()).add("*/").startLine();
}
if (callMthNode.getMethodInfo().getArgumentsTypes().isEmpty()) {
makeInsn(inl, code, Flags.BODY_ONLY);
} else {
@@ -794,6 +911,7 @@ public class InsnGen {
} else {
condGen.wrap(code, insn.getCondition());
code.add(" ? ");
addCastIfNeeded(code, first, second);
addArg(code, first, false);
code.add(" : ");
addArg(code, second, false);
@@ -803,6 +921,33 @@ public class InsnGen {
}
}
private void addCastIfNeeded(CodeWriter code, InsnArg first, InsnArg second) {
if (first.isLiteral() && second.isLiteral()) {
if (first.getType() == ArgType.BYTE) {
long lit1 = ((LiteralArg) first).getLiteral();
long lit2 = ((LiteralArg) second).getLiteral();
if (lit1 != Byte.MAX_VALUE && lit1 != Byte.MIN_VALUE
&& lit2 != Byte.MAX_VALUE && lit2 != Byte.MIN_VALUE) {
code.add("(byte) ");
}
} else if (first.getType() == ArgType.SHORT) {
long lit1 = ((LiteralArg) first).getLiteral();
long lit2 = ((LiteralArg) second).getLiteral();
if (lit1 != Short.MAX_VALUE && lit1 != Short.MIN_VALUE
&& lit2 != Short.MAX_VALUE && lit2 != Short.MIN_VALUE) {
code.add("(short) ");
}
} else if (first.getType() == ArgType.CHAR) {
long lit1 = ((LiteralArg) first).getLiteral();
long lit2 = ((LiteralArg) second).getLiteral();
if (!NameMapper.isPrintableChar((char) (lit1))
&& !NameMapper.isPrintableChar((char) (lit2))) {
code.add("(char) ");
}
}
}
}
private void makeArith(ArithNode insn, CodeWriter code, Set<Flags> state) throws CodegenException {
if (insn.contains(AFlag.ARITH_ONEARG)) {
makeArithOneArg(insn, code);
@@ -3,18 +3,21 @@ package jadx.core.codegen;
import java.util.Iterator;
import java.util.List;
import com.android.dx.rop.code.AccessFlags;
import jadx.core.dex.info.ClassInfo;
import jadx.core.utils.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import 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;
@@ -22,10 +25,12 @@ 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.ErrorsCounter;
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);
@@ -80,13 +85,21 @@ public class MethodGen {
ai = ai.remove(AccessFlags.ACC_PUBLIC);
}
if (mth.getMethodInfo().isRenamed() && !ai.isConstructor()) {
code.startLine("/* renamed from: ").add(mth.getName()).add(" */");
if (mth.getMethodInfo().hasAlias() && !ai.isConstructor()) {
CodeGenUtils.addRenamedComment(code, mth, mth.getName());
}
CodeGenUtils.addSourceFileInfo(code, mth);
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.getGenericMap())) {
if (classGen.addGenericMap(code, mth.getGenericMap(), false)) {
code.add(' ');
}
if (ai.isConstructor()) {
@@ -108,86 +121,128 @@ public class MethodGen {
} else if (args.size() > 2) {
args = args.subList(2, args.size());
} else {
LOG.warn(ErrorsCounter.formatMsg(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);
// 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);
}
SSAVar argSVar = arg.getSVar();
if (argSVar != null && argSVar.contains(AFlag.FINAL)) {
argsCode.add("final ");
if (var.isFinal()) {
code.add("final ");
}
ArgType argType;
if (var.getType() == ArgType.UNKNOWN) {
// occur on decompilation errors
argType = mthArg.getInitType();
} else {
argType = var.getType();
}
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.formatMsg(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)
|| mth.contains(AFlag.INCONSISTENT_CODE)
|| mth.getRegion() == null) {
code.startLine("/*");
if (mth.root().getArgs().isFallbackMode()) {
addFallbackMethodCode(code);
code.startLine("*/");
ClassInfo clsAlias = mth.getParentClass().getAlias();
code.startLine("throw new UnsupportedOperationException(\"Method not decompiled: ")
.add(clsAlias.makeFullClsName(clsAlias.getShortName(), true))
.add(".")
.add(mth.getAlias())
.add("(")
.add(Utils.listToString(mth.getMethodInfo().getArgumentsTypes()))
.add("):")
.add(mth.getMethodInfo().getReturnType().toString())
.add("\");");
} else if (classGen.isFallbackMode()) {
dumpInstructions(code);
} else {
addRegionInsns(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("/*");
addFallbackMethodCode(code);
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) {
@@ -201,42 +256,81 @@ public class MethodGen {
code.startLine("// Can't load method instructions.");
return;
}
code.incIndent();
if (mth.getThisArg() != null) {
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 || insn.getType() == InsnType.NOP) {
if (insn == null) {
continue;
}
if (addLabels && (insn.contains(AType.JUMP) || insn.contains(AType.EXC_HANDLER))) {
if (addLabels && needLabel(insn, prevInsn)) {
code.decIndent();
code.startLine(getLabelName(insn.getOffset()) + ":");
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
*/
public static MethodGen getFallbackMethodGen(MethodNode mth) {
ClassGen clsGen = new ClassGen(mth.getParentClass(), null, true, true, true);
ClassGen clsGen = new ClassGen(mth.getParentClass(), null, false, true, true);
return new MethodGen(clsGen, mth);
}
@@ -1,6 +1,7 @@
package jadx.core.codegen;
import java.util.LinkedHashSet;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -11,12 +12,15 @@ 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;
@@ -26,7 +30,7 @@ public class NameGen {
private static final Map<String, String> OBJ_ALIAS;
private final Set<String> varNames = new LinkedHashSet<>();
private final Set<String> varNames = new HashSet<>();
private final MethodNode mth;
private final boolean fallback;
@@ -44,22 +48,36 @@ public class NameGen {
"java.lang.Byte", "b",
"java.lang.Float", "f",
"java.lang.Long", "l",
"java.lang.Double", "d"
);
"java.lang.Double", "d",
"java.lang.StringBuilder", "sb",
"java.lang.Exception", "exc");
}
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);
arg.setName(name);
var.setName(name);
return name;
}
@@ -99,39 +117,50 @@ public class NameGen {
return r;
}
private String makeArgName(RegisterArg arg) {
private String makeArgName(CodeVar var) {
if (fallback) {
return getFallbackName(arg);
return getFallbackName(var);
}
if (arg.isThis()) {
if (var.isThis()) {
return RegisterArg.THIS_ARG_NAME;
}
String name = arg.getName();
String varName = name != null ? name : guessName(arg);
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);
}
return varName;
}
private String getFallbackName(CodeVar var) {
return getFallbackName(var.getSsaVars().get(0).getAssign());
}
private String getFallbackName(RegisterArg arg) {
return "r" + arg.getRegNum();
}
private String guessName(RegisterArg arg) {
SSAVar sVar = arg.getSVar();
if (sVar != null && sVar.getName() == null) {
RegisterArg assignArg = sVar.getAssign();
InsnNode assignInsn = assignArg.getParentInsn();
if (assignInsn != null) {
String name = makeNameFromInsn(assignInsn);
if (name != null && !NameMapper.isReserved(name)) {
assignArg.setName(name);
return name;
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(arg.getType());
return makeNameForType(var.getType());
}
private String makeNameForType(ArgType type) {
@@ -149,12 +178,15 @@ public class NameGen {
}
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 extClsInfo = ClassInfo.extCls(mth.root(), type);
ClassInfo extClsInfo = ClassInfo.fromType(mth.root(), type);
String shortName = extClsInfo.getShortName();
String vName = fromName(shortName);
if (vName != null) {
@@ -227,16 +259,19 @@ public class NameGen {
if (name.startsWith("get") || name.startsWith("set")) {
return fromName(name.substring(3));
}
ArgType declType = callMth.getDeclClass().getAlias().getType();
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;
}
}
@@ -3,6 +3,7 @@ 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;
@@ -14,6 +15,7 @@ import jadx.core.dex.attributes.nodes.ForceReturnAttr;
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.instructions.SwitchNode;
import jadx.core.dex.instructions.args.CodeVar;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.NamedArg;
import jadx.core.dex.instructions.args.RegisterArg;
@@ -35,6 +37,7 @@ 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.utils.BlockUtils;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.RegionUtils;
import jadx.core.utils.exceptions.CodegenException;
@@ -75,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(';');
@@ -97,8 +100,12 @@ 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()) {
if (!insn.contains(AFlag.SKIP)) {
if (!insn.contains(AFlag.DONT_GENERATE)) {
makeInsn(insn, code);
}
}
@@ -114,6 +121,17 @@ public class RegionGen extends InsnGen {
} else {
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);
}
}
}
code.add("if (");
new ConditionGen(this).add(code, region.getCondition());
code.add(") {");
@@ -121,7 +139,7 @@ public class RegionGen extends InsnGen {
code.startLine('}');
IContainer els = region.getElseRegion();
if (els != null && RegionUtils.notEmpty(els)) {
if (RegionUtils.notEmpty(els)) {
code.add(" else ");
if (connectElseIf(code, els)) {
return;
@@ -232,7 +250,8 @@ 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, false);
@@ -258,7 +277,7 @@ public class RegionGen extends InsnGen {
}
}
} else if (k instanceof Integer) {
code.add(TypeGen.literalToString((Integer) k, arg.getType(), mth));
code.add(TypeGen.literalToString((Integer) k, arg.getType(), mth, fallback));
} else {
throw new JadxRuntimeException("Unexpected key in switch: " + (k != null ? k.getClass() : null));
}
@@ -323,7 +342,8 @@ public class RegionGen extends InsnGen {
code.add(' ');
InsnArg arg = handler.getArg();
if (arg instanceof RegisterArg) {
code.add(mgen.getNameGen().assignArg((RegisterArg) arg));
RegisterArg reg = (RegisterArg) arg;
code.add(mgen.getNameGen().assignArg(reg.getSVar().getCodeVar()));
} else if (arg instanceof NamedArg) {
code.add(mgen.getNameGen().assignNamedArg((NamedArg) arg));
}
@@ -3,7 +3,6 @@ package jadx.core.codegen;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs;
import jadx.core.deobf.NameMapper;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.PrimitiveType;
@@ -34,22 +33,24 @@ public class TypeGen {
*
* @throws JadxRuntimeException for incorrect type or literal value
*/
public static String literalToString(long lit, ArgType type, IDexNode dexNode) {
return literalToString(lit, type, dexNode.root().getStringUtils());
public static String literalToString(long lit, ArgType type, IDexNode dexNode, boolean fallback) {
return literalToString(lit, type, dexNode.root().getStringUtils(), fallback);
}
@Deprecated
public static String literalToString(long lit, ArgType type) {
return literalToString(lit, type, new StringUtils(new JadxArgs()));
}
private static String literalToString(long lit, ArgType type, StringUtils stringUtils) {
public static String literalToString(long lit, ArgType type, StringUtils stringUtils, boolean fallback) {
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;
}
@@ -64,11 +65,11 @@ public class TypeGen {
}
return stringUtils.unescapeChar(ch);
case BYTE:
return formatByte((byte) lit);
return formatByte(lit);
case SHORT:
return formatShort((short) lit);
return formatShort(lit);
case INT:
return formatInteger((int) lit);
return formatInteger(lit);
case LONG:
return formatLong(lit);
case FLOAT:
@@ -89,34 +90,34 @@ public class TypeGen {
}
}
public static String formatShort(short s) {
if (s == Short.MAX_VALUE) {
public static String formatShort(long l) {
if (l == Short.MAX_VALUE) {
return "Short.MAX_VALUE";
}
if (s == Short.MIN_VALUE) {
if (l == Short.MIN_VALUE) {
return "Short.MIN_VALUE";
}
return "(short) " + Short.toString(s);
return Long.toString(l);
}
public static String formatByte(byte b) {
if (b == Byte.MAX_VALUE) {
public static String formatByte(long l) {
if (l == Byte.MAX_VALUE) {
return "Byte.MAX_VALUE";
}
if (b == Byte.MIN_VALUE) {
if (l == Byte.MIN_VALUE) {
return "Byte.MIN_VALUE";
}
return "(byte) " + Byte.toString(b);
return Long.toString(l);
}
public static String formatInteger(int i) {
if (i == Integer.MAX_VALUE) {
public static String formatInteger(long l) {
if (l == Integer.MAX_VALUE) {
return "Integer.MAX_VALUE";
}
if (i == Integer.MIN_VALUE) {
if (l == Integer.MIN_VALUE) {
return "Integer.MIN_VALUE";
}
return Integer.toString(i);
return Long.toString(l);
}
public static String formatLong(long l) {
@@ -128,7 +129,7 @@ public class TypeGen {
}
String str = Long.toString(l);
if (Math.abs(l) >= Integer.MAX_VALUE) {
str += "L";
str += 'L';
}
return str;
}
@@ -152,7 +153,7 @@ public class TypeGen {
if (d == Double.MIN_NORMAL) {
return "Double.MIN_NORMAL";
}
return Double.toString(d) + "d";
return Double.toString(d) + 'd';
}
public static String formatFloat(float f) {
@@ -174,6 +175,6 @@ public class TypeGen {
if (f == Float.MIN_NORMAL) {
return "Float.MIN_NORMAL";
}
return Float.toString(f) + "f";
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) {
int 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;
}
}
@@ -1,14 +1,15 @@
package jadx.core.deobf;
import java.io.File;
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.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -16,19 +17,21 @@ 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 String MAP_FILE_CHARSET = "UTF-8";
private static final Charset MAP_FILE_CHARSET = UTF_8;
private final Deobfuscator deobfuscator;
private final File deobfMapFile;
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, File deobfMapFile) {
public DeobfPresets(Deobfuscator deobfuscator, Path deobfMapFile) {
this.deobfuscator = deobfuscator;
this.deobfMapFile = deobfMapFile;
}
@@ -37,12 +40,12 @@ class DeobfPresets {
* Loads deobfuscator presets
*/
public void load() {
if (!deobfMapFile.exists()) {
if (!Files.exists(deobfMapFile)) {
return;
}
LOG.info("Loading obfuscation map from: {}", deobfMapFile.getAbsoluteFile());
LOG.info("Loading obfuscation map from: {}", deobfMapFile.toAbsolutePath());
try {
List<String> lines = FileUtils.readLines(deobfMapFile, MAP_FILE_CHARSET);
List<String> lines = Files.readAllLines(deobfMapFile, MAP_FILE_CHARSET);
for (String l : lines) {
l = l.trim();
if (l.isEmpty() || l.startsWith("#")) {
@@ -65,7 +68,7 @@ class DeobfPresets {
}
}
} catch (IOException e) {
LOG.error("Failed to load deobfuscation map file '{}'", deobfMapFile.getAbsolutePath(), e);
LOG.error("Failed to load deobfuscation map file '{}'", deobfMapFile.toAbsolutePath(), e);
}
}
@@ -79,18 +82,18 @@ class DeobfPresets {
public void save(boolean forceSave) {
try {
if (deobfMapFile.exists()) {
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.getAbsolutePath());
deobfMapFile.toAbsolutePath());
}
} else {
dumpMapping();
}
} catch (IOException e) {
LOG.error("Failed to load deobfuscation map file '{}'", deobfMapFile.getAbsolutePath(), e);
LOG.error("Failed to load deobfuscation map file '{}'", deobfMapFile.toAbsolutePath(), e);
}
}
@@ -122,7 +125,7 @@ class DeobfPresets {
list.add(String.format("m %s = %s", mth.getRawFullId(), mth.getAlias()));
}
Collections.sort(list);
FileUtils.writeLines(deobfMapFile, MAP_FILE_CHARSET, list);
Files.write(deobfMapFile, list, MAP_FILE_CHARSET);
if (LOG.isDebugEnabled()) {
LOG.debug("Deobfuscation map file saved as: {}", deobfMapFile);
}
@@ -1,10 +1,11 @@
package jadx.core.deobf;
import java.io.File;
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;
@@ -17,6 +18,7 @@ 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;
@@ -41,7 +43,7 @@ public class Deobfuscator {
private final List<DexNode> dexNodes;
private final DeobfPresets deobfPresets;
private final Map<ClassInfo, DeobfClsInfo> clsMap = new HashMap<>();
private final Map<ClassInfo, DeobfClsInfo> clsMap = new LinkedHashMap<>();
private final Map<FieldInfo, String> fldMap = new HashMap<>();
private final Map<MethodInfo, String> mthMap = new HashMap<>();
@@ -61,7 +63,7 @@ public class Deobfuscator {
private int fldIndex = 0;
private int mthIndex = 0;
public Deobfuscator(JadxArgs args, @NotNull List<DexNode> dexNodes, File deobfMapFile) {
public Deobfuscator(JadxArgs args, @NotNull List<DexNode> dexNodes, Path deobfMapFile) {
this.args = args;
this.dexNodes = dexNodes;
@@ -78,8 +80,20 @@ public class Deobfuscator {
initIndexes();
}
process();
}
public void savePresets() {
deobfPresets.save(args.isDeobfuscationForceSave());
clear();
}
public void clear() {
deobfPresets.clear();
clsMap.clear();
fldMap.clear();
mthMap.clear();
ovrd.clear();
ovrdMap.clear();
}
private void initIndexes() {
@@ -128,7 +142,7 @@ public class Deobfuscator {
}
for (MethodInfo mth : o.getMethods()) {
if (aliasToUse == null) {
if (mth.isRenamed() && !mth.isAliasFromPreset()) {
if (mth.hasAlias() && !mth.isAliasFromPreset()) {
mth.setAlias(String.format("mo%d%s", id, prepareNamePart(mth.getName())));
}
aliasToUse = mth.getAlias();
@@ -140,16 +154,6 @@ public class Deobfuscator {
}
}
void clear() {
deobfPresets.clear();
clsMap.clear();
fldMap.clear();
mthMap.clear();
ovrd.clear();
ovrdMap.clear();
}
private void resolveOverriding(MethodNode mth) {
Set<ClassNode> clsParents = new LinkedHashSet<>();
collectClassHierarchy(mth.getParentClass(), clsParents);
@@ -219,12 +223,28 @@ public class Deobfuscator {
}
private void processClass(ClassNode cls) {
if (isR(cls.getParentClass())) {
return;
}
ClassInfo clsInfo = cls.getClassInfo();
String fullName = getClassFullName(clsInfo);
if (!fullName.equals(clsInfo.getFullName())) {
clsInfo.rename(cls.dex().root(), fullName);
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()) {
@@ -235,7 +255,7 @@ public class Deobfuscator {
}
}
public void renameField(FieldNode field) {
private void renameField(FieldNode field) {
FieldInfo fieldInfo = field.getFieldInfo();
String alias = getFieldAlias(field);
if (alias != null) {
@@ -247,7 +267,7 @@ public class Deobfuscator {
field.getFieldInfo().setAlias(makeFieldAlias(field));
}
public void renameMethod(MethodNode mth) {
private void renameMethod(MethodNode mth) {
String alias = getMethodAlias(mth);
if (alias != null) {
mth.getMethodInfo().setAlias(alias);
@@ -274,7 +294,8 @@ public class Deobfuscator {
*
* @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}
* @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)) {
@@ -383,7 +404,7 @@ public class Deobfuscator {
} else if (name.endsWith(".kt")) {
name = name.substring(0, name.length() - ".kt".length());
}
if (!NameMapper.isValidIdentifier(name) || NameMapper.isReserved(name)) {
if (!NameMapper.isValidAndPrintable(name)) {
return null;
}
for (DeobfClsInfo deobfClsInfo : clsMap.values()) {
@@ -391,7 +412,7 @@ public class Deobfuscator {
return null;
}
}
ClassNode otherCls = cls.dex().root().searchClassByName(cls.getPackage() + "." + name);
ClassNode otherCls = cls.root().searchClassByName(cls.getPackage() + '.' + name);
if (otherCls != null) {
return null;
}
@@ -475,29 +496,14 @@ public class Deobfuscator {
private boolean shouldRename(String s) {
int len = s.length();
return len < minLength || len > maxLength
|| !NameMapper.isValidIdentifier(s);
return len < minLength || len > maxLength;
}
private String prepareNamePart(String name) {
if (name.length() > maxLength) {
return "x" + Integer.toHexString(name.hashCode());
return 'x' + Integer.toHexString(name.hashCode());
}
if (!NameMapper.isAllCharsPrintable(name)) {
return removeInvalidChars(name);
}
return name;
}
private String removeInvalidChars(String name) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < name.length(); i++) {
int ch = name.charAt(i);
if (NameMapper.isPrintableChar(ch)) {
sb.append((char) ch);
}
}
return sb.toString();
return NameMapper.removeInvalidCharsMiddle(name);
}
private void dumpClassAlias(ClassNode cls) {
@@ -537,10 +543,7 @@ public class Deobfuscator {
}
private String getClassFullName(ClassNode cls) {
return getClassFullName(cls.getClassInfo());
}
private String getClassFullName(ClassInfo clsInfo) {
ClassInfo clsInfo = cls.getClassInfo();
DeobfClsInfo deobfClsInfo = clsMap.get(clsInfo);
if (deobfClsInfo != null) {
return deobfClsInfo.getFullName();
@@ -563,4 +566,27 @@ public class Deobfuscator {
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;
}
}
@@ -69,9 +69,7 @@ public class NameMapper {
"try",
"void",
"volatile",
"while"
)
);
"while"));
public static boolean isReserved(String str) {
return RESERVED_NAMES.contains(str);
@@ -80,15 +78,25 @@ public class NameMapper {
public static boolean isValidIdentifier(String str) {
return notEmpty(str)
&& !isReserved(str)
&& VALID_JAVA_IDENTIFIER.matcher(str).matches()
&& isAllCharsPrintable(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()
&& isAllCharsPrintable(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) {
@@ -105,6 +113,53 @@ public class NameMapper {
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() {
}
}
@@ -58,6 +58,16 @@ public class PackageNode {
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();
@@ -127,4 +137,9 @@ public class PackageNode {
}
return pp;
}
@Override
public String toString() {
return packageAlias;
}
}
@@ -8,26 +8,40 @@ public enum AFlag {
LOOP_END,
SYNTHETIC,
FINAL, // SSAVar attribute for make var final
RETURN, // block contains only return instruction
ORIG_RETURN,
DECLARE_VAR,
DONT_WRAP,
DONT_SHRINK,
DONT_INLINE,
DONT_GENERATE,
SKIP,
REMOVE,
DONT_GENERATE, // process as usual, but don't output to generated code
RESTART_CODEGEN,
DONT_RENAME, // do not rename during deobfuscation
REMOVE, // can be completely removed
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,
CUSTOM_DECLARE, // variable for this register don't need declaration
DECLARE_VAR,
ELSE_IF_CHAIN,
WRAPPED,
@@ -35,5 +49,7 @@ public enum AFlag {
FALL_THROUGH,
EXPLICIT_GENERICS,
INCONSISTENT_CODE, // warning about incorrect decompilation
}
@@ -10,12 +10,14 @@ import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
import jadx.core.dex.attributes.nodes.ForceReturnAttr;
import jadx.core.dex.attributes.nodes.IgnoreEdgeAttr;
import jadx.core.dex.attributes.nodes.JadxError;
import jadx.core.dex.attributes.nodes.JadxWarn;
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.SourceFileAttr;
import jadx.core.dex.nodes.parser.FieldInitAttr;
import jadx.core.dex.trycatch.CatchAttr;
@@ -34,9 +36,9 @@ public class AType<T extends IAttribute> {
public static final AType<AttrList<LoopInfo>> LOOP = new AType<>();
public static final AType<AttrList<EdgeInsnAttr>> EDGE_INSN = new AType<>();
public static final AType<AttrList<JadxError>> JADX_ERROR = new AType<>();
public static final AType<AttrList<JadxWarn>> JADX_WARN = new AType<>();
public static final AType<AttrList<String>> COMMENTS = new AType<>();
public static final AType<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
public static final AType<ExcHandlerAttr> EXC_HANDLER = new AType<>();
public static final AType<CatchAttr> CATCH_BLOCK = new AType<>();
@@ -54,7 +56,11 @@ public class AType<T extends IAttribute> {
public static final AType<DeclareVariablesAttr> DECLARE_VARIABLES = new AType<>();
public static final AType<LoopLabelAttr> LOOP_LABEL = new AType<>();
public static final AType<IgnoreEdgeAttr> IGNORE_EDGE = new AType<>();
public static final AType<RenameReasonAttr> RENAME_REASON = new AType<>();
private AType() {
}
// method
public static final AType<LocalVarsDebugInfoAttr> LOCAL_VARS_DEBUG_INFO = new AType<>();
// registers
public static final AType<RegDebugInfoAttr> REG_DEBUG_INFO = new AType<>();
}
@@ -25,6 +25,6 @@ public class AttrList<T> implements IAttribute {
@Override
public String toString() {
return Utils.listToString(list);
return Utils.listToString(list, "\n");
}
}
@@ -42,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);
@@ -70,21 +76,25 @@ 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();
}
@Override
@@ -121,6 +121,7 @@ public class AttributeStorage {
if (list.isEmpty()) {
return "";
}
return "A:{" + Utils.listToString(list) + "}";
list.sort(String::compareTo);
return "A[" + Utils.listToString(list) + ']';
}
}
@@ -1,5 +1,5 @@
package jadx.core.dex.attributes;
public interface IAttribute {
<T extends IAttribute> AType<T> getType();
AType<? extends IAttribute> getType();
}
@@ -42,6 +42,6 @@ public class Annotation {
@Override
public String toString() {
return "Annotation[" + visibility + ", " + atype + ", " + values + "]";
return "Annotation[" + visibility + ", " + atype + ", " + values + ']';
}
}
@@ -12,7 +12,7 @@ import jadx.core.utils.Utils;
public class AnnotationsList implements IAttribute {
public static final AnnotationsList EMPTY = new AnnotationsList(Collections.<Annotation>emptyList());
public static final AnnotationsList EMPTY = new AnnotationsList(Collections.emptyList());
private final Map<String, Annotation> map;
@@ -1,11 +1,11 @@
package jadx.core.dex.attributes.nodes;
import java.util.LinkedList;
import java.util.ArrayList;
import java.util.List;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.CodeVar;
import jadx.core.utils.Utils;
/**
@@ -13,13 +13,13 @@ import jadx.core.utils.Utils;
*/
public class DeclareVariablesAttr implements IAttribute {
private final List<RegisterArg> vars = new LinkedList<>();
private final List<CodeVar> vars = new ArrayList<>();
public Iterable<RegisterArg> getVars() {
public Iterable<CodeVar> getVars() {
return vars;
}
public void addVar(RegisterArg arg) {
public void addVar(CodeVar arg) {
vars.add(arg);
}
@@ -43,6 +43,6 @@ public class EdgeInsnAttr implements IAttribute {
@Override
public String toString() {
return "EDGE_INSN: " + start + "->" + end + " " + insn;
return "EDGE_INSN: " + start + "->" + end + ' ' + insn;
}
}
@@ -44,6 +44,6 @@ public class FieldReplaceAttr implements IAttribute {
@Override
public String toString() {
return "REPLACE: " + replaceType + " " + replaceObj;
return "REPLACE: " + replaceType + ' ' + replaceObj;
}
}
@@ -1,18 +1,18 @@
package jadx.core.dex.attributes.nodes;
import java.util.Objects;
import org.jetbrains.annotations.NotNull;
import jadx.core.utils.Utils;
public class JadxError {
public class JadxError implements Comparable<JadxError> {
private final String error;
private final Throwable cause;
public JadxError(Throwable cause) {
this(null, cause);
}
public JadxError(String error, Throwable cause) {
this.error = error;
this.error = Objects.requireNonNull(error);
this.cause = cause;
}
@@ -24,6 +24,28 @@ public class JadxError {
return cause;
}
@Override
public int compareTo(@NotNull JadxError o) {
return this.error.compareTo(o.getError());
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
JadxError other = (JadxError) o;
return error.equals(other.error);
}
@Override
public int hashCode() {
return error.hashCode();
}
@Override
public String toString() {
StringBuilder str = new StringBuilder();
@@ -34,9 +56,9 @@ public class JadxError {
}
if (cause != null) {
str.append(cause.getClass());
str.append(":");
str.append(':');
str.append(cause.getMessage());
str.append("\n");
str.append('\n');
str.append(Utils.getStackTrace(cause));
}
return str.toString();
@@ -1,21 +0,0 @@
package jadx.core.dex.attributes.nodes;
import java.util.Objects;
public class JadxWarn {
private final String warn;
public JadxWarn(String warn) {
this.warn = Objects.requireNonNull(warn);
}
public String getWarn() {
return warn;
}
@Override
public String toString() {
return "JadxWarn: " + warn;
}
}
@@ -0,0 +1,30 @@
package jadx.core.dex.attributes.nodes;
import java.util.List;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.visitors.debuginfo.LocalVar;
import jadx.core.utils.Utils;
public class LocalVarsDebugInfoAttr implements IAttribute {
private final List<LocalVar> localVars;
public LocalVarsDebugInfoAttr(List<LocalVar> localVars) {
this.localVars = localVars;
}
public List<LocalVar> getLocalVars() {
return localVars;
}
@Override
public AType<LocalVarsDebugInfoAttr> getType() {
return AType.LOCAL_VARS_DEBUG_INFO;
}
@Override
public String toString() {
return "Debug Info:\n " + Utils.listToString(localVars, "\n ");
}
}
@@ -25,7 +25,10 @@ public class PhiListAttr implements IAttribute {
StringBuilder sb = new StringBuilder();
sb.append("PHI: ");
for (PhiInsn phiInsn : list) {
sb.append('r').append(phiInsn.getResult().getRegNum()).append(" ");
sb.append('r').append(phiInsn.getResult().getRegNum()).append(' ');
}
for (PhiInsn phiInsn : list) {
sb.append("\n ").append(phiInsn).append(' ').append(phiInsn.getAttributesString());
}
return sb.toString();
}
@@ -0,0 +1,58 @@
package jadx.core.dex.attributes.nodes;
import java.util.Objects;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.visitors.debuginfo.LocalVar;
public class RegDebugInfoAttr implements IAttribute {
private final ArgType type;
private final String name;
public RegDebugInfoAttr(LocalVar var) {
this(var.getType(), var.getName());
}
public RegDebugInfoAttr(ArgType type, String name) {
this.type = type;
this.name = name;
}
public String getName() {
return name;
}
public ArgType getRegType() {
return type;
}
@Override
public AType<RegDebugInfoAttr> getType() {
return AType.REG_DEBUG_INFO;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
RegDebugInfoAttr that = (RegDebugInfoAttr) o;
return Objects.equals(type, that.type) && Objects.equals(name, that.name);
}
@Override
public int hashCode() {
return Objects.hash(type, name);
}
@Override
public String toString() {
return "D('" + name + "' " + type + ')';
}
}
@@ -0,0 +1,68 @@
package jadx.core.dex.attributes.nodes;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.AttrNode;
import jadx.core.dex.attributes.IAttribute;
public class RenameReasonAttr implements IAttribute {
private String description;
public RenameReasonAttr() {
this.description = "";
}
public RenameReasonAttr(String description) {
this.description = description;
}
public RenameReasonAttr(AttrNode node) {
RenameReasonAttr renameReasonAttr = node.get(AType.RENAME_REASON);
if (renameReasonAttr != null) {
this.description = renameReasonAttr.description;
} else {
this.description = "";
}
}
public RenameReasonAttr(AttrNode node, boolean notValid, boolean notPrintable) {
this(node);
if (notValid) {
notValid();
}
if (notPrintable) {
notPrintable();
}
}
public RenameReasonAttr notValid() {
return append("not valid java name");
}
public RenameReasonAttr notPrintable() {
return append("contains not printable characters");
}
public RenameReasonAttr append(String reason) {
if (description.isEmpty()) {
description += reason;
} else {
description += " and " + reason;
}
return this;
}
public String getDescription() {
return description;
}
@Override
public AType<RenameReasonAttr> getType() {
return AType.RENAME_REASON;
}
@Override
public String toString() {
return "RENAME_REASON:" + description;
}
}
@@ -201,8 +201,12 @@ public class AccessInfo {
}
}
public int rawValue() {
return accFlags;
}
@Override
public String toString() {
return "AccessInfo: " + type + " 0x" + Integer.toHexString(accFlags) + " (" + rawString() + ")";
return "AccessInfo: " + type + " 0x" + Integer.toHexString(accFlags) + " (" + rawString() + ')';
}
}
@@ -0,0 +1,39 @@
package jadx.core.dex.info;
import org.jetbrains.annotations.Nullable;
class ClassAliasInfo {
private final String shortName;
@Nullable
private final String pkg;
@Nullable
private String fullName;
ClassAliasInfo(@Nullable String pkg, String shortName) {
this.pkg = pkg;
this.shortName = shortName;
}
@Nullable
public String getPkg() {
return pkg;
}
public String getShortName() {
return shortName;
}
@Nullable
public String getFullName() {
return fullName;
}
public void setFullName(@Nullable String fullName) {
this.fullName = fullName;
}
@Override
public String toString() {
return "Alias{" + shortName + ", pkg=" + pkg + ", fullName=" + fullName + '}';
}
}
@@ -1,8 +1,10 @@
package jadx.core.dex.info;
import java.io.File;
import java.util.Objects;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.DexNode;
@@ -10,40 +12,29 @@ import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.exceptions.JadxRuntimeException;
public final class ClassInfo implements Comparable<ClassInfo> {
private final ArgType type;
private String pkg;
private String name;
@Nullable("for inner classes")
private String pkg;
private String fullName;
// for inner class not equals null
@Nullable
private ClassInfo parentClass;
// class info after rename (deobfuscation)
private ClassInfo alias;
private ClassInfo(RootNode root, ArgType type) {
this(root, type, true);
}
@Nullable
private ClassAliasInfo alias;
private ClassInfo(RootNode root, ArgType type, boolean inner) {
if (!type.isObject() || type.isGeneric()) {
throw new JadxRuntimeException("Not class type: " + type);
}
this.type = type;
this.alias = this;
splitNames(root, inner);
splitAndApplyNames(root, type, inner);
}
public static ClassInfo fromType(RootNode root, ArgType type) {
if (type.isArray()) {
type = ArgType.OBJECT;
}
ClassInfo cls = root.getInfoStorage().getCls(type);
ArgType clsType = checkClassType(type);
ClassInfo cls = root.getInfoStorage().getCls(clsType);
if (cls != null) {
return cls;
}
cls = new ClassInfo(root, type);
return root.getInfoStorage().putCls(cls);
ClassInfo newClsInfo = new ClassInfo(root, clsType, true);
return root.getInfoStorage().putCls(newClsInfo);
}
public static ClassInfo fromDex(DexNode dex, int clsIndex) {
@@ -57,71 +48,144 @@ public final class ClassInfo implements Comparable<ClassInfo> {
return fromType(root, ArgType.object(clsName));
}
public static ClassInfo extCls(RootNode root, ArgType type) {
ClassInfo classInfo = fromName(root, type.getObject());
return classInfo.alias;
private static ArgType checkClassType(ArgType type) {
if (type == null) {
throw new JadxRuntimeException("Null class type");
}
if (type.isArray()) {
// TODO: check case with method declared in array class like ( clone in int[])
return ArgType.OBJECT;
}
if (!type.isObject() || type.isGenericType()) {
throw new JadxRuntimeException("Not class type: " + type);
}
if (type.isGeneric()) {
return ArgType.object(type.getObject());
}
return type;
}
public void rename(RootNode root, String fullName) {
ClassInfo newAlias = new ClassInfo(root, ArgType.object(fullName), isInner());
if (!alias.getFullName().equals(newAlias.getFullName())) {
public void changeShortName(String aliasName) {
if (!Objects.equals(name, aliasName)) {
ClassAliasInfo newAlias = new ClassAliasInfo(getAliasPkg(), aliasName);
fillAliasFullName(newAlias);
this.alias = newAlias;
}
}
public boolean isRenamed() {
return alias != this;
public void changePkg(String aliasPkg) {
if (isInner()) {
throw new JadxRuntimeException("Can't change package for inner class");
}
if (!Objects.equals(getAliasPkg(), aliasPkg)) {
ClassAliasInfo newAlias = new ClassAliasInfo(aliasPkg, getAliasShortName());
fillAliasFullName(newAlias);
this.alias = newAlias;
}
}
public ClassInfo getAlias() {
return alias;
private void fillAliasFullName(ClassAliasInfo alias) {
if (parentClass == null) {
alias.setFullName(makeFullClsName(alias.getPkg(), alias.getShortName(), null, true, false));
}
}
private void splitNames(RootNode root, boolean canBeInner) {
public String getAliasPkg() {
if (isInner()) {
return parentClass.getAliasPkg();
}
return alias == null ? getPackage() : alias.getPkg();
}
public String getAliasShortName() {
return alias == null ? getShortName() : alias.getShortName();
}
public String getAliasFullName() {
if (alias != null) {
String aliasFullName = alias.getFullName();
if (aliasFullName == null) {
return makeAliasFullName();
}
return aliasFullName;
}
if (parentClass != null && parentClass.hasAlias()) {
return makeAliasFullName();
}
return getFullName();
}
public boolean hasAlias() {
if (alias != null) {
return true;
}
return parentClass != null && parentClass.hasAlias();
}
private void splitAndApplyNames(RootNode root, ArgType type, boolean canBeInner) {
String fullObjectName = type.getObject();
String clsPkg;
String clsName;
int dot = fullObjectName.lastIndexOf('.');
if (dot == -1) {
pkg = "";
clsPkg = "";
clsName = fullObjectName;
} else {
pkg = fullObjectName.substring(0, dot);
clsPkg = fullObjectName.substring(0, dot);
clsName = fullObjectName.substring(dot + 1);
}
int sep = clsName.lastIndexOf('$');
if (canBeInner && sep > 0 && sep != clsName.length() - 1) {
String parClsName = pkg + "." + clsName.substring(0, sep);
if (pkg.isEmpty()) {
String parClsName = clsPkg + '.' + clsName.substring(0, sep);
if (clsPkg.isEmpty()) {
parClsName = clsName.substring(0, sep);
}
pkg = null;
parentClass = fromName(root, parClsName);
clsName = clsName.substring(sep + 1);
} else {
pkg = clsPkg;
parentClass = null;
}
this.name = clsName;
this.fullName = makeFullClsName(clsName, false);
this.fullName = makeFullName();
}
public String makeFullClsName(String shortName, boolean raw) {
private static String makeFullClsName(String pkg, String shortName, ClassInfo parentClass, boolean alias, boolean raw) {
if (parentClass != null) {
String innerSep = raw ? "$" : ".";
return parentClass.makeFullClsName(parentClass.getShortName(), raw) + innerSep + shortName;
String parentFullName;
if (alias) {
parentFullName = raw ? parentClass.makeAliasRawFullName() : parentClass.getAliasFullName();
} else {
parentFullName = raw ? parentClass.makeRawFullName() : parentClass.getFullName();
}
return parentFullName + innerSep + shortName;
}
return pkg.isEmpty() ? shortName : pkg + "." + shortName;
return pkg.isEmpty() ? shortName : pkg + '.' + shortName;
}
private String makeFullName() {
return makeFullClsName(pkg, name, parentClass, false, false);
}
public String makeRawFullName() {
return makeFullClsName(this.name, true);
return makeFullClsName(pkg, name, parentClass, false, true);
}
public String getFullPath() {
ClassInfo usedAlias = getAlias();
return usedAlias.getPackage().replace('.', File.separatorChar)
private String makeAliasFullName() {
return makeFullClsName(getAliasPkg(), getAliasShortName(), parentClass, true, false);
}
private String makeAliasRawFullName() {
return makeFullClsName(pkg, name, parentClass, true, true);
}
public String getAliasFullPath() {
return getAliasPkg().replace('.', File.separatorChar)
+ File.separatorChar
+ usedAlias.getNameWithoutPackage().replace('.', '_');
+ getAliasNameWithoutPackage().replace('.', '_');
}
public String getFullName() {
@@ -132,25 +196,33 @@ public final class ClassInfo implements Comparable<ClassInfo> {
return name;
}
@NotNull
public String getPackage() {
if (parentClass != null) {
return parentClass.getPackage();
}
if (pkg == null) {
throw new JadxRuntimeException("Package is null for not inner class");
}
return pkg;
}
public boolean isDefaultPackage() {
return pkg.isEmpty();
return getPackage().isEmpty();
}
public String getRawName() {
return type.getObject();
}
public String getNameWithoutPackage() {
public String getAliasNameWithoutPackage() {
if (parentClass == null) {
return name;
return getAliasShortName();
}
return parentClass.getNameWithoutPackage() + "." + name;
return parentClass.getAliasNameWithoutPackage() + '.' + getAliasShortName();
}
@Nullable
public ClassInfo getParentClass() {
return parentClass;
}
@@ -168,7 +240,12 @@ public final class ClassInfo implements Comparable<ClassInfo> {
}
public void notInner(RootNode root) {
splitNames(root, false);
this.parentClass = null;
splitAndApplyNames(root, type, false);
}
public void updateNames(RootNode root) {
splitAndApplyNames(root, type, isInner());
}
public ArgType getType() {
@@ -177,7 +254,7 @@ public final class ClassInfo implements Comparable<ClassInfo> {
@Override
public String toString() {
return fullName;
return getFullName();
}
@Override
@@ -199,6 +276,6 @@ public final class ClassInfo implements Comparable<ClassInfo> {
@Override
public int compareTo(@NotNull ClassInfo o) {
return fullName.compareTo(o.fullName);
return getFullName().compareTo(o.getFullName());
}
}
@@ -156,7 +156,7 @@ public class ConstStorage {
String typeName = parts[0];
String fieldName = parts[1];
for (ClassNode innerClass : appResClass.getInnerClasses()) {
if (innerClass.getShortName().equals(typeName)) {
if (innerClass.getClassInfo().getShortName().equals(typeName)) {
return innerClass.searchFieldByName(fieldName);
}
}
@@ -1,5 +1,7 @@
package jadx.core.dex.info;
import java.util.Objects;
import com.android.dex.FieldId;
import jadx.core.codegen.TypeGen;
@@ -53,12 +55,16 @@ public final class FieldInfo {
this.alias = alias;
}
public boolean hasAlias() {
return !Objects.equals(name, alias);
}
public String getFullId() {
return declClass.getFullName() + "." + name + ":" + TypeGen.signature(type);
return declClass.getFullName() + '.' + name + ':' + TypeGen.signature(type);
}
public String getRawFullId() {
return declClass.makeRawFullName() + "." + name + ":" + TypeGen.signature(type);
return declClass.makeRawFullName() + '.' + name + ':' + TypeGen.signature(type);
}
public boolean isRenamed() {
@@ -93,6 +99,6 @@ public final class FieldInfo {
@Override
public String toString() {
return declClass + "." + name + " " + type;
return declClass + "." + name + ' ' + type;
}
}
@@ -33,6 +33,21 @@ public final class MethodInfo {
shortId = makeSignature(true);
}
private MethodInfo(ClassInfo declClass, String name, List<ArgType> args, ArgType retType) {
this.name = name;
alias = name;
aliasFromPreset = false;
this.declClass = declClass;
this.args = args;
this.retType = retType;
shortId = makeSignature(true);
}
public static MethodInfo externalMth(ClassInfo declClass, String name, List<ArgType> args, ArgType retType) {
return new MethodInfo(declClass, name, args, retType);
}
public static MethodInfo fromDex(DexNode dex, int mthIndex) {
MethodInfo mth = dex.root().getInfoStorage().getMethod(dex, mthIndex);
if (mth != null) {
@@ -43,8 +58,12 @@ public final class MethodInfo {
}
public String makeSignature(boolean includeRetType) {
return makeSignature(false, includeRetType);
}
public String makeSignature(boolean useAlias, boolean includeRetType) {
StringBuilder signature = new StringBuilder();
signature.append(name);
signature.append(useAlias ? alias : name);
signature.append('(');
for (ArgType arg : args) {
signature.append(TypeGen.signature(arg));
@@ -61,15 +80,15 @@ public final class MethodInfo {
}
public String getFullName() {
return declClass.getFullName() + "." + name;
return declClass.getFullName() + '.' + name;
}
public String getFullId() {
return declClass.getFullName() + "." + shortId;
return declClass.getFullName() + '.' + shortId;
}
public String getRawFullId() {
return declClass.makeRawFullName() + "." + shortId;
return declClass.makeRawFullName() + '.' + shortId;
}
/**
@@ -111,7 +130,7 @@ public final class MethodInfo {
this.alias = alias;
}
public boolean isRenamed() {
public boolean hasAlias() {
return !name.equals(alias);
}
@@ -147,7 +166,7 @@ public final class MethodInfo {
@Override
public String toString() {
return declClass.getFullName() + "." + name
+ "(" + Utils.listToString(args) + "):" + retType;
return declClass.getFullName() + '.' + name
+ '(' + Utils.listToString(args) + "):" + retType;
}
}
@@ -76,8 +76,8 @@ public class ArithNode extends InsnNode {
return InsnUtils.formatOffset(offset) + ": "
+ InsnUtils.insnTypeToString(insnType)
+ getResult() + " = "
+ getArg(0) + " "
+ op.getSymbol() + " "
+ getArg(0) + ' '
+ op.getSymbol() + ' '
+ getArg(1);
}
}
@@ -24,5 +24,4 @@ public enum ArithOp {
public String getSymbol() {
return this.symbol;
}
}
@@ -4,5 +4,5 @@ import jadx.core.dex.info.MethodInfo;
public interface CallMthInterface {
public MethodInfo getCallMth();
MethodInfo getCallMth();
}
@@ -35,6 +35,6 @@ public final class ConstClassNode extends InsnNode {
@Override
public String toString() {
return super.toString() + " " + clsType;
return super.toString() + ' ' + clsType;
}
}
@@ -34,6 +34,6 @@ public final class ConstStringNode extends InsnNode {
@Override
public String toString() {
return super.toString() + " \"" + str + "\"";
return super.toString() + " \"" + str + '"';
}
}
@@ -1,6 +1,7 @@
package jadx.core.dex.instructions;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import com.android.dx.io.instructions.FillArrayDataPayloadDecodedInstruction;
@@ -9,43 +10,45 @@ import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.PrimitiveType;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.exceptions.JadxRuntimeException;
public final class FillArrayNode extends InsnNode {
private static final ArgType ONE_BYTE_TYPE = ArgType.unknown(PrimitiveType.BOOLEAN, PrimitiveType.BYTE);
private static final ArgType TWO_BYTES_TYPE = ArgType.unknown(PrimitiveType.SHORT, PrimitiveType.CHAR);
private static final ArgType FOUR_BYTES_TYPE = ArgType.unknown(PrimitiveType.INT, PrimitiveType.FLOAT);
private static final ArgType EIGHT_BYTES_TYPE = ArgType.unknown(PrimitiveType.LONG, PrimitiveType.DOUBLE);
private final Object data;
private final int size;
private ArgType elemType;
public FillArrayNode(int resReg, FillArrayDataPayloadDecodedInstruction payload) {
super(InsnType.FILL_ARRAY, 0);
ArgType elType;
switch (payload.getElementWidthUnit()) {
case 1:
elType = ArgType.unknown(PrimitiveType.BOOLEAN, PrimitiveType.BYTE);
break;
case 2:
elType = ArgType.unknown(PrimitiveType.SHORT, PrimitiveType.CHAR);
break;
case 4:
elType = ArgType.unknown(PrimitiveType.INT, PrimitiveType.FLOAT);
break;
case 8:
elType = ArgType.unknown(PrimitiveType.LONG, PrimitiveType.DOUBLE);
break;
default:
throw new JadxRuntimeException("Unknown array element width: " + payload.getElementWidthUnit());
}
setResult(InsnArg.reg(resReg, ArgType.array(elType)));
super(InsnType.FILL_ARRAY, 1);
ArgType elType = getElementType(payload.getElementWidthUnit());
addArg(InsnArg.reg(resReg, ArgType.array(elType)));
this.data = payload.getData();
this.size = payload.getSize();
this.elemType = elType;
}
private static ArgType getElementType(short elementWidthUnit) {
switch (elementWidthUnit) {
case 1:
return ONE_BYTE_TYPE;
case 2:
return TWO_BYTES_TYPE;
case 4:
return FOUR_BYTES_TYPE;
case 8:
return EIGHT_BYTES_TYPE;
default:
throw new JadxRuntimeException("Unknown array element width: " + elementWidthUnit);
}
}
public Object getData() {
return data;
}
@@ -58,34 +61,27 @@ public final class FillArrayNode extends InsnNode {
return elemType;
}
public void mergeElementType(DexNode dex, ArgType foundElemType) {
ArgType r = ArgType.merge(dex, elemType, foundElemType);
if (r != null) {
elemType = r;
}
}
public List<LiteralArg> getLiteralArgs() {
public List<LiteralArg> getLiteralArgs(ArgType type) {
List<LiteralArg> list = new ArrayList<>(size);
Object array = data;
if (array instanceof int[]) {
for (int b : (int[]) array) {
list.add(InsnArg.lit(b, elemType));
list.add(InsnArg.lit(b, type));
}
} else if (array instanceof byte[]) {
for (byte b : (byte[]) array) {
list.add(InsnArg.lit(b, elemType));
list.add(InsnArg.lit(b, type));
}
} else if (array instanceof short[]) {
for (short b : (short[]) array) {
list.add(InsnArg.lit(b, elemType));
list.add(InsnArg.lit(b, type));
}
} else if (array instanceof long[]) {
for (long b : (long[]) array) {
list.add(InsnArg.lit(b, elemType));
list.add(InsnArg.lit(b, type));
}
} else {
throw new JadxRuntimeException("Unknown type: " + data.getClass() + ", expected: " + elemType);
throw new JadxRuntimeException("Unknown type: " + data.getClass() + ", expected: " + type);
}
return list;
}
@@ -101,4 +97,25 @@ public final class FillArrayNode extends InsnNode {
FillArrayNode other = (FillArrayNode) obj;
return elemType.equals(other.elemType) && data == other.data;
}
public String dataToString() {
if (data instanceof int[]) {
return Arrays.toString((int[]) data);
}
if (data instanceof short[]) {
return Arrays.toString((short[]) data);
}
if (data instanceof byte[]) {
return Arrays.toString((byte[]) data);
}
if (data instanceof long[]) {
return Arrays.toString((long[]) data);
}
return "?";
}
@Override
public String toString() {
return super.toString() + ", data: " + dataToString();
}
}
@@ -16,21 +16,21 @@ import static jadx.core.utils.BlockUtils.selectOther;
public class IfNode extends GotoNode {
// change default types priority
private static final ArgType ARG_TYPE = ArgType.unknown(
PrimitiveType.INT,
PrimitiveType.OBJECT, PrimitiveType.ARRAY,
PrimitiveType.BOOLEAN, PrimitiveType.BYTE, PrimitiveType.SHORT, PrimitiveType.CHAR);
protected IfOp op;
private BlockNode thenBlock;
private BlockNode elseBlock;
public IfNode(DecodedInstruction insn, IfOp op) {
this(op, insn.getTarget(),
InsnArg.reg(insn, 0, ARG_TYPE),
insn.getRegisterCount() == 1 ? InsnArg.lit(0, ARG_TYPE) : InsnArg.reg(insn, 1, ARG_TYPE));
super(InsnType.IF, insn.getTarget(), 2);
this.op = op;
ArgType argType = narrowTypeByOp(op);
addArg(InsnArg.reg(insn, 0, argType));
if (insn.getRegisterCount() == 1) {
addArg(InsnArg.lit(0, argType));
} else {
addArg(InsnArg.reg(insn, 1, argType));
}
}
public IfNode(IfOp op, int targetOffset, InsnArg arg1, InsnArg arg2) {
@@ -40,6 +40,22 @@ public class IfNode extends GotoNode {
addArg(arg2);
}
// change default types priority
private static final ArgType WIDE_TYPE = ArgType.unknown(
PrimitiveType.INT, PrimitiveType.BOOLEAN,
PrimitiveType.OBJECT, PrimitiveType.ARRAY,
PrimitiveType.BYTE, PrimitiveType.SHORT, PrimitiveType.CHAR);
private static final ArgType NUMBERS_TYPE = ArgType.unknown(
PrimitiveType.INT, PrimitiveType.BYTE, PrimitiveType.SHORT, PrimitiveType.CHAR);
private static ArgType narrowTypeByOp(IfOp op) {
if (op == IfOp.EQ || op == IfOp.NE) {
return WIDE_TYPE;
}
return NUMBERS_TYPE;
}
public IfOp getOp() {
return op;
}
@@ -111,7 +127,7 @@ public class IfNode extends GotoNode {
public String toString() {
return InsnUtils.formatOffset(offset) + ": "
+ InsnUtils.insnTypeToString(insnType)
+ getArg(0) + " " + op.getSymbol() + " " + getArg(1)
+ getArg(0) + ' ' + op.getSymbol() + ' ' + getArg(1)
+ " -> " + (thenBlock != null ? thenBlock : InsnUtils.formatOffset(target));
}
}
@@ -1,7 +1,10 @@
package jadx.core.dex.instructions;
import java.util.Objects;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.Utils;
public class IndexInsnNode extends InsnNode {
@@ -30,11 +33,21 @@ public class IndexInsnNode extends InsnNode {
return false;
}
IndexInsnNode other = (IndexInsnNode) obj;
return index == null ? other.index == null : index.equals(other.index);
return Objects.equals(index, other.index);
}
@Override
public String toString() {
return super.toString() + " " + InsnUtils.indexToString(index);
switch (insnType) {
case CAST:
case CHECK_CAST:
return InsnUtils.formatOffset(offset) + ": "
+ InsnUtils.insnTypeToString(insnType)
+ getResult() + " = (" + InsnUtils.indexToString(index) + ") "
+ Utils.listToString(getArguments());
default:
return super.toString() + ' ' + InsnUtils.indexToString(index);
}
}
}
@@ -1,6 +1,11 @@
package jadx.core.dex.instructions;
import java.io.EOFException;
import java.util.function.Predicate;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.android.dex.Code;
import com.android.dx.io.OpcodeInfo;
@@ -10,17 +15,16 @@ import com.android.dx.io.instructions.FillArrayDataPayloadDecodedInstruction;
import com.android.dx.io.instructions.PackedSwitchPayloadDecodedInstruction;
import com.android.dx.io.instructions.ShortArrayCodeInput;
import com.android.dx.io.instructions.SparseSwitchPayloadDecodedInstruction;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.Consts;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.PrimitiveType;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.InsnUtils;
@@ -101,15 +105,15 @@ public class InsnDecoder {
case Opcodes.CONST_4:
case Opcodes.CONST_16:
case Opcodes.CONST_HIGH16:
return insn(InsnType.CONST, InsnArg.reg(insn, 0, ArgType.NARROW),
InsnArg.lit(insn, ArgType.NARROW));
LiteralArg narrowLitArg = InsnArg.lit(insn, ArgType.NARROW);
return insn(InsnType.CONST, InsnArg.reg(insn, 0, narrowLitArg.getType()), narrowLitArg);
case Opcodes.CONST_WIDE:
case Opcodes.CONST_WIDE_16:
case Opcodes.CONST_WIDE_32:
case Opcodes.CONST_WIDE_HIGH16:
return insn(InsnType.CONST, InsnArg.reg(insn, 0, ArgType.WIDE),
InsnArg.lit(insn, ArgType.WIDE));
LiteralArg wideLitArg = InsnArg.lit(insn, ArgType.WIDE);
return insn(InsnType.CONST, InsnArg.reg(insn, 0, wideLitArg.getType()), wideLitArg);
case Opcodes.CONST_STRING:
case Opcodes.CONST_STRING_JUMBO:
@@ -117,10 +121,13 @@ public class InsnDecoder {
constStrInsn.setResult(InsnArg.reg(insn, 0, ArgType.STRING));
return constStrInsn;
case Opcodes.CONST_CLASS:
InsnNode constClsInsn = new ConstClassNode(dex.getType(insn.getIndex()));
constClsInsn.setResult(InsnArg.reg(insn, 0, ArgType.CLASS));
case Opcodes.CONST_CLASS: {
ArgType clsType = dex.getType(insn.getIndex());
InsnNode constClsInsn = new ConstClassNode(clsType);
constClsInsn.setResult(
InsnArg.reg(insn, 0, ArgType.generic(Consts.CLASS_CLASS, clsType)));
return constClsInsn;
}
case Opcodes.MOVE:
case Opcodes.MOVE_16:
@@ -403,12 +410,10 @@ public class InsnDecoder {
return new GotoNode(insn.getTarget());
case Opcodes.THROW:
return insn(InsnType.THROW, null,
InsnArg.reg(insn, 0, ArgType.unknown(PrimitiveType.OBJECT)));
return insn(InsnType.THROW, null, InsnArg.reg(insn, 0, ArgType.THROWABLE));
case Opcodes.MOVE_EXCEPTION:
return insn(InsnType.MOVE_EXCEPTION,
InsnArg.reg(insn, 0, ArgType.unknown(PrimitiveType.OBJECT)));
return insn(InsnType.MOVE_EXCEPTION, InsnArg.reg(insn, 0, ArgType.UNKNOWN_OBJECT_NO_ARRAY));
case Opcodes.RETURN_VOID:
return new InsnNode(InsnType.RETURN, 0);
@@ -442,7 +447,7 @@ public class InsnDecoder {
case Opcodes.IGET_OBJECT:
FieldInfo igetFld = FieldInfo.fromDex(dex, insn.getIndex());
InsnNode igetInsn = new IndexInsnNode(InsnType.IGET, igetFld, 1);
igetInsn.setResult(InsnArg.reg(insn, 0, igetFld.getType()));
igetInsn.setResult(InsnArg.reg(insn, 0, tryResolveFieldType(igetFld)));
igetInsn.addArg(InsnArg.reg(insn, 1, igetFld.getDeclClass().getType()));
return igetInsn;
@@ -455,7 +460,7 @@ public class InsnDecoder {
case Opcodes.IPUT_OBJECT:
FieldInfo iputFld = FieldInfo.fromDex(dex, insn.getIndex());
InsnNode iputInsn = new IndexInsnNode(InsnType.IPUT, iputFld, 2);
iputInsn.addArg(InsnArg.reg(insn, 0, iputFld.getType()));
iputInsn.addArg(InsnArg.reg(insn, 0, tryResolveFieldType(iputFld)));
iputInsn.addArg(InsnArg.reg(insn, 1, iputFld.getDeclClass().getType()));
return iputInsn;
@@ -468,7 +473,7 @@ public class InsnDecoder {
case Opcodes.SGET_OBJECT:
FieldInfo sgetFld = FieldInfo.fromDex(dex, insn.getIndex());
InsnNode sgetInsn = new IndexInsnNode(InsnType.SGET, sgetFld, 0);
sgetInsn.setResult(InsnArg.reg(insn, 0, sgetFld.getType()));
sgetInsn.setResult(InsnArg.reg(insn, 0, tryResolveFieldType(sgetFld)));
return sgetInsn;
case Opcodes.SPUT:
@@ -480,7 +485,7 @@ public class InsnDecoder {
case Opcodes.SPUT_OBJECT:
FieldInfo sputFld = FieldInfo.fromDex(dex, insn.getIndex());
InsnNode sputInsn = new IndexInsnNode(InsnType.SPUT, sputFld, 1);
sputInsn.addArg(InsnArg.reg(insn, 0, sputFld.getType()));
sputInsn.addArg(InsnArg.reg(insn, 0, tryResolveFieldType(sputFld)));
return sputInsn;
case Opcodes.ARRAY_LENGTH:
@@ -490,7 +495,7 @@ public class InsnDecoder {
return arrLenInsn;
case Opcodes.AGET:
return arrayGet(insn, ArgType.NARROW);
return arrayGet(insn, ArgType.INT_FLOAT);
case Opcodes.AGET_BOOLEAN:
return arrayGet(insn, ArgType.BOOLEAN);
case Opcodes.AGET_BYTE:
@@ -505,7 +510,7 @@ public class InsnDecoder {
return arrayGet(insn, ArgType.UNKNOWN_OBJECT);
case Opcodes.APUT:
return arrayPut(insn, ArgType.NARROW);
return arrayPut(insn, ArgType.INT_FLOAT);
case Opcodes.APUT_BOOLEAN:
return arrayPut(insn, ArgType.BOOLEAN);
case Opcodes.APUT_BYTE:
@@ -544,14 +549,16 @@ public class InsnDecoder {
return invoke(insn, offset, InvokeType.VIRTUAL, true);
case Opcodes.NEW_INSTANCE:
return insn(InsnType.NEW_INSTANCE,
InsnArg.reg(insn, 0, dex.getType(insn.getIndex())));
ArgType clsType = dex.getType(insn.getIndex());
IndexInsnNode newInstInsn = new IndexInsnNode(InsnType.NEW_INSTANCE, clsType, 0);
newInstInsn.setResult(InsnArg.reg(insn, 0, clsType));
return newInstInsn;
case Opcodes.NEW_ARRAY:
ArgType arrType = dex.getType(insn.getIndex());
return new NewArrayNode(arrType,
InsnArg.reg(insn, 0, arrType),
InsnArg.reg(insn, 1, ArgType.INT));
InsnArg.typeImmutableReg(insn, 1, ArgType.INT));
case Opcodes.FILL_ARRAY_DATA:
return fillArray(insn);
@@ -578,13 +585,21 @@ public class InsnDecoder {
InsnArg.reg(insn, 0, ArgType.UNKNOWN_OBJECT));
default:
throw new DecodeException("Unknown instruction: '" + OpcodeInfo.getName(insn.getOpcode()) + "'");
throw new DecodeException("Unknown instruction: '" + OpcodeInfo.getName(insn.getOpcode()) + '\'');
}
}
private ArgType tryResolveFieldType(FieldInfo igetFld) {
FieldNode fieldNode = dex.resolveField(igetFld);
if (fieldNode != null) {
return fieldNode.getType();
}
return igetFld.getType();
}
private InsnNode decodeSwitch(DecodedInstruction insn, int offset, boolean packed) {
int payloadOffset = insn.getTarget();
DecodedInstruction payload = insnArr[payloadOffset];
DecodedInstruction payload = getInsnByOffsetSkipNop(insnArr, payloadOffset);
Object[] keys;
int[] targets;
if (packed) {
@@ -612,7 +627,7 @@ public class InsnDecoder {
}
private InsnNode fillArray(DecodedInstruction insn) {
DecodedInstruction payload = insnArr[insn.getTarget()];
DecodedInstruction payload = getInsnByOffsetSkipNop(insnArr, insn.getTarget());
return new FillArrayNode(insn.getA(), (FillArrayDataPayloadDecodedInstruction) payload);
}
@@ -666,26 +681,34 @@ public class InsnDecoder {
private InsnNode arrayGet(DecodedInstruction insn, ArgType argType) {
InsnNode inode = new InsnNode(InsnType.AGET, 2);
inode.setResult(InsnArg.reg(insn, 0, argType));
inode.addArg(InsnArg.reg(insn, 1, ArgType.unknown(PrimitiveType.ARRAY)));
inode.addArg(InsnArg.reg(insn, 2, ArgType.INT));
inode.setResult(InsnArg.typeImmutableIfKnownReg(insn, 0, argType));
inode.addArg(InsnArg.typeImmutableIfKnownReg(insn, 1, ArgType.array(argType)));
inode.addArg(InsnArg.reg(insn, 2, ArgType.NARROW_INTEGRAL));
return inode;
}
private InsnNode arrayPut(DecodedInstruction insn, ArgType argType) {
InsnNode inode = new InsnNode(InsnType.APUT, 3);
inode.addArg(InsnArg.reg(insn, 1, ArgType.unknown(PrimitiveType.ARRAY)));
inode.addArg(InsnArg.reg(insn, 2, ArgType.INT));
inode.addArg(InsnArg.reg(insn, 0, argType));
inode.addArg(InsnArg.typeImmutableIfKnownReg(insn, 1, ArgType.array(argType)));
inode.addArg(InsnArg.reg(insn, 2, ArgType.NARROW_INTEGRAL));
inode.addArg(InsnArg.typeImmutableIfKnownReg(insn, 0, argType));
return inode;
}
private InsnNode arith(DecodedInstruction insn, ArithOp op, ArgType type) {
return new ArithNode(insn, op, type, false);
return new ArithNode(insn, op, fixTypeForBitOps(op, type), false);
}
private InsnNode arithLit(DecodedInstruction insn, ArithOp op, ArgType type) {
return new ArithNode(insn, op, type, true);
return new ArithNode(insn, op, fixTypeForBitOps(op, type), true);
}
private ArgType fixTypeForBitOps(ArithOp op, ArgType type) {
if (type == ArgType.INT
&& (op == ArithOp.AND || op == ArithOp.OR || op == ArithOp.XOR)) {
return ArgType.NARROW_NUMBERS_NO_FLOAT;
}
return type;
}
private InsnNode neg(DecodedInstruction insn, ArgType type) {
@@ -716,7 +739,7 @@ public class InsnDecoder {
}
private int getMoveResultRegister(DecodedInstruction[] insnArr, int offset) {
int nextOffset = getNextInsnOffset(insnArr, offset);
int nextOffset = getNextInsnOffsetSkipNop(insnArr, offset);
if (nextOffset >= 0) {
DecodedInstruction next = insnArr[nextOffset];
int opc = next.getOpcode();
@@ -729,21 +752,35 @@ public class InsnDecoder {
return -1;
}
public static int getPrevInsnOffset(Object[] insnArr, int offset) {
int i = offset - 1;
while (i >= 0 && insnArr[i] == null) {
i--;
private static DecodedInstruction getInsnByOffsetSkipNop(DecodedInstruction[] insnArr, int offset) {
DecodedInstruction payload = insnArr[offset];
if (payload.getOpcode() == Opcodes.NOP) {
return insnArr[getNextInsnOffsetSkipNop(insnArr, offset)];
}
if (i < 0) {
return -1;
}
return i;
return payload;
}
public static int getNextInsnOffset(Object[] insnArr, int offset) {
public static int getNextInsnOffset(DecodedInstruction[] insnArr, int offset) {
return getNextInsnOffset(insnArr, offset, null);
}
public static int getNextInsnOffsetSkipNop(DecodedInstruction[] insnArr, int offset) {
return getNextInsnOffset(insnArr, offset, i -> i.getOpcode() == Opcodes.NOP);
}
public static int getNextInsnOffset(InsnNode[] insnArr, int offset) {
return getNextInsnOffset(insnArr, offset, null);
}
public static <T> int getNextInsnOffset(T[] insnArr, int offset, Predicate<T> skip) {
int i = offset + 1;
while (i < insnArr.length && insnArr[i] == null) {
i++;
while (i < insnArr.length) {
T insn = insnArr[i];
if (insn == null || (skip != null && skip.test(insn))) {
i++;
} else {
break;
}
}
if (i >= insnArr.length) {
return -1;
@@ -66,9 +66,6 @@ public enum InsnType {
ONE_ARG,
PHI,
// merge all arguments in one
MERGE,
// TODO: now multidimensional arrays created using Array.newInstance function
NEW_MULTIDIM_ARRAY
}
@@ -36,7 +36,7 @@ public class InvokeNode extends InsnNode implements CallMthInterface {
}
}
private InvokeNode(MethodInfo mth, InvokeType invokeType, int argsCount) {
public InvokeNode(MethodInfo mth, InvokeType invokeType, int argsCount) {
super(InsnType.INVOKE, argsCount);
this.mth = mth;
this.type = invokeType;
@@ -46,6 +46,7 @@ public class InvokeNode extends InsnNode implements CallMthInterface {
return type;
}
@Override
public MethodInfo getCallMth() {
return mth;
}
@@ -73,7 +74,7 @@ public class InvokeNode extends InsnNode implements CallMthInterface {
+ InsnUtils.insnTypeToString(insnType)
+ (getResult() == null ? "" : getResult() + " = ")
+ Utils.listToString(getArguments())
+ " " + mth
+ ' ' + mth
+ " type: " + type;
}
}
@@ -1,9 +1,10 @@
package jadx.core.dex.instructions;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.ArrayList;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.instructions.args.ArgType;
@@ -11,41 +12,47 @@ import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.InstructionRemover;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
public final class PhiInsn extends InsnNode {
private final Map<RegisterArg, BlockNode> blockBinds;
// map arguments to blocks (in same order as in arguments list)
private final List<BlockNode> blockBinds;
public PhiInsn(int regNum, int predecessors) {
super(InsnType.PHI, predecessors);
this.blockBinds = new LinkedHashMap<>(predecessors);
this.blockBinds = new ArrayList<>(predecessors);
setResult(InsnArg.reg(regNum, ArgType.UNKNOWN));
add(AFlag.DONT_INLINE);
add(AFlag.DONT_GENERATE);
}
public RegisterArg bindArg(BlockNode pred) {
RegisterArg arg = InsnArg.reg(getResult().getRegNum(), getResult().getType());
RegisterArg arg = InsnArg.reg(getResult().getRegNum(), getResult().getInitType());
bindArg(arg, pred);
return arg;
}
public void bindArg(RegisterArg arg, BlockNode pred) {
if (blockBinds.containsValue(pred)) {
if (blockBinds.contains(pred)) {
throw new JadxRuntimeException("Duplicate predecessors in PHI insn: " + pred + ", " + this);
}
addArg(arg);
blockBinds.put(arg, pred);
super.addArg(arg);
blockBinds.add(pred);
}
@Nullable
public BlockNode getBlockByArg(RegisterArg arg) {
return blockBinds.get(arg);
int index = getArgIndex(arg);
if (index == -1) {
return null;
}
return blockBinds.get(index);
}
public Map<RegisterArg, BlockNode> getBlockBinds() {
return blockBinds;
public BlockNode getBlockByArgIndex(int argIndex) {
return blockBinds.get(argIndex);
}
@Override
@@ -56,16 +63,20 @@ public final class PhiInsn extends InsnNode {
@Override
public boolean removeArg(InsnArg arg) {
if (!(arg instanceof RegisterArg)) {
int index = getArgIndex(arg);
if (index == -1) {
return false;
}
RegisterArg reg = (RegisterArg) arg;
if (super.removeArg(reg)) {
blockBinds.remove(reg);
InstructionRemover.fixUsedInPhiFlag(reg);
return true;
}
return false;
removeArg(index);
return true;
}
@Override
protected RegisterArg removeArg(int index) {
RegisterArg reg = (RegisterArg) super.removeArg(index);
blockBinds.remove(index);
reg.getSVar().updateUsedInPhiList();
return reg;
}
@Override
@@ -73,19 +84,31 @@ public final class PhiInsn extends InsnNode {
if (!(from instanceof RegisterArg) || !(to instanceof RegisterArg)) {
return false;
}
BlockNode pred = getBlockByArg((RegisterArg) from);
int argIndex = getArgIndex(from);
if (argIndex == -1) {
return false;
}
BlockNode pred = getBlockByArgIndex(argIndex);
if (pred == null) {
throw new JadxRuntimeException("Unknown predecessor block by arg " + from + " in PHI: " + this);
}
if (removeArg(from)) {
bindArg((RegisterArg) to, pred);
}
removeArg(argIndex);
RegisterArg reg = (RegisterArg) to;
bindArg(reg, pred);
reg.getSVar().addUsedInPhi(this);
return true;
}
@Override
public void addArg(InsnArg arg) {
throw new JadxRuntimeException("Direct addArg is forbidden for PHI insn, bindArg must be used");
}
@Override
public void setArg(int n, InsnArg arg) {
throw new JadxRuntimeException("Unsupported operation for PHI node");
throw new JadxRuntimeException("Direct setArg is forbidden for PHI insn, bindArg must be used");
}
@Override
@@ -64,6 +64,9 @@ public class SwitchNode extends TargetInsnNode {
@Override
public boolean replaceTargetBlock(BlockNode origin, BlockNode replace) {
if (targetBlocks == null) {
return false;
}
int count = 0;
int len = targetBlocks.length;
for (int i = 0; i < len; i++) {
@@ -1,18 +1,18 @@
package jadx.core.dex.instructions.args;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.jetbrains.annotations.Nullable;
import jadx.core.Consts;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.nodes.parser.SignatureParser;
import jadx.core.utils.Utils;
public abstract class ArgType {
public static final ArgType INT = primitive(PrimitiveType.INT);
public static final ArgType BOOLEAN = primitive(PrimitiveType.BOOLEAN);
public static final ArgType BYTE = primitive(PrimitiveType.BYTE);
@@ -28,9 +28,12 @@ public abstract class ArgType {
public static final ArgType STRING = object(Consts.CLASS_STRING);
public static final ArgType ENUM = object(Consts.CLASS_ENUM);
public static final ArgType THROWABLE = object(Consts.CLASS_THROWABLE);
public static final ArgType OBJECT_ARRAY = array(OBJECT);
public static final ArgType UNKNOWN = unknown(PrimitiveType.values());
public static final ArgType UNKNOWN_OBJECT = unknown(PrimitiveType.OBJECT, PrimitiveType.ARRAY);
public static final ArgType UNKNOWN_OBJECT_NO_ARRAY = unknown(PrimitiveType.OBJECT);
public static final ArgType UNKNOWN_ARRAY = array(UNKNOWN);
public static final ArgType NARROW = unknown(
PrimitiveType.INT, PrimitiveType.FLOAT,
@@ -38,11 +41,24 @@ public abstract class ArgType {
PrimitiveType.OBJECT, PrimitiveType.ARRAY);
public static final ArgType NARROW_NUMBERS = unknown(
PrimitiveType.BOOLEAN, PrimitiveType.INT, PrimitiveType.FLOAT,
PrimitiveType.SHORT, PrimitiveType.BYTE, PrimitiveType.CHAR);
public static final ArgType NARROW_INTEGRAL = unknown(
PrimitiveType.INT, PrimitiveType.SHORT, PrimitiveType.BYTE, PrimitiveType.CHAR);
public static final ArgType NARROW_NUMBERS_NO_BOOL = unknown(
PrimitiveType.INT, PrimitiveType.FLOAT,
PrimitiveType.BOOLEAN, PrimitiveType.SHORT, PrimitiveType.BYTE, PrimitiveType.CHAR);
PrimitiveType.SHORT, PrimitiveType.BYTE, PrimitiveType.CHAR);
public static final ArgType NARROW_NUMBERS_NO_FLOAT = unknown(
PrimitiveType.INT, PrimitiveType.BOOLEAN,
PrimitiveType.SHORT, PrimitiveType.BYTE, PrimitiveType.CHAR);
public static final ArgType WIDE = unknown(PrimitiveType.LONG, PrimitiveType.DOUBLE);
public static final ArgType INT_FLOAT = unknown(PrimitiveType.INT, PrimitiveType.FLOAT);
protected int hash;
private static ArgType primitive(PrimitiveType stype) {
@@ -61,15 +77,15 @@ public abstract class ArgType {
return new WildcardType(OBJECT, 0);
}
public static ArgType wildcard(ArgType obj, int bound) {
return new WildcardType(obj, bound);
public static ArgType wildcard(ArgType obj, int bounds) {
return new WildcardType(obj, bounds);
}
public static ArgType generic(String sign) {
return new SignatureParser(sign).consumeType();
}
public static ArgType generic(String obj, ArgType[] generics) {
public static ArgType generic(String obj, ArgType... generics) {
return new GenericObject(obj, generics);
}
@@ -174,6 +190,8 @@ public abstract class ArgType {
}
private static final class GenericType extends ObjectType {
private List<ArgType> extendTypes;
public GenericType(String obj) {
super(obj);
}
@@ -182,16 +200,26 @@ public abstract class ArgType {
public boolean isGenericType() {
return true;
}
@Override
public List<ArgType> getExtendTypes() {
return extendTypes;
}
@Override
public void setExtendTypes(List<ArgType> extendTypes) {
this.extendTypes = extendTypes;
}
}
private static final class WildcardType extends ObjectType {
private final ArgType type;
private final int bounds;
public WildcardType(ArgType obj, int bound) {
public WildcardType(ArgType obj, int bounds) {
super(OBJECT.getObject());
this.type = obj;
this.bounds = bound;
this.bounds = bounds;
}
@Override
@@ -207,9 +235,9 @@ public abstract class ArgType {
/**
* Return wildcard bounds:
* <ul>
* <li> 1 for upper bound (? extends A) </li>
* <li> 0 no bounds (?) </li>
* <li>-1 for lower bound (? super A) </li>
* <li>1 for upper bound (? extends A)</li>
* <li>0 no bounds (?)</li>
* <li>-1 for lower bound (? super A)</li>
* </ul>
*/
@Override
@@ -229,7 +257,7 @@ public abstract class ArgType {
if (bounds == 0) {
return "?";
}
return "? " + (bounds == -1 ? "super" : "extends") + " " + type;
return "? " + (bounds == -1 ? "super" : "extends") + ' ' + type;
}
}
@@ -245,7 +273,7 @@ public abstract class ArgType {
}
public GenericObject(GenericObject outerType, String innerName, ArgType[] generics) {
super(outerType.getObject() + "$" + innerName);
super(outerType.getObject() + '$' + innerName);
this.outerType = outerType;
this.generics = generics;
this.hash = outerType.hashCode() + 31 * innerName.hashCode()
@@ -275,12 +303,12 @@ public abstract class ArgType {
@Override
public String toString() {
return super.toString() + "<" + Utils.arrayToString(generics) + ">";
return super.toString() + '<' + Utils.arrayToStr(generics) + '>';
}
}
private static final class ArrayArg extends KnownType {
private static final PrimitiveType[] ARRAY_POSSIBLES = new PrimitiveType[]{PrimitiveType.ARRAY};
private static final PrimitiveType[] ARRAY_POSSIBLES = new PrimitiveType[] { PrimitiveType.ARRAY };
private final ArgType arrayElement;
public ArrayArg(ArgType arrayElement) {
@@ -329,8 +357,9 @@ public abstract class ArgType {
}
@Override
boolean internalEquals(Object obj) {
return arrayElement.equals(((ArrayArg) obj).arrayElement);
boolean internalEquals(Object other) {
ArrayArg otherArr = (ArrayArg) other;
return this.arrayElement.equals(otherArr.getArrayElement());
}
@Override
@@ -369,14 +398,13 @@ public abstract class ArgType {
@Override
public ArgType selectFirst() {
PrimitiveType f = possibleTypes[0];
if (contains(PrimitiveType.OBJECT)) {
return OBJECT;
} else if (contains(PrimitiveType.ARRAY)) {
return array(OBJECT);
} else {
return primitive(f);
}
if (contains(PrimitiveType.ARRAY)) {
return array(OBJECT);
}
return primitive(possibleTypes[0]);
}
@Override
@@ -389,7 +417,7 @@ public abstract class ArgType {
if (possibleTypes.length == PrimitiveType.values().length) {
return "?";
} else {
return "?" + Arrays.toString(possibleTypes);
return "?[" + Utils.arrayToStr(possibleTypes) + ']';
}
}
}
@@ -426,6 +454,13 @@ public abstract class ArgType {
return null;
}
public List<ArgType> getExtendTypes() {
return Collections.emptyList();
}
public void setExtendTypes(List<ArgType> extendTypes) {
}
public ArgType getWildcardType() {
return null;
}
@@ -463,110 +498,6 @@ public abstract class ArgType {
public abstract PrimitiveType[] getPossibleTypes();
@Nullable
public static ArgType merge(@Nullable DexNode dex, ArgType a, ArgType b) {
if (a == null || b == null) {
return null;
}
if (a.equals(b)) {
return a;
}
ArgType res = mergeInternal(dex, a, b);
if (res == null) {
res = mergeInternal(dex, b, a); // swap
}
return res;
}
private static ArgType mergeInternal(@Nullable DexNode dex, ArgType a, ArgType b) {
if (a == UNKNOWN) {
return b;
}
if (a.isArray()) {
return mergeArrays(dex, (ArrayArg) a, b);
} else if (b.isArray()) {
return mergeArrays(dex, (ArrayArg) b, a);
}
if (!a.isTypeKnown()) {
if (b.isTypeKnown()) {
if (a.contains(b.getPrimitiveType())) {
return b;
}
return null;
} else {
// both types unknown
List<PrimitiveType> types = new ArrayList<>();
for (PrimitiveType type : a.getPossibleTypes()) {
if (b.contains(type)) {
types.add(type);
}
}
if (types.isEmpty()) {
return null;
}
if (types.size() == 1) {
PrimitiveType nt = types.get(0);
if (nt == PrimitiveType.OBJECT || nt == PrimitiveType.ARRAY) {
return unknown(nt);
} else {
return primitive(nt);
}
} else {
return unknown(types.toArray(new PrimitiveType[types.size()]));
}
}
} else {
if (a.isGenericType()) {
return a;
}
if (b.isGenericType()) {
return b;
}
if (a.isObject() && b.isObject()) {
String aObj = a.getObject();
String bObj = b.getObject();
if (aObj.equals(bObj)) {
return a.getGenericTypes() != null ? a : b;
}
if (aObj.equals(Consts.CLASS_OBJECT)) {
return b;
}
if (bObj.equals(Consts.CLASS_OBJECT)) {
return a;
}
if (dex == null) {
return null;
}
String obj = dex.root().getClsp().getCommonAncestor(aObj, bObj);
return obj == null ? null : object(obj);
}
if (a.isPrimitive() && b.isPrimitive() && a.getRegCount() == b.getRegCount()) {
return primitive(PrimitiveType.getSmaller(a.getPrimitiveType(), b.getPrimitiveType()));
}
}
return null;
}
private static ArgType mergeArrays(DexNode dex, ArrayArg array, ArgType b) {
if (b.isArray()) {
ArgType ea = array.getArrayElement();
ArgType eb = b.getArrayElement();
if (ea.isPrimitive() && eb.isPrimitive()) {
return OBJECT;
}
ArgType res = merge(dex, ea, eb);
return res == null ? null : array(res);
}
if (b.contains(PrimitiveType.ARRAY)) {
return array;
}
if (b.equals(OBJECT)) {
return OBJECT;
}
return null;
}
public static boolean isCastNeeded(DexNode dex, ArgType from, ArgType to) {
if (from.equals(to)) {
return false;
@@ -578,14 +509,62 @@ public abstract class ArgType {
return true;
}
public static boolean isInstanceOf(DexNode dex, ArgType type, ArgType of) {
public static boolean isInstanceOf(RootNode root, ArgType type, ArgType of) {
if (type.equals(of)) {
return true;
}
if (!type.isObject() || !of.isObject()) {
return false;
}
return dex.root().getClsp().isImplements(type.getObject(), of.getObject());
return root.getClsp().isImplements(type.getObject(), of.getObject());
}
public static boolean isClsKnown(RootNode root, ArgType cls) {
if (cls.isObject()) {
return root.getClsp().isClsKnown(cls.getObject());
}
return false;
}
public boolean canBeObject() {
return isObject() || (!isTypeKnown() && contains(PrimitiveType.OBJECT));
}
public boolean canBeArray() {
return isArray() || (!isTypeKnown() && contains(PrimitiveType.ARRAY));
}
public boolean canBePrimitive(PrimitiveType primitiveType) {
return (isPrimitive() && getPrimitiveType() == primitiveType)
|| (!isTypeKnown() && contains(primitiveType));
}
public static ArgType convertFromPrimitiveType(PrimitiveType primitiveType) {
switch (primitiveType) {
case BOOLEAN:
return BOOLEAN;
case CHAR:
return CHAR;
case BYTE:
return BYTE;
case SHORT:
return SHORT;
case INT:
return INT;
case FLOAT:
return FLOAT;
case LONG:
return LONG;
case DOUBLE:
return DOUBLE;
case OBJECT:
return OBJECT;
case ARRAY:
return OBJECT_ARRAY;
case VOID:
return ArgType.VOID;
}
return OBJECT;
}
public static ArgType parse(String type) {
@@ -643,6 +622,31 @@ public abstract class ArgType {
return 1;
}
public static ArgType tryToResolveClassAlias(DexNode dex, ArgType type) {
if (!type.isObject() || type.isGenericType()) {
return type;
}
ClassNode cls = dex.resolveClass(type);
if (cls == null) {
return type;
}
ClassInfo clsInfo = cls.getClassInfo();
if (!clsInfo.hasAlias()) {
return type;
}
String aliasFullName = clsInfo.getAliasFullName();
if (type.isGeneric()) {
if (type instanceof GenericObject) {
return new GenericObject(aliasFullName, type.getGenericTypes());
}
if (type instanceof WildcardType) {
return new WildcardType(ArgType.object(aliasFullName), type.getWildcardBounds());
}
}
return ArgType.object(aliasFullName);
}
@Override
public String toString() {
return "ARG_TYPE";
@@ -0,0 +1,99 @@
package jadx.core.dex.instructions.args;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class CodeVar {
private String name;
private ArgType type; // before type inference can be null and set only for immutable types
private List<SSAVar> ssaVars = new ArrayList<>(3);
private boolean isFinal;
private boolean isThis;
private boolean isDeclared;
public static CodeVar fromMthArg(RegisterArg mthArg) {
CodeVar var = new CodeVar();
var.setType(mthArg.getInitType());
var.setName(mthArg.getName());
var.setDeclared(true);
var.setThis(mthArg.isThis());
var.setSsaVars(Collections.singletonList(new SSAVar(mthArg.getRegNum(), 0, mthArg)));
return var;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public ArgType getType() {
return type;
}
public void setType(ArgType type) {
this.type = type;
}
public List<SSAVar> getSsaVars() {
return ssaVars;
}
public void addSsaVar(SSAVar ssaVar) {
if (!ssaVars.contains(ssaVar)) {
ssaVars.add(ssaVar);
}
}
public void setSsaVars(List<SSAVar> ssaVars) {
this.ssaVars = ssaVars;
}
public boolean isFinal() {
return isFinal;
}
public void setFinal(boolean aFinal) {
isFinal = aFinal;
}
public boolean isThis() {
return isThis;
}
public void setThis(boolean aThis) {
isThis = aThis;
}
public boolean isDeclared() {
return isDeclared;
}
public void setDeclared(boolean declared) {
isDeclared = declared;
}
/**
* Merge flags with OR operator
*/
public void mergeFlagsFrom(CodeVar other) {
if (other.isDeclared()) {
setDeclared(true);
}
if (other.isThis()) {
setThis(true);
}
if (other.isFinal()) {
setFinal(true);
}
}
@Override
public String toString() {
return (isFinal ? "final " : "") + type + ' ' + name;
}
}

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