Compare commits

...

633 Commits

Author SHA1 Message Date
Skylot 381afa2741 chore: fix release config 2020-11-14 16:14:13 +00:00
Skylot 82d4099541 fix(res): skip unused bytes in xml attributes parsing (#1013) 2020-11-11 16:46:26 +00:00
Skylot 5f659c8de7 fix: don't apply again already resolved types (#1012) 2020-11-10 13:31:49 +00:00
Skylot e054ea6683 fix: adjust limits to skip processing of large methods (#1012) 2020-11-10 13:23:41 +00:00
Skylot 0deafb768b fix: correct merge code variables across PHI instructions (#930) 2020-11-09 20:46:44 +00:00
Skylot cd612b452c chore: resolve some warnings reported by IntelliJ Idea 2020-11-07 15:44:57 +00:00
Skylot 009939f866 fix: prevent endless loop in method signature parsing (#1007)
Signed-off-by: Skylot <skylot@gmail.com>
2020-11-05 17:23:37 +00:00
Skylot cd006ce78e fix: improve resource type detection and remove deprecated method 2020-11-04 21:09:57 +00:00
Skylot 71bf2aa59f fix: don't apply const split if not needed 2020-11-04 20:21:24 +00:00
Skylot 714b935474 fix: improve checks for boolean to int conversion (#921) 2020-11-04 19:23:57 +00:00
Skylot 2a2b83a695 fix: do not copy input files as resources to output folder 2020-11-03 18:08:57 +00:00
Skylot acdaa95854 test(cli): add multiple input test (#936) 2020-11-03 17:27:15 +00:00
Skylot 278c5f6142 feat(gui): allow to load multiple files, button for add files (#936) 2020-11-02 18:40:55 +00:00
Skylot 8ca3cd3155 fix: don't use static vars of mutable LiteralArg class (#1005) 2020-11-01 15:56:29 +00:00
Skylot 2b7d7ce2cf fix: additional casts at use place to help type inference (#1002) 2020-10-31 16:00:57 +00:00
Skylot a22efc2eb6 fix: don't add cast for PHI insn (#1002) 2020-10-30 23:04:57 +03:00
bagipro 804c8eff91 fix(res): fixes deobfuscated resource text files saving (PR #1004)
Co-authored-by: sergey-wowwow <bugi@MacBook-Pro.local>
2020-10-30 20:28:46 +03:00
Skylot aec8ebe237 fix: reload request for correct class in method inline visitor (#999) 2020-10-29 21:41:35 +03:00
Alisson Lauffer 7353790ed1 fix(gui): only show renaming option for JClass, JField and JMethod (PR #1001) 2020-10-29 20:23:36 +03:00
Skylot e09e8e5823 chore: resolve some issues reported by sonar and lgtm 2020-10-28 14:16:50 +00:00
Skylot 92773417b3 perf(gui): reduce updates count for memory usage bar (#1000)
- disabled by default
- update only if label changed
- skip updates if app window becomes inactive
2020-10-27 12:15:04 +00:00
Skylot 12dc4fde8a fix(gui): clear jumps history on file close 2020-10-27 11:54:11 +03:00
Skylot d1e5186d4a perf(res): speed up rename of deobfuscated resources 2020-10-26 12:28:41 +00:00
Skylot d06ba95374 chore: remove unused static strings array 2020-10-26 11:15:25 +00:00
sergey-wowwow f0e6c8ea8e fix(res): put deobfuscated files to res/ folder (PR #995) 2020-10-25 20:28:06 +00:00
Skylot c94c204da2 chore: fix travis badge link 2020-10-25 22:37:46 +03:00
sergey-wowwow 71617a1c70 feat(res): fix duplicate entries and deobfuscate file names in XML resources (PR #995)
* Fixes dublicates entries in XML resources
* can't use binary search on this list
* add entry config to name comparator, preserve renames by id, improve performance
* Deobf resource files
* Add break
* Changes ResourceFile

Co-authored-by: sergey-wowwow <bugi@MacBook-Pro.local>
Co-authored-by: Skylot <skylot@gmail.com>
2020-10-25 22:18:41 +03:00
Alisson Lauffer 9f684937c6 feat(gui): rename classes and fields by popup menu in tree panel (PR #993) 2020-10-20 12:26:15 +03:00
Coin ff6665c716 fix(gui): use correct charset when writing mapping file (PR #992) 2020-10-17 18:51:08 +03:00
Skylot aa8fd3c861 fix: added another enum restore pattern (#926) 2020-10-02 18:38:08 +01:00
Skylot e2b42804d5 fix: resolve several issues with package rename and class reload (#987) 2020-10-02 16:34:38 +01:00
Skylot 0f6e942c5b build: remove check stage in gitlab build 2020-10-01 21:55:51 +01:00
Skylot c0a81978bf fix(gui): allow to rename packages (#987) 2020-10-01 21:39:23 +01:00
Skylot b76c882210 fix(gui): fix rename dialog pack (#972) 2020-09-28 19:27:48 +01:00
Skylot 14cbfbc5a4 fix: rerun signature parser on class reload (#981) 2020-09-28 16:19:52 +01:00
skylot 9b1761f71f fix: prevent zipbomb forged headers attacks (#980, PR #982) 2020-09-27 21:10:30 +03:00
Skylot 73ca2e0fa4 fix: move class unload to synchronized block (#977)
Signed-off-by: Skylot <skylot@gmail.com>
2020-09-27 18:23:23 +01:00
Skylot 4e4c7f7d7b fix: more visibility checks for @Override (#984)
Signed-off-by: Skylot <skylot@gmail.com>
2020-09-24 17:44:29 +01:00
Skylot 33f2c3f220 fix: transform loop to for with branching at end
Signed-off-by: Skylot <skylot@gmail.com>
2020-09-23 19:20:37 +01:00
Skylot dcca0133fb chore: update gradle and dependencies 2020-09-17 21:03:17 +03:00
Skylot 408201b69b fix: restore enum for java 15 2020-09-17 20:13:36 +03:00
Skylot e024628d46 chore: fix directory search for smali tests 2020-09-17 19:53:18 +03:00
Skylot 6428f29373 fix: don't add @Override for static methods (#976) 2020-09-17 16:47:44 +03:00
Skylot cfaa6ab6df fix: don't add @Override if super method is private (#976) 2020-09-17 16:39:55 +03:00
Skylot 91ee7565ac fix: resolved regression in Kotlin metadata parser 2020-09-14 19:09:54 +01:00
Skylot 1bbcac2ab3 fix: handle xor on boolean (#921) 2020-09-13 22:06:04 +01:00
Skylot 60b2353afe fix: adjust types for arithmetic instructions (#921) 2020-09-11 21:31:10 +03:00
Skylot 50cfa4c971 chore: improve error reporting in tests 2020-09-11 21:31:10 +03:00
Skylot 691bf8faca fix: checks for casts in field access, move method inline to visitor (#962)
Signed-off-by: Skylot <skylot@gmail.com>
2020-09-07 17:29:39 +01:00
Skylot 89b4ae6a6f fix: correct type and data merge for filled-array instruction 2020-08-23 15:31:46 +01:00
Skylot 605a67932f chore: remove unused array allocation 2020-08-22 15:33:43 +01:00
Jan S 1774dc74e3 feat(deobf): improve deobfuscated class names by including class properties and info about super class respectively implemented interfaces (PR #969) 2020-08-21 19:18:41 +03:00
Skylot 2d641bf049 fix: don't trust type info in signature, check before apply (#858) 2020-08-17 22:05:13 +01:00
Skylot 94a06d9b6f feat(gui): on mouse hover highlight identifiers with enabled actions (like 'find usage' or 'rename') 2020-08-17 17:38:48 +01:00
Skylot a485942731 docs: add jadx logo to readme 2020-08-16 13:24:09 +01:00
Skylot 2c1b3b2480 Merge branch 'jpstotz-copyprefs' into master 2020-08-16 15:14:10 +03:00
Skylot f1f7c70aee chore(gui): fix locales test 2020-08-16 13:13:50 +01:00
Jan Peter Stotz 718caf8cb1 chore (gui): Add button in preferences dialog to copy the preference values in text form (json) to clipboard 2020-08-16 13:05:49 +02:00
Skylot 545cd4ec12 fix: don't inline 'null' object to make code compilable (#964) 2020-08-10 20:42:11 +01:00
Skylot 444ea9ec7e fix: load .class files 2020-08-10 12:20:42 +01:00
Skylot 13609a5c44 fix: allow to inline variables around 'monitor-exit' in synchronized block 2020-08-09 15:19:54 +01:00
Skylot d6ad21f6f9 fix: correct detection of exits in synchronized block (#942) 2020-08-09 17:04:54 +03:00
Skylot 593a32a689 refactor: use same instance for empty type vars annotation 2020-08-09 17:04:54 +03:00
Skylot 7fed5534eb refactor: add method info caching to speed up initial loading 2020-08-07 19:22:01 +01:00
Skylot 1560284831 refactor: fix zip security in dex plugin, remove smali deps from jadx-core 2020-08-06 13:39:40 +01:00
Skylot 558a86739f fix: bring back smali files support (#961) 2020-08-05 19:58:43 +01:00
Skylot bfd60b733a fix: handle method arguments in primitive types conversion (#956) 2020-08-04 12:26:31 +01:00
Skylot ae26512601 fix: use internal usage info for rename, fix index refresh (#791) 2020-08-03 11:55:19 +01:00
Skylot 498c2f5256 Merge branch 'rename' into master 2020-08-02 13:25:15 +03:00
Skylot 459f12d61f fix: several improvements for generics and type inference
- support 'extends' for generic type variables
- insert cast instructions to help type inference (#956)
- correct move instructions insertion (to resolve types in PHI)

Signed-off-by: Skylot <skylot@gmail.com>
2020-07-31 18:47:32 +01:00
Skylot bcd6e537e0 fix: correct parsing for array-data-payload 2020-07-30 19:25:15 +01:00
Skylot 867c3413e9 test: replace const values 2020-07-20 13:56:02 +01:00
Skylot 0f808d5c60 fix: resolve char literal incorrect print as string (#856) 2020-07-20 10:33:10 +01:00
Skylot f5767dd865 fix: use recursive objects for nested inner generic classes (#869) 2020-07-16 14:14:06 +01:00
Skylot 631a855bac chore: resolve deprecations in tests 2020-07-13 09:26:55 +01:00
Skylot c616b5b03b build: resolve gradle deprecation warnings 2020-07-11 12:51:38 +01:00
Skylot 3e9f4a5060 fix: improve limit calculation for type updates in type inference (#854) 2020-07-10 18:36:17 +01:00
Skylot 31434186ab fix: improve boolean type handling in type inference 2020-07-08 12:34:21 +01:00
Skylot e81ba1c431 build: fix latest java version for gitlab build 2020-07-06 14:16:57 +01:00
Skylot b219ab607f fix: exclude directories from dex convertion results 2020-07-05 19:02:14 +03:00
Skylot cd8307f432 chore: remove unused d8 from convert plugin 2020-07-05 19:02:14 +03:00
Skylot a720105208 chore: update dependencies 2020-07-05 19:02:14 +03:00
Skylot 34692c41f2 Merge branch 'master' into rename
# Conflicts:
#	jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java
#	jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java
2020-07-03 16:39:02 +01:00
Bet4 8a8b945eb8 fix(gui): run indexJob before rename (PR #910) 2020-07-03 17:26:24 +03:00
M.Yasoob Ullah Khalid ☺ 99569c52ac docs: fixed grammar slightly (PR #957) 2020-07-03 17:22:00 +03:00
Snowhite f696dc715b fix(gui): update Messages_zh_CN.properties (PR #959)
modifying error of positional argument in Chinese search result text
2020-07-03 17:19:18 +03:00
Jan S 21b8552386 fix (gui): launch4j exe startup wrapper does not apply maximum heap configuration correctly (#951) 2020-06-11 17:06:43 +03:00
Skylot 4b1886700d fix(gui): merge plugins service files (#949)
Signed-off-by: Skylot <skylot@gmail.com>
2020-06-11 14:26:23 +01:00
Skylot a83ca1f85b fix: don't use FileChannel on ZipFs to avoid creation of temp files (#950) 2020-06-10 22:39:56 +01:00
Skylot 65553c156c fix: restore android R class (#947) (regression) 2020-06-06 19:19:55 +01:00
Skylot 440357d2e8 fix: allow cross-block move inline (#946)
Signed-off-by: Skylot <skylot@gmail.com>
2020-06-01 22:24:42 +01:00
Skylot 5e62b9077a fix: resolve ClassCastException on encoded value access (#946) 2020-06-01 23:39:18 +03:00
Skylot 6192ced214 fix: improve type inference of type variables in method invoke (#913) 2020-06-01 23:39:12 +03:00
Skylot ae31fee8dd fix: add cast to exact type on field access (#729) 2020-05-29 17:53:39 +01:00
Skylot e7b00cc76e fix: add methods and fields types into usage info (#729) 2020-05-27 20:55:32 +01:00
Skylot 7d29c5d766 fix: correct skip size for sparse switch payload 2020-05-27 20:54:28 +01:00
Skylot 15776c4ce3 fix: make class public if some method going to be inlined (#729) 2020-05-26 21:23:27 +01:00
Skylot d720179deb fix: collect class usage and fix class access modifiers (#729) 2020-05-22 12:29:34 +01:00
Skylot 0d69e0ac97 refactor: use own dex parser instead deprecated dx lib 2020-05-21 22:02:20 +01:00
Skylot 09e267f8bc fix: resolve generic types in method arguments (#913) 2020-05-17 14:58:34 +01:00
Soul Trace 705ceca42a fix(gui): RenameDialog: Warn user if deobfuscation settings are invalid (PR #935)
The rename functionality relies on deobfuscation for now - so let the user know
this and ask the user to enable deobfuscation to get rename working.

The "Force rewrite deobfuscation map file" option effectively disables
renaming, because renaming relies on deobfuscation map modification for now,
but Force rewrite rewrites the map on each file reload, destroying changes.
So. let the user know this issue instead of silent failure.
2020-05-14 22:56:35 +03:00
Skylot 58722d372e fix: don't generate string concatenation without assign to variable 2020-05-14 12:25:42 +03:00
Skylot f482b8b95c fix: restart comment to escape strings in insn fallback dump 2020-05-14 12:25:42 +03:00
Jan S 21e4dee0e2 chore: exclude project build directories from code style checking (PR #934) 2020-05-13 20:46:58 +03:00
Jan S c7a12ad75b fix(res): resource XML generation for parent attribute added (PR #933, #931) 2020-05-13 20:25:45 +03:00
Skylot 7cd77ae379 fix: try raw types to help type inference (#913) 2020-05-12 19:23:49 +01:00
Skylot d59c99ddfe fix: attach method details before OverrideMethodVisitor 2020-05-12 19:23:27 +01:00
Skylot 85760cc844 fix: replace type variables in arrays (#913) 2020-05-12 19:16:52 +01:00
Skylot 0692464b85 fix: mark override methods and fix return type (#913) 2020-05-11 21:35:00 +01:00
Skylot 3968222744 style: fix incorrect import 2020-05-10 15:46:24 +01:00
Skylot c05368d92e style: prefer use Stream.of method 2020-05-10 15:46:06 +01:00
Skylot 404136cd72 fix: improve type inference for generics in invoke insn (#927) 2020-05-10 14:20:27 +01:00
Skylot b1d5ed0066 fix: don't modify method argument types in MethodInvokeVisitor (#927 #836) 2020-05-09 21:10:37 +01:00
Skylot e22474e0a7 fix: inline move instructions to help process constructors (#927) 2020-05-09 15:38:18 +01:00
Skylot 45b7304630 fix: workaround for link clicks in RSyntaxTextArea (#929) 2020-05-08 20:01:20 +01:00
Skylot 692536c584 chore: ignore tests in tmp package 2020-05-04 15:47:30 +01:00
Skylot 4e3d85887c fix: correctly process extended enums if class is not inner (#924) 2020-05-04 15:19:17 +01:00
Skylot 87852328da fix: resolve error in SkipMethodArgsAttr if method not yet loaded (#924) 2020-05-04 11:50:31 +01:00
Skylot 2207cd7b52 fix: inline class constants to fix enum transform (#916) 2020-05-03 22:32:32 +03:00
Skylot f3cd4e38d7 fix: check enum constructor content before removing (#922) 2020-05-03 22:32:32 +03:00
Skylot 2dce1c0ad9 build: update gradle and dependencies 2020-05-03 22:32:27 +03:00
Jan S 258ecad277 fix(res): XML parsing: handling of TYPE_DYNAMIC_REFERENCE entries (#919, PR #923) 2020-04-29 21:47:20 +03:00
Skylot 7f5092c0d5 fix: redone shadowed fields handling (#897) 2020-04-27 22:29:34 +01:00
Skylot a7f315f596 fix: split CONST used in PHI to help type inference (#900) 2020-04-26 20:37:34 +01:00
Skylot 4dc4aa122b fix: don't duplicate result arg with instruction copy (breaks SSA variable assign) 2020-04-26 18:44:43 +01:00
Skylot e3f388af11 fix(deobf): resolve NPE when package is empty (if rename is disabled) 2020-04-26 20:40:20 +03:00
Skylot 83196628c9 docs: fix issue and PR templates 2020-04-26 20:40:20 +03:00
skylot 315c07d4f6 feat(res): rename resources keys if contains unprintable chars or duplicates (#844) (PR #909) 2020-04-21 22:33:35 +03:00
Skylot 47dadf0a43 fix: use correct class for Throwable, insert catch arg name if not defined (#896) 2020-04-18 21:28:29 +03:00
yunlong17568 c62039f327 fix(gui): use env %JAVA_HOME% as the JRE path (PR #895)
Co-authored-by: yunlong.yang <yunlong.yang@inveno.com>
2020-04-11 20:41:27 +03:00
Skylot a5ea560edc fix: preserve code semantics on array-for-each transform (#893) 2020-03-31 21:41:36 +01:00
Skylot e09e933f9c fix: additional checks for class signature 2020-03-22 12:12:47 +00:00
Skylot dbd00d5a8b refactor: use instance methods for error and warning notifications 2020-03-22 12:01:42 +00:00
Skylot 2da772df8e fix: resolve some cases of switch in loop (#876) 2020-03-21 18:42:27 +00:00
Skylot 4cdad0e83e fix: correct method exit blocks collection (#876) 2020-03-17 19:38:45 +00:00
Skylot 2f780da305 fix: remove enum methods after instructions check (#884) 2020-03-16 20:31:47 +00:00
Skylot 9d8066f4b8 fix: don't remove synthetic methods from enum (#884) 2020-03-16 19:06:52 +00:00
Jan S 2cc49256a9 chore(gui): optimized the text search for classes, methods and fields (PR #887) 2020-03-15 18:55:32 +03:00
Jan S 79ab2e11f8 chore(gui): preferences dialog changed to two-column mode (PR #888) 2020-03-15 18:21:08 +03:00
Jan S c1f4302e62 feat(gui): allow to search for full method/field signature, not only the name (PR #880) 2020-03-10 12:11:44 +03:00
Skylot f66ec9168c test: update test TestAnonymousInline 2020-03-09 22:44:30 +03:00
Skylot 37aecf72cb Merge branch 'master' into rename 2020-03-09 19:43:40 +00:00
Bendegúz Ács 3c7be5e9be fix: use super instead this when super member is shadowed (PR #878)
* Added failing test for super member shadowing.
* Fixed new test containing incorrect variable names.
* Implemented marking super fields used in a subclass with super keyword.
* Renamed member variable in the example to reflect smali and test.
* Fixed formatting and imports.
2020-03-07 19:52:21 +03:00
Skylot 89dbae8f8e fix: resolve NPE while compare outer generic types 2020-02-29 19:55:16 +00:00
Skylot 5eec8f754d fix: class resolving issues (#867) 2020-02-29 19:24:01 +00:00
Skylot 49a82c8388 fix: method info cache error (#868) 2020-02-29 19:22:18 +00:00
Skylot 26bad4a1cd fix: replace constants for arrays in annotations (#831) 2020-02-29 18:55:30 +00:00
Skylot fa0a38d3aa fix: don't use OS specific new line chars (#861) 2020-02-23 15:37:07 +00:00
Jan S b56fd4d29a chore: add inlined class name as comment (PR #865)
* chore: add inlined class name as comment
* chore: adapt unit test for inlined class name as comment
2020-02-23 17:06:10 +03:00
Jan S 4520747167 fix: concurrent access may cause an java.lang.ClassCastException in JNodeCache (PR #864) 2020-02-23 16:22:49 +03:00
Skylot e444ecb2c7 fix: improve wildcard types compare (#857) 2020-02-17 19:31:13 +00:00
Skylot 1336c47d18 fix: speed up switch processing by skip not relevant exits (#846) 2020-02-16 15:50:02 +00:00
Skylot 519a74e8d2 fix: improve type inference for arrays (#837) 2020-02-16 17:12:31 +03:00
Skylot dea7714ef3 feat: add methods information from standard library, improve generics and varargs restore (#836) 2020-02-16 17:12:31 +03:00
Skylot 74b88b407e chore: add config for lgtm.com analysis 2020-02-14 19:29:24 +00:00
Skylot 57c28c61e0 fix: restore enum for several blocks in class init method 2020-02-14 18:08:37 +00:00
Skylot 87320348dd chore: update all dependencies and gradle 2020-02-12 20:43:27 +00:00
Skylot fcb70e69c1 fix: don't print commented case value if it is an instruction (#850) 2020-02-11 22:08:14 +03:00
Jan S 4859629850 fix: record inlined classes and generate Smali code for them (PR #851, #848)
* fix: additionally show smali code of all inlined classes (recursively)
* variable name corrected
2020-02-11 22:06:07 +03:00
Jan S bd0d248fd0 fix: additionally show smali code of all inner classes (recursively) (PR #849, #848) 2020-02-10 22:00:32 +03:00
Skylot c24a3edb44 fix: inline assignment instruction from same block (#820) 2020-02-09 14:40:27 +00:00
Skylot d0f197ea3d fix: shrink code if region maker add FORCE_ASSIGN_INLINE to insn (#845) 2020-02-09 14:36:59 +00:00
Skylot 5502d93cd5 fix: additional checks before insert move to help type inference (#843) 2020-02-04 18:45:27 +00:00
Skylot 073fd76aa2 chore: update actions/checkout in gradle wrapper validation (#832) 2020-02-04 17:19:16 +00:00
Yaroslav Yadrov 492a3f6928 feat(deobf): add classname parsing for Kotlin metadata (PR #842, #758) 2020-02-02 17:08:29 +03:00
Skylot 1ce8fa8bdd fix: don't apply types if search failed (#840) 2020-01-28 20:07:58 +00:00
Skylot 1bb90233b9 test: NYI tests for #836 and #837 2020-01-27 19:23:35 +00:00
Skylot 49ce92f540 fix: remove move instructions with unused result (#835) 2020-01-24 17:37:53 +00:00
Skylot 2107da2e1a fix: improve 'out' block detection in switch (#826) 2020-01-23 18:58:17 +00:00
S-trace d98321026d gui: RenameDialog: Unload classes in refreshTabs() before refreshing
This should fix possible problems with incorrect refresh for open classes.
2020-01-20 10:13:20 +03:00
S-trace 0b6fabbc71 gui: Perform classes unload in the background UnloadJob
This should improve interface responsibility if there are many classes
to refresh after rename.
2020-01-20 10:13:20 +03:00
Skylot bb0fad2834 fix: resolve multi-threaded unloading 2020-01-20 10:13:20 +03:00
Skylot 08f9722e33 Merge branch 'master' into rename 2020-01-20 10:12:49 +03:00
Skylot 62ca30bbc6 fix: additional patterns to restore enum classes (#830) 2020-01-19 11:12:23 +00:00
S-trace 467403362d core: ConstStorage: Use ConcurrentHashMap for values map in ValueStorage
Exception in thread "pool-9-thread-7" java.util.ConcurrentModificationException
        at java.util.HashMap$HashIterator.nextNode(HashMap.java:1445)
        at java.util.HashMap$EntryIterator.next(HashMap.java:1479)
        at java.util.HashMap$EntryIterator.next(HashMap.java:1477)
        at jadx.core.dex.info.ConstStorage$ValueStorage.removeForCls(ConstStorage.java:61)
        at jadx.core.dex.info.ConstStorage.removeForClass(ConstStorage.java:100)
        at jadx.core.dex.nodes.ClassNode.deepUnload(ClassNode.java:290)
        at jadx.core.dex.nodes.ClassNode.deepUnload(ClassNode.java:295)
        at jadx.core.dex.nodes.ClassNode.reloadCode(ClassNode.java:284)
        at jadx.api.JavaClass.refresh(JavaClass.java:62)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:748)
2020-01-17 10:38:15 +03:00
S-trace 265a78cd23 core: ConstStorage: Do not remove values from duplicates set in removeForCls()
If the constant already got duplicates - it will have duplicates even after
class reload, won't it?
But removing this constant from duplicates may break constants replacing
(just imagine a class TestClass with public static final int TEST_CONSTANT = 1;
 - after reloading TestClass each "(int) 1" litheral will be replaced to
"TEST_CONSTANT" reference in each reloaded class and trivial increments will
become werid expressions (int y = x + 1; will become
int y = x + TestClass.TEST_CONSTANT;)).
2020-01-17 10:38:15 +03:00
S-trace a0e13d0481 gui: RenameDialog: Rename tmp deobf map file too
Fixes /tmp/deobf_tmp_*.txt temporary files accumulation on renames.
2020-01-17 10:38:15 +03:00
S-trace 77fc6435a0 core: ConstStorage: Don't put known duplicate value to ValueStorage.values map
This is a microoptimization, which remove unnecessary values.put() and
values.remove() pair of operations if ValueStorage.put() is called for a
known duplicated value.
2020-01-17 10:38:15 +03:00
Skylot cd7e5bf020 Merge branch 'master' into rename 2020-01-17 10:37:43 +03:00
Skylot 5e7388f686 refactor: fix several issues reported by sonar 2020-01-16 12:16:32 +03:00
Skylot 1047e751e6 chore: fix github action for gradle wrapper validation 2020-01-16 10:47:31 +03:00
Jonathan Leitschuh c598871764 chore: official Gradle Wrapper Validation GitHub Action (PR #832)
See: https://github.com/gradle/wrapper-validation-action
2020-01-16 10:19:39 +03:00
Skylot 2921c66834 fix: replace constants inside annotations (#831) 2020-01-14 19:52:03 +00:00
Skylot 7bbb083c36 refactor: small changes to switch region and region debug print 2020-01-13 19:27:52 +00:00
Skylot 531650c9f2 refactor: allow to change temp dir using system property JADX_TMP_DIR 2020-01-13 15:32:32 +03:00
Skylot f3098741c3 test: switch with fallthrough cases (#826) 2020-01-08 14:26:40 +00:00
Skylot 9dbffef140 fix: deep reload for inner classes, const values and anonymous classes 2020-01-05 22:12:13 +03:00
Jan S c97e504686 fix: additionally show smali code of inner classes (PR #824) 2020-01-05 12:46:07 +03:00
Skylot 0c4b807caa fix: improve ClassNode reloading and revert some changes 2020-01-03 20:46:47 +03:00
S-trace 1eca2b6cb0 core: ClassInfo: Do not ignore setting alias to original class name
Fixes trouble with renaming class back to its original name.
2020-01-03 06:09:26 +03:00
S-trace 17cbb3eab0 core: Fix possible NPE in DebugInfoParser.addrChange()
This may happen because MethodNode.unloadInsnArr() call from BlockSplitter.visit() - after it instructions[] become null.
So, try to reload method before processing its instructions array and bail if insnArr still null even after reloading method.
2020-01-03 06:09:25 +03:00
S-trace c72f2a2c96 core: RenameReasonAttr: Do not append new reason if it is already there
Fixes possible "reason: invalid class name and invalid class name" comments after class refresh.
2020-01-03 06:09:23 +03:00
S-trace 610f531653 core: EnumVisitor: Do not remove ACC_ENUM access flag
This flag is necessary for class refresh, and should be left there.
Fixes disappearing of enum fields after class refresh.
2020-01-03 06:09:22 +03:00
S-trace 1e9b28b369 core: AType: Add FIELD_INIT and SOURCE_FILE to SKIP_ON_UNLOAD set
Fixes disappearing litheral values for replaced constants in switches and source file names..
2020-01-03 06:09:21 +03:00
S-trace 6d4caca6cc core: ClassModifier: Don't skip methods with SKIP_FIRST_ARG attr
Skipping those methods on class refresh leads to "M.this = r1;" like
assigments appears in the inner class constructors.

Unsure is this saint or not.
2020-01-03 06:09:20 +03:00
S-trace 15953f832f core: Do not call addConstField() on class refresh - fix constants replacing
There is a duplicate control in the ConstStorage.ValueStorage.add() method,
so each constant should be added only once, and not be added on class refresh.

Fixes "Replace constants" failure after renaming any node.
2020-01-03 06:09:19 +03:00
S-trace d346ed0570 core: MethodNode: Fix possible decompilation failure on refresh inner class
checkInstructions() may fail with NPE:
ERROR - NullPointerException in pass: BlockSplitter in method: com.google.common.primitives.Ints.IntArrayAsList.<init>(int[], int, int):void, dex: out.dex
java.lang.NullPointerException: null
        at jadx.core.dex.nodes.MethodNode.checkInstructions(MethodNode.java:159)
        at jadx.core.dex.visitors.blocksmaker.BlockSplitter.visit(BlockSplitter.java:49)
        at jadx.core.dex.visitors.DepthTraversal.visit(DepthTraversal.java:31)
        at jadx.core.dex.visitors.DepthTraversal.lambda$visit$1(DepthTraversal.java:16)
        at java.util.ArrayList.forEach(ArrayList.java:1257)
        at jadx.core.dex.visitors.DepthTraversal.visit(DepthTraversal.java:16)
        at jadx.core.dex.visitors.DepthTraversal.lambda$visit$0(DepthTraversal.java:15)
        at java.util.ArrayList.forEach(ArrayList.java:1257)
        at jadx.core.dex.visitors.DepthTraversal.visit(DepthTraversal.java:15)
        at jadx.core.ProcessClass.process(ProcessClass.java:41)
        at jadx.core.ProcessClass.generateCode(ProcessClass.java:58)
        at jadx.core.dex.nodes.ClassNode.decompile(ClassNode.java:292)
        at jadx.core.dex.nodes.ClassNode.decompile(ClassNode.java:271)
        at jadx.core.dex.nodes.ClassNode.refresh(ClassNode.java:303)
        at jadx.api.JavaClass.refresh(JavaClass.java:61)
        at jadx.gui.treemodel.JClass.refresh(JClass.java:63)
...

This happens because MethodNode.unloadInsnArr() call from BlockSplitter.visit() - after it instructions[] become null.
So, try to reload method before processing its instructions array.
2020-01-03 06:09:17 +03:00
S-trace df520a1134 core: ClassNode: Check is field really static or not in loadStaticValues()
Fixes appearing of the 0/null/false initializers for instance final fields.
2020-01-03 06:09:16 +03:00
S-trace f90fc1d5ec core: ClassNode: Load recursively missing information on refresh()
Fixes loss of static identifiers, comments, annotations and source file name after rename.
2020-01-03 06:09:14 +03:00
S-trace 797904afb5 gui: Perform refresh of non-displayed classes in background thread
After renaming some classes needs to be redecompiled to reflect new state.
Move recompilation of non-displayed classes to background thread.
This should improve performance on weak machines.
2020-01-03 06:09:13 +03:00
S-trace 489fbb5e42 gui: Improve performance of renaming
Fixes multiple decompilation of classes - now each class decompiled just once.
2020-01-03 06:09:12 +03:00
S-trace 9dd5a9ef89 gui: More advanced implementation of renaming
This implementation does not reload file after renaming, and so works faster.
2020-01-03 06:09:08 +03:00
Skylot 02213802c5 fix: make correct replacement for synthetic constructor (#808) 2019-12-28 08:42:39 +00:00
Skylot 8365855475 fix(gui): search only in short names of methods and fields (#818) 2019-12-27 18:55:11 +00:00
Skylot 55eb86d2d5 fix(gui): search class with --select-class also by not deobfuscated names 2019-12-27 18:52:28 +00:00
Skylot 0a9b944431 refactor(gui): add on finish runnable to open action 2019-12-27 18:42:07 +00:00
Skylot f1e229193c fix: resolve exception on assign inline 2019-12-27 21:20:14 +03:00
Skylot 04e309aeff fix: additional checks for 'if' blocks inside loops (#809) 2019-12-27 21:20:14 +03:00
Skylot 99eb31b312 fix(gui): add dots for progress titles 2019-12-27 21:19:37 +03:00
Soul Trace 287275d886 fix(gui): resolve --select-class option regression (PR #816) 2019-12-26 20:29:47 +03:00
Skylot af6f8b5391 fix(gui): resolve exceptions during index (#812) 2019-12-23 22:39:06 +03:00
Hen Ry 3b9b103c3f fix(gui): update Deutsch translation (PR #811) 2019-12-23 20:48:17 +03:00
Skylot 0c55ab9001 fix: resolve class cast exception introduced in CodeWriter refactoring (#810) 2019-12-22 16:19:47 +00:00
Skylot 9c88f70740 fix(gui): load file in background thread and show progress indicator 2019-12-22 15:56:50 +00:00
Skylot 9ab003df4c feat(gui): map back and forward mouse keys for navigation (#807) 2019-12-21 16:15:05 +00:00
Skylot 7f8d03d192 style: fix some sonar and compiler warnings 2019-12-21 15:37:25 +03:00
Skylot c64ffde11f refactor: use ICodeInfo interface instead CodeWriter 2019-12-21 12:36:54 +00:00
Hen Ry 1568008c67 fix(gui): improve Deutsch translation (PR #806) 2019-12-20 21:34:32 +03:00
Skylot 84211576e4 fix(gui): add Deutsch to locales list (#804) 2019-12-19 17:20:50 +00:00
Hen Ry 553f5b063f feat(gui): add Messages_de_DE.properties (PR #804) 2019-12-19 20:14:46 +03:00
Skylot f5d1f288d0 fix: don't inline constants in synchronized statement (#799) 2019-12-15 12:04:24 +00:00
Skylot a2df92dd68 fix(gui): correct app close on menu exit action 2019-12-14 15:14:20 +00:00
Skylot 1c6e51f8b2 fix: allow to regenerate class code (#791) 2019-12-13 18:37:10 +00:00
Soul Trace ef5da49bc0 fix(xml): reset nsMap in parse method (PR #798 #796)
Fixes injection of xmlns: attributes from other files (#796)
2019-12-13 21:16:08 +03:00
Skylot 7545625af4 test: add NYI test for empty finally block (#789) 2019-12-10 22:10:27 +03:00
Soul Trace e3055b95f6 feat(gui): support for renaming methods, classes and fields (PR #794 #791)
* Add getRealFullName() to ClassNode and JavaClass and searchJavaClassByRealName() to JadxWrapper

Those methods is like getFullName() and searchJavaClassByClassName(), but for class names without aliases.
It is necessary for renaming classes/methods/fields.

* core: Make getFieldNode(), getMethodNode() and getRoot() public

This is necessary for renaming functionality

* jadx-gui: Add Rename popup menu entry (renames classes, methods and fields)

It allows user to rename classes, methods and fields.
It updates deobfuscation map and reload file.
This may be suboptimal, and maybe some RenameVisitor should be added.
Deobfuscation should be enabled in order to allow this.
2019-12-10 22:08:27 +03:00
Soul Trace 78eed8629c feat(gui): reopened tabs on file reload (PR #793 #792)
* Add getRealFullName() to ClassNode and JavaClass and searchJavaClassByRealName() to JadxWrapper

Those methods is like getFullName() and searchJavaClassByClassName(), but for class names without aliases.
It is necessary for renaming classes/methods/fields.

* MainWindow: Try to restore open tabs on deobfuscation toggle

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

* fix: use computeIfAbsent instead of synchronized block
2019-01-18 16:47:44 +03:00
Jan S d1af751226 feat(gui): APK signature check v1/v2 using the apksig library from Google (#431)
* feat: APK signature check v1/v2 using the apksig library from Google
* fix: proposed changes implemented
2019-01-18 12:26:22 +03:00
Skylot d8b39c2698 Merge branch 'master' into type-inference-wip 2019-01-16 22:29:39 +03:00
Skylot 618b014b3d fix: rename method wrapped by synthetic only from same class (#430) 2019-01-16 22:27:50 +03:00
Skylot 4e990ae2b0 fix: safe SSA variables replacement in filled new array instruction (#399) 2019-01-16 19:03:47 +03:00
Skylot 41ee57a6f7 Merge branch 'master' into type-inference-wip 2019-01-15 14:18:31 +03:00
Jan S 7c353a6c6f fix(gui): unsynchronized search index creation results in NullPointerException upon performing search (#429) 2019-01-15 13:05:45 +03:00
Jan S 72b2663949 fix: ArrayIndexOutOfBoundsException in string concatenation visitor (#427)
* fix: ArrayIndexOutOfBoundsException in string concatenation visitor
* fix: typo in comment
* fix: StringBuilder chain processing created wrong code
* test: simple JUnit test cases added for testing StringBuilder chain processing (chains that can be and that can't be simplified)
2019-01-12 21:12:28 +03:00
Skylot 727285e3df chore: update dependencies and gradle 2019-01-12 19:07:37 +03:00
Skylot a932c7c569 build: add java 11 to build on travis 2019-01-12 13:06:41 +03:00
Skylot 1272ae2d4d fix(gui): don't skip indexing code lines starting with '}' (#426) 2019-01-10 23:46:59 +03:00
Skylot ddaf0375dc docs: add pyjadx link in readme (#424) 2019-01-07 11:28:03 +03:00
Jan S f60bb6b121 fix: various UI improvements (#419)
* fixed wait time for background jobs
* enable multi-threaded decompiling
* added preference for excluding certain packages from decompiling and indexing
* show message dialog in case classes are not indexed because of low memory
* added heap usage bar for visualizing Java memory usage
2019-01-06 15:46:54 +03:00
Donlon fd3498add6 fix: show method alias in "method not decompiled" messages (#410) 2019-01-06 14:02:37 +03:00
Skylot 43de744c88 fix: don't reject type update for generics 2018-12-26 21:41:36 +03:00
Jan Peter Stotz 1ac2cdfc41 fix: wait time for background jobs too short 2018-12-26 20:21:16 +03:00
Skylot 2dea6f55b5 fix: add more details for variable with type inference error 2018-12-25 18:28:35 +03:00
Skylot 76cf4f053f Merge branch 'master' into type-inference-wip 2018-12-25 18:28:00 +03:00
Skylot eadf046b2c chore: show try/catch processing problems in code comments 2018-12-25 17:29:36 +03:00
Skylot e9591efd7e fix: search exception handler splitter block by offset if jump source unknown (#406) 2018-12-25 17:27:42 +03:00
Skylot fbf750f588 build: update jacoco for build with java 11 2018-12-22 15:10:09 +03:00
Skylot 63c528dba9 build: update shadowJar for build with gradle 5.0 2018-12-22 14:57:06 +03:00
Skylot 0f27eba1b1 fix: don't rename constructors and class init methods in deobfuscator (#415) 2018-12-22 13:03:06 +03:00
Skylot a841d0ebe7 fix: use '$' for inner classes also in methods and fields (#415) 2018-12-22 13:02:27 +03:00
Skylot 6a1717a624 fix: use original call class for invoke inherited methods (#413) 2018-12-21 21:54:26 +03:00
Skylot ee6508e93c fix: use '$' as separator for inner classes in .jobf file (#415) 2018-12-21 20:22:24 +03:00
Skylot 5ad082627f fix(gui): fill background before draw line numbers (#404) 2018-12-21 20:22:22 +03:00
Skylot e0624ce986 fix: use '$' as separator for inner classes in .jobf file (#415) 2018-12-21 19:44:25 +03:00
Skylot 995cf2ad42 fix: use types with generics for overloaded method casts (#402) 2018-12-08 20:54:55 +03:00
Skylot b9fffa149b fix: allow override type with wider one only from debug info (#403) 2018-12-06 17:55:38 +03:00
Skylot 7e8435cceb fix(gui): fill background before draw line numbers (#404) 2018-12-06 14:03:09 +03:00
Skylot 37071dbaf3 fix: use soft checks for objects and arrays in 'if' type listener (#401) 2018-12-06 13:22:34 +03:00
Skylot 87c1231422 fix: show type inference errors as method comments 2018-12-06 13:22:18 +03:00
Skylot d553157bb3 fix: hide debug type inference logs 2018-12-03 12:36:06 +03:00
Skylot 95f9ab035d fix: inline constants in chained move instructions (#399) 2018-12-01 23:37:14 +03:00
Skylot 21e11c1d47 fix: implement new type inference approach 2018-12-01 23:37:04 +03:00
Skylot 6d59f77165 fix: process try/catch without move-exception instruction (#395) 2018-11-26 14:31:49 +03:00
Marcin Kamionowski 3a798cb21c fix: return type lost after type inference (#396) 2018-11-23 20:01:50 +03:00
Skylot 1fc92d2a16 fix: instruction deep equals must check result 2018-11-16 19:04:38 +03:00
Skylot 850bd96976 fix: don't remove synthetic class with inner classes 2018-11-11 21:04:37 +03:00
Skylot 20b03aa755 fix: don't remove synthetic method if args count or name not same (#361) 2018-11-09 19:54:00 +03:00
Jan S 5281eed1a5 fix: loading of i18n resources as UTF-8 (see #363) (PR #386) 2018-11-07 22:57:31 +03:00
Parth Bhatia bedbf94b4a fix: update dx to version 16 (#369) 2018-11-07 20:10:37 +03:00
Skylot 47917fd5c2 fix: resolve some sonar critical issues 2018-10-29 22:27:28 +03:00
Skylot 0abb51c87a fix(gui): on settings reset run upgrade method 2018-10-29 22:27:05 +03:00
Skylot 557667b125 fix(gui): allow partial settings sync to not save command line options 2018-10-29 22:27:05 +03:00
Skylot 1d7bb43dfd fix: correct code line number calculation 2018-10-29 18:43:22 +03:00
Skylot 6b3e8f083c fix(gui): override settings by cmd options 2018-10-29 18:42:17 +03:00
Skylot bc629337d6 fix(gui): add "use imports" option to preferences 2018-10-29 18:35:23 +03:00
Skylot 58993b9799 fix(gui): apply render hints for line numbers 2018-10-28 18:52:43 +03:00
Skylot a3464d7184 fix(gui): make link for full class names (#378) 2018-10-28 18:52:43 +03:00
Jan S a8a31643f1 fix: Fix for #377 (Jadx in Windows open with list) (#379) 2018-10-28 18:51:25 +03:00
Jan S df9ae295db feat: make the import class name clickable (#378) 2018-10-25 16:36:37 +03:00
Skylot 8c348c935c build: disable travis build on oracle jdk 10 2018-10-24 21:45:00 +03:00
Skylot 3815d30fc1 fix: force rename fields and methods with reserved names (#364) 2018-10-24 21:30:36 +03:00
Skylot 778b9bb851 fix: resolve lint errors in resource save methods 2018-10-24 21:30:36 +03:00
Sergey Toshin 57dd9e6146 Removes useless imports which prevented gradle build 2018-10-24 21:21:17 +03:00
sergey-wowwow 8eef4a9075 fix: saves all resources (#375) 2018-10-24 20:58:49 +03:00
sergey-wowwow 87f50ab513 fix: exports resources first (#376) 2018-10-24 20:56:32 +03:00
Skylot 2de86b6db5 fix(gui): make correct size truncate for recent files list 2018-09-08 17:59:38 +03:00
Skylot 9be62fb16e fix: lower regions count limit (#354) 2018-09-08 17:45:04 +03:00
Skylot f6f883b9d1 fix: change resource fields generations in R class (#308) 2018-09-08 14:33:33 +03:00
sergey-wowwow 5de4d0792f fix: generates code of missing R class (#353) 2018-09-08 14:32:59 +03:00
Skylot 8c43e7f7ce style: fix code formating 2018-09-08 10:22:06 +03:00
Skylot 9e24a5abeb fix(gui): show 'copy name' action only for supported nodes 2018-09-08 10:12:40 +03:00
Skylot b587b6d694 fix(gui): use correct font and style for certificate panel 2018-09-08 10:12:40 +03:00
Skylot bc3af8e64d fix: resolve some sonar warnings 2018-09-08 10:12:40 +03:00
Skylot 7bd428cf6d build: fix gitlab config 2018-09-08 10:12:40 +03:00
Skylot 912f3c8467 build: skip gradle assemble before build 2018-09-01 14:27:15 +03:00
javaeryang a8febb2447 feat(gui): add a menu to copy class name (#351) 2018-08-28 11:28:53 +03:00
Skylot 1b0b526822 chore: remove 'v' from version string 2018-08-26 23:15:48 +03:00
Skylot 6250ebdd75 chore: don't use labels for artifacts in github release 2018-08-26 23:14:18 +03:00
1128 changed files with 55018 additions and 16723 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,20 @@
---
name: Decompilation error
about: Create a report to help us improve jadx decompiler
title: "[core]"
labels: Core, bug
assignees: ''
---
**Checks before report**
- check [latest unstable build](https://bintray.com/skylot/jadx/unstable/_latestVersion#files) (maybe issue already fixed)
- check [Troubleshooting Q&A](https://github.com/skylot/jadx/wiki/Troubleshooting-Q&A) section on wiki
- search existing issues by exception message
**Describe error**
- full name of method or class with error
- full java stacktrace (no need to copy method fallback code (commented pseudocode))
- **IMPORTANT!** attach or provide link to apk file (double check apk version)
**Note**: GitHub don't allow attach files with `.apk` extension, but you can change extension by adding `.zip` at the end :)
+10
View File
@@ -0,0 +1,10 @@
---
name: Feature Request
about: Suggest an idea for jadx
title: "[feature]"
labels: new feature
assignees: ''
---
*Describe your idea:*
+5
View File
@@ -0,0 +1,5 @@
:exclamation: Please review the [guidelines for contributing](https://github.com/skylot/jadx/blob/master/CONTRIBUTING.md#Pull-Request-Process)
### Description
Please describe your pull request.
Reference issue it fixes.
@@ -0,0 +1,10 @@
name: "Validate Gradle Wrapper"
on: [push]
jobs:
validation:
name: "Validation"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: gradle/wrapper-validation-action@v1
+6 -1
View File
@@ -5,10 +5,13 @@
# IntelliJ Idea files
.idea/
.run/
out/
*.iml
*.ipr
*.iws
.attach_pid*
*.hprof
**/.DS_Store
@@ -22,9 +25,11 @@ node_modules/
jadx-output/
*-tmp/
**/tmp/
*.jobf
*.dex
*.class
*.dump
*.log
*.cfg
*.orig
+18 -18
View File
@@ -1,24 +1,24 @@
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
build:
stage: build
script:
- sed -i " 1 s/.*/&-glb$(git rev-list --count HEAD)-$(git rev-parse --short HEAD)/" version
- cat version
- ./gradlew -g /cache/.gradle clean build jacocoTestReport
- ./gradlew -g /cache/.gradle clean sonarqube -Dsonar.host.url=$SONAR_HOST -Dsonar.organization=$SONAR_ORG -Dsonar.login=$SONAR_TOKEN
- ./gradlew -g /cache/.gradle clean dist
artifacts:
paths:
- build/jadx*.zip
- build/jadx*.exe
java-8:
stage: test
image: openjdk:8
script: ./gradlew clean build dist
java-11:
stage: test
image: openjdk:11
script: ./gradlew clean build dist
java-latest:
stage: test
image: openjdk:latest
script: java -version && ./gradlew clean build dist
-3
View File
@@ -1,3 +0,0 @@
[submodule "jadx-test-app/test-app"]
path = jadx-test-app/test-app
url = git://github.com/skylot/jadx-test-app.git
+24 -3
View File
@@ -1,16 +1,37 @@
branch: release
branches:
- release
verifyConditions:
- '@semantic-release/github'
prepare:
- path: '@semantic-release/exec'
cmd: "JADX_VERSION=${nextRelease.version} ./gradlew clean dist"
preset: "angular"
generateNotes:
- path: '@semantic-release/release-notes-generator'
writerOpts:
groupBy: "type"
commitGroupsSort:
- "feat"
- "perf"
- "fix"
commitsSort: "header"
publish:
- path: '@semantic-release/exec'
cmd: "JADX_VERSION=${nextRelease.version} BINTRAY_PACKAGE=releases bash scripts/bintray-upload.sh"
- path: '@semantic-release/github'
assets:
- path: 'build/*.zip'
label: 'zip bundle'
- path: 'build/*.exe'
label: 'jadx-gui windows'
success:
- path: '@semantic-release/github'
successComment: false
fail:
- path: '@semantic-release/github'
failComment: false
+27 -31
View File
@@ -1,5 +1,5 @@
language: java
sudo: false
os: linux
dist: trusty
# don't build on tag push
@@ -9,42 +9,38 @@ git:
depth: false
before_install:
- wget https://github.com/sormuras/bach/raw/master/install-jdk.sh
- chmod +x gradlew
# override install to skip 'gradle assemble'
install: true
env:
global:
- TERM=dumb
- JADX_VERSION="$(git describe --abbrev=0 --tags)-b$TRAVIS_BUILD_NUMBER-$(git rev-parse --short HEAD)"
- 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
- openjdk11
script: ./gradlew clean build
jobs:
include:
- env: JDK=oracle-8
jdk: oraclejdk8
- env: JDK=oracle-10
install: . ./install-jdk.sh -F 10 -L BCL
- stage: checks
jdk: openjdk11
if: branch = master AND repo = env(MAIN_REPO) AND type = push
script: bash scripts/travis-checks.sh
script:
- java -version
- ./gradlew clean build
- stage: deploy-unstable
jdk: openjdk8
if: branch = master AND repo = env(MAIN_REPO) AND type = push
script: bash scripts/travis-master.sh
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
language: node_js
jdk: openjdk8
node_js: 11
if: branch = release AND repo = env(MAIN_REPO) AND type = push
script: bash scripts/travis-release.sh
+76
View File
@@ -0,0 +1,76 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at skylot@gmail.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq
+32 -6
View File
@@ -1,7 +1,33 @@
# Contribution
# Contributing
To support this project you can:
- Post thoughts about new features/optimizations that important to you
- Submit bug using one of following patterns:
* Java code examples which decompiles incorrectly
* Error log and link to _public available_ apk file or app page on Google play
Please note we have a [code of conduct](CODE_OF_CONDUCT.md), please follow it in all your interactions with the project.
## Open Issue
1. Before proceed please do:
- check [latest unstable build](https://bintray.com/skylot/jadx/unstable/_latestVersion#files) (maybe issue already fixed)
- check [Troubleshooting Q&A](https://github.com/skylot/jadx/wiki/Troubleshooting-Q&A) section on wiki
- search existing issues by exception message
2. Describe error
**Describe error**
- full name of method or class with error
- full java stacktrace (no need to copy method fallback code (commented pseudocode))
- **IMPORTANT!:** attach or provide link to apk file (double check apk version)
**Note**: GitHub don't allow attach files with `.apk` extension, but you can change extension by adding `.zip` at the end :)
## Pull Request Process
1. Please don't submit any code style fixes, dependencies updates or other changes which are not fixing any issues.
1. Before start working on PR please discuss the change you wish to make via issue. PR without corresponding issue will be rejected.
1. Use only features and API from Java 8 or below.
1. If possible don't add additional dependencies especially if they are big.
1. Make sure your code is correctly formatted, see description here: [Code Formatting](https://github.com/skylot/jadx/wiki/Code-Formatting).
1. Make sure your changes is passing build: `./gradlew clean build dist`
+84 -51
View File
@@ -1,92 +1,125 @@
<img src="https://raw.githubusercontent.com/skylot/jadx/master/jadx-gui/src/main/resources/logos/jadx-logo.png" width="64" align="left" />
## JADX
[![Build Status](https://travis-ci.org/skylot/jadx.png?branch=master)](https://travis-ci.org/skylot/jadx)
[![Build Status](https://travis-ci.com/skylot/jadx.svg?branch=master)](https://travis-ci.com/skylot/jadx)
[![Code Coverage](https://codecov.io/gh/skylot/jadx/branch/master/graph/badge.svg)](https://codecov.io/gh/skylot/jadx)
[![Alerts from lgtm.com](https://img.shields.io/lgtm/alerts/g/skylot/jadx.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/skylot/jadx/alerts/)
[![SonarQube Bugs](https://sonarcloud.io/api/project_badges/measure?project=jadx&metric=bugs)](https://sonarcloud.io/dashboard?id=jadx)
[![License](http://img.shields.io/:license-apache-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0.html)
[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)
**jadx** - Dex to Java decompiler
Command line and GUI tools for produce Java source code from Android Dex and Apk files
Command line and GUI tools for producing Java source code from Android Dex and Apk files
**Main features:**
- decompile Dalvik bytecode to java classes from APK, dex, aar and zip files
- decode `AndroidManifest.xml` and other resources from `resources.arsc`
- deobfuscator included
**jadx-gui features:**
- view decompiled code with highlighted syntax
- jump to declaration
- find usage
- full text search
See these features in action here: [jadx-gui features overview](https://github.com/skylot/jadx/wiki/jadx-gui-features-overview)
![jadx-gui screenshot](https://i.imgur.com/h917IBZ.png)
### Downloads
### Download
- latest [unstable build: ![Download](https://api.bintray.com/packages/skylot/jadx/unstable/images/download.svg) ](https://bintray.com/skylot/jadx/unstable/_latestVersion#files)
- release from [github: ![Latest release](https://img.shields.io/github/release/skylot/jadx.svg)](https://github.com/skylot/jadx/releases/latest)
- release from [bintray: ![Download](https://api.bintray.com/packages/skylot/jadx/releases/images/download.svg) ](https://bintray.com/skylot/jadx/releases/_latestVersion#files)
After download unpack zip file go to `bin` directory and run:
- `jadx` - command line version
- `jadx-gui` - graphical version
- `jadx-gui` - UI version
On Windows run `.bat` files with double-click\
**Note:** ensure you have installed Java 8 64-bit version
**Note:** ensure you have installed Java 8 or later 64-bit version.
For windows you can download it from [adoptopenjdk.net](https://adoptopenjdk.net/releases.html?variant=openjdk11&jvmVariant=hotspot#x64_win) (select "Install JRE").
### Building from source
Java 8 JDK or higher must be installed:
### Install
1. Arch linux
```bash
sudo pacman -S jadx
```
2. macOS
```bash
brew install jadx
```
git clone https://github.com/skylot/jadx.git
cd jadx
./gradlew dist
### Build from source
JDK 8 or higher must be installed:
```
git clone https://github.com/skylot/jadx.git
cd jadx
./gradlew dist
```
(on Windows, use `gradlew.bat` instead of `./gradlew`)
Scripts for run jadx will be placed in `build/jadx/bin`
and also packed to `build/jadx-<version>.zip`
### Run
Run **jadx** on itself:
cd build/jadx/
bin/jadx -d out lib/jadx-core-*.jar
# or
bin/jadx-gui lib/jadx-core-*.jar
### Usage
```
jadx[-gui] [options] <input file> (.dex, .apk, .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
-r, --no-res - do not decode resources
-s, --no-src - do not decompile source code
--single-class - decompile a single class
--output-format - can be 'java' or 'json', default: java
-e, --export-gradle - save as android gradle project
-j, --threads-count - processing threads count, default: 4
--show-bad-code - show inconsistent code (incorrectly decompiled)
--no-imports - disable use of imports, always write entire package name
--no-debug-info - disable debug info
--no-inline-anonymous - disable anonymous classes inline
--no-replace-consts - don't replace constant value with matching constant field
--escape-unicode - escape non latin characters in strings (with \u)
--respect-bytecode-access-modifiers - don't change original access modifiers
--deobf - activate deobfuscation
--deobf-min - min length of name, renamed if shorter, default: 3
--deobf-max - max length of name, renamed if longer, default: 64
--deobf-rewrite-cfg - force to save deobfuscation map
--deobf-use-sourcename - use source file name as class name alias
--rename-flags - what to rename, comma-separated, 'case' for system case sensitivity, 'valid' for java identifiers, 'printable' characters, 'none' or 'all' (default)
--fs-case-sensitive - treat filesystem as case sensitive, false by default
--cfg - save methods control flow graph to dot file
--raw-cfg - save methods control flow graph (use raw instructions)
-f, --fallback - make simple dump (using goto instead of 'if', 'for', etc)
-v, --verbose - verbose output (set --log-level to DEBUG)
-q, --quiet - turn off output (set --log-level to QUIET)
--log-level - set log level, values: QUIET, PROGRESS, ERROR, WARN, INFO, DEBUG, default: PROGRESS
--version - print jadx version
-h, --help - print this help
Example:
jadx -d out classes.dex
jadx --rename-flags "none" classes.dex
jadx --rename-flags "valid,printable" classes.dex
jadx --log-level error app.apk
```
These options also worked on jadx-gui running from command line and override options from preferences dialog
### Troubleshooting
##### Out of memory error:
- Reduce processing threads count (`-j` option)
- Increase maximum java heap size:
* command line (example for linux):
`JAVA_OPTS="-Xmx4G" jadx -j 1 some.apk`
* edit 'jadx' script (jadx.bat on Windows) and setup bigger heap size:
`DEFAULT_JVM_OPTS="-Xmx2500M"`
Please check wiki page [Troubleshooting Q&A](https://github.com/skylot/jadx/wiki/Troubleshooting-Q&A)
### Contributing
To support this project you can:
- Post thoughts about new features/optimizations that important to you
- Submit decompilation issues, please read before proceed: [Open issue](CONTRIBUTING.md#Open-Issue)
- Open pull request, please follow these rules: [Pull Request Process](CONTRIBUTING.md#Pull-Request-Process)
### Related projects:
- [PyJadx](https://github.com/romainthomas/pyjadx) - python binding for jadx by [@romainthomas](https://github.com/romainthomas)
---------------------------------------
*Licensed under the Apache 2.0 License*
*Copyright 2018 by Skylot*
+126 -85
View File
@@ -1,7 +1,7 @@
plugins {
id 'com.github.ksoichiro.console.reporter' version '0.5.0'
id 'org.sonarqube' version '2.6.2'
id 'com.github.ben-manes.versions' version '0.20.0'
id 'org.sonarqube' version '3.0'
id 'com.github.ben-manes.versions' version '0.33.0'
id "com.diffplug.spotless" version "5.5.1"
}
ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev"
@@ -9,121 +9,162 @@ version = jadxVersion
println("jadx version: ${jadxVersion}")
allprojects {
apply plugin: 'java'
apply plugin: 'groovy'
apply plugin: 'jacoco'
apply plugin: 'com.github.ksoichiro.console.reporter'
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
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
if (!"$it".contains(':jadx-samples:')) {
options.compilerArgs << '-Xlint' << '-Xlint:unchecked' << '-Xlint:deprecation'
}
}
tasks.withType(JavaCompile) {
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 {
manifest {
mainAttributes('jadx-version': jadxVersion)
}
}
dependencies {
compile 'org.slf4j:slf4j-api:1.7.25'
dependencies {
implementation 'org.slf4j:slf4j-api:1.7.30'
compileOnly 'org.jetbrains:annotations:20.1.0'
testCompile 'ch.qos.logback:logback-classic:1.2.3'
testCompile 'junit:junit:4.12'
testCompile 'org.hamcrest:hamcrest-library:1.3'
testCompile 'org.mockito:mockito-core:2.20.1'
testCompile 'org.spockframework:spock-core:1.1-groovy-2.4'
testCompile 'cglib:cglib-nodep:3.2.7'
}
testImplementation 'ch.qos.logback:logback-classic:1.2.3'
testImplementation 'org.hamcrest:hamcrest-library:2.2'
testImplementation 'org.mockito:mockito-core:3.5.10'
testImplementation 'org.assertj:assertj-core:3.17.2'
repositories {
mavenLocal()
mavenCentral()
jcenter()
}
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0'
jacoco {
toolVersion = "0.8.1"
}
jacocoTestReport {
reports {
xml.enabled = true
html.enabled = true
}
}
testImplementation 'org.eclipse.jdt.core.compiler:ecj:4.6.1'
testCompileOnly 'org.jetbrains:annotations:20.1.0'
}
test {
useJUnitPlatform()
}
repositories {
mavenLocal()
mavenCentral()
jcenter()
google()
}
jacoco {
toolVersion = "0.8.6"
}
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/**/*'
}
}
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')
}
}
}
spotless {
java {
target fileTree(rootDir).matching {
include 'jadx-cli/src/**/java/**/*.java'
include 'jadx-core/src/**/java/**/*.java'
include 'jadx-gui/src/**/java/**/*.java'
include 'jadx-plugins/**/java/**/*.java'
}
importOrderFile 'config/code-formatter/eclipse.importorder'
eclipse().configFile 'config/code-formatter/eclipse.xml'
removeUnusedImports()
lineEndings(com.diffplug.spotless.LineEnding.UNIX)
encoding("UTF-8")
trimTrailingWhitespace()
endWithNewline()
}
format 'misc', {
target '**/*.gradle', '**/*.md', '**/*.xml', '**/.gitignore', '**/.properties'
targetExclude ".gradle/**", ".idea/**", "*/build/**"
lineEndings(com.diffplug.spotless.LineEnding.UNIX)
encoding("UTF-8")
trimTrailingWhitespace()
endWithNewline()
}
}
dependencyUpdates {
resolutionStrategy {
componentSelection { rules ->
rules.all { ComponentSelection selection ->
boolean rejected = ['alpha', 'beta', 'rc', 'cr', 'm', 'atlassian'].any { qualifier ->
selection.candidate.version ==~ /(?i).*[.-]${qualifier}[.\d-]*/
}
if (rejected) {
selection.reject('Release candidate')
}
}
}
}
}
task copyArtifacts(type: Sync, dependsOn: ['jadx-cli:installDist', 'jadx-gui:installDist']) {
destinationDir file("$buildDir/jadx")
['jadx-cli', 'jadx-gui'].each {
from tasks.getByPath(":${it}:installDist").destinationDir
}
destinationDir file("$buildDir/jadx")
['jadx-cli', 'jadx-gui'].each {
from tasks.getByPath(":${it}:installDist").destinationDir
}
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
task pack(type: Zip, dependsOn: copyArtifacts) {
destinationDir buildDir
archiveName "jadx-${jadxVersion}.zip"
from copyArtifacts.destinationDir
destinationDirectory = buildDir
archiveFileName = "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'
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
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'
}
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)
+126
View File
@@ -0,0 +1,126 @@
<?xml version="1.0" ?>
<!DOCTYPE module PUBLIC
"-//Puppy Crawl//DTD Check Configuration 1.2//EN"
"http://www.puppycrawl.com/dtds/configuration_1_2.dtd">
<module name="Checker">
<property name="fileExtensions" value="java, properties, xml"/>
<property name="charset" value="UTF-8"/>
<module name="TreeWalker">
<property name="tabWidth" value="4"/>
<module name="RegexpSinglelineJava">
<property name="format" value="^\t* "/>
<property name="message" value="Indent must use tab characters"/>
<property name="ignoreComments" value="true"/>
</module>
<module name="RegexpSinglelineJava">
<property name="format" value="^(?!\s+\* $).*?\s+$"/>
<property name="message" value="Line has trailing spaces."/>
</module>
<module name="AvoidEscapedUnicodeCharacters">
<property name="allowEscapesForControlCharacters" value="true"/>
<property name="allowByTailComment" value="true"/>
<property name="allowNonPrintableEscapes" value="true"/>
</module>
<module name="EmptyLineSeparator">
<property name="allowNoEmptyLineBetweenFields" value="true"/>
<property name="allowMultipleEmptyLines" value="false"/>
</module>
<!-- whitespaces -->
<module name="SingleSpaceSeparator"/>
<module name="GenericWhitespace"/>
<module name="MethodParamPad"/>
<module name="NoWhitespaceBefore"/>
<module name="OperatorWrap"/>
<module name="ParenPad"/>
<module name="TypecastParenPad"/>
<module name="WhitespaceAfter"/>
<module name="WhitespaceAround">
<property name="allowEmptyMethods" value="true"/>
</module>
<!-- <module name="EmptyForIteratorPad"/> -->
<!-- <module name="NoWhitespaceAfter"/>-->
<module name="NoLineWrap"/>
<module name="IllegalImport"/> <!-- defaults to sun.* packages -->
<module name="RedundantImport"/>
<module name="UnusedImports"/>
<!-- <module name="AvoidStarImport"/> -->
<module name="NeedBraces"/>
<module name="LeftCurly"/>
<module name="RightCurly"/>
<module name="EmptyCatchBlock">
<property name="exceptionVariableName" value="expected|ignore"/>
</module>
<!-- naming -->
<module name="PackageName"/>
<module name="TypeName"/>
<module name="InterfaceTypeParameterName"/>
<module name="ClassTypeParameterName"/>
<module name="StaticVariableName"/>
<module name="ConstantName"/>
<module name="MemberName"/>
<module name="MethodName"/>
<module name="MethodTypeParameterName"/>
<module name="ParameterName"/>
<module name="LambdaParameterName"/>
<module name="LocalVariableName"/>
<module name="LocalFinalVariableName"/>
<module name="CatchParameterName"/>
<!-- <module name="HiddenField"/> -->
<!-- annotations -->
<module name="AnnotationLocation"/>
<module name="AnnotationUseStyle">
<property name="elementStyle" value="compact"/>
</module>
<module name="MissingOverride"/>
<!-- <module name="MissingDeprecated"/> -->
<module name="ModifierOrder"/>
<!-- <module name="RedundantModifier"/> -->
<!-- <module name="ParameterNumber"/> -->
<module name="EmptyStatement"/>
<module name="DefaultComesLast"/>
<module name="EqualsHashCode"/>
<module name="FallThrough"/>
<!-- <module name="IllegalCatch"/> -->
<module name="IllegalThrows"/>
<module name="IllegalType"/>
<module name="InnerAssignment"/>
<module name="MultipleVariableDeclarations"/>
<module name="NoClone"/>
<module name="NoFinalizer"/>
<module name="OneStatementPerLine"/>
<module name="PackageDeclaration"/>
<module name="StringLiteralEquality"/>
<!-- design -->
<module name="OneTopLevelClass"/>
<module name="MutableException"/>
<module name="InterfaceIsType"/>
<module name="ThrowsCount">
<property name="max" value="2"/>
</module>
<!-- misc -->
<module name="ArrayTypeStyle"/>
<module name="OuterTypeFilename"/>
<!-- sizes -->
<module name="OuterTypeNumber"/>
<module name="SuppressWarningsHolder"/>
</module>
<module name="NewlineAtEndOfFile"/>
<module name="SuppressWarningsFilter"/>
</module>
@@ -0,0 +1,8 @@
#Import Order
0=java
1=javax
2=org
3=com
4=
5=jadx
6=\#
+348
View File
@@ -0,0 +1,348 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<profiles version="16">
<profile kind="CodeFormatterProfile" name="jadx eclipse" version="16">
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_ellipsis" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_for_statment" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_logical_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_method_invocation" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_imports" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_switch_statement" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_javadoc_comments" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.indentation.size" value="4"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_enum_constant_declaration" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.align_with_spaces" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.disabling_tag" value="@formatter:off"/>
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation" value="2"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_enum_constants" value="48"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_imports" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_package" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_if_while_statement" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.indent_root_tags" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.enabling_tag" value="@formatter:on"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.count_line_length_from_starting_position" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_multiplicative_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameterized_type_references" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_logical_operator" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_annotation_declaration_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_enum_constant" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_multiplicative_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_block" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.align_tags_descriptions_grouped" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.comment.line_length" value="100"/>
<setting id="org.eclipse.jdt.core.formatter.use_on_off_tags" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.keep_method_body_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_loop_body_block_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_method_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.keep_enum_constant_declaration_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.align_variable_declarations_on_columns" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_type_declaration_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_catch_clause" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_additive_operator" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_relational_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_multiplicative_operator" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.keep_anonymous_type_declaration_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_shift_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_lambda_body" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.compact_else_if" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_bitwise_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_type_parameters" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_loops" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_simple_for_body_on_same_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_relational_operator" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_unary_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation" value="separate_lines_if_wrapped"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_ellipsis" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_additive_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_string_concatenation" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_line_comments" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.align_type_members_on_columns" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_assignment" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_module_statements" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.align_tags_names_descriptions" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.keep_if_then_body_block_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression" value="48"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.align_assignment_statements_on_columns" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block_in_case" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression_chain" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_additive_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_method_declaration" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.join_wrapped_lines" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_conditional_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_shift_operator" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines" value="2147483647"/>
<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_bitwise_operator" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_resources_in_try" value="80"/>
<setting id="org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_try_clause" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.keep_code_block_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.tabulation.size" value="4"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_bitwise_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_source_code" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_field" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer" value="2"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_method" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_assignment_operator" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_switch" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_html" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_method_delcaration" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_if" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.keep_lambda_body_block_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.indent_empty_lines" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_type_arguments" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_unary_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_label" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_member_type" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_logical_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_bitwise_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_relational_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_block_comments" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.indent_tag_description" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_string_concatenation" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_body" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_multiple_fields" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_simple_while_body_on_same_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_array_initializer" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_logical_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_shift_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_lambda_declaration" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_shift_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_simple_do_while_body_on_same_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_enum_declaration_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_constant" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_type_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_multiplicative_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_package" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_for_loop_header" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_additive_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.keep_simple_getter_setter_on_one_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_string_concatenation" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.join_lines_in_comments" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.indent_parameter_description" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.tabulation.char" value="tab"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_relational_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_string_concatenation" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_import_groups" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.lineSplit" value="140"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch" value="insert"/>
</profile>
</profiles>
Binary file not shown.
+1 -1
View File
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored
+33 -20
View File
@@ -1,5 +1,21 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
@@ -28,7 +44,7 @@ APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
@@ -66,6 +82,7 @@ esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
@@ -109,10 +126,11 @@ if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
@@ -138,19 +156,19 @@ if $cygwin ; then
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
i=`expr $i + 1`
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
@@ -159,14 +177,9 @@ save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"
Vendored
+24 -19
View File
@@ -1,3 +1,19 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@@ -13,15 +29,18 @@ if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@@ -35,7 +54,7 @@ goto fail
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
@@ -45,28 +64,14 @@ echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
+23 -17
View File
@@ -1,23 +1,29 @@
apply plugin: 'application'
mainClassName = 'jadx.cli.JadxCLI'
applicationName = 'jadx'
plugins {
id 'application'
}
dependencies {
compile(project(':jadx-core'))
compile 'com.beust:jcommander:1.74'
compile 'ch.qos.logback:logback-classic:1.2.3'
implementation(project(':jadx-core'))
runtimeOnly(project(':jadx-plugins:jadx-dex-input'))
runtimeOnly(project(':jadx-plugins:jadx-smali-input'))
runtimeOnly(project(':jadx-plugins:jadx-java-convert'))
implementation 'com.beust:jcommander:1.80'
implementation 'ch.qos.logback:logback-classic:1.2.3'
}
application {
applicationName = 'jadx'
mainClassName = 'jadx.cli.JadxCLI'
applicationDefaultJvmArgs = ['-Xms128M', '-Xmx4g', '-XX:+UseG1GC']
}
applicationDistribution.with {
into('') {
from '../.'
include 'README.md'
include 'NOTICE'
include 'LICENSE'
}
}
startScripts {
defaultJvmOpts = ['-Xms128M', '-Xmx4g']
into('') {
from '../.'
include 'README.md'
include 'NOTICE'
include 'LICENSE'
}
}
@@ -0,0 +1,137 @@
package jadx.cli;
import java.io.PrintStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.jetbrains.annotations.Nullable;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.ParameterDescription;
import com.beust.jcommander.ParameterException;
import com.beust.jcommander.Parameterized;
import jadx.api.JadxDecompiler;
public class JCommanderWrapper<T> {
private final JCommander jc;
public JCommanderWrapper(T obj) {
this.jc = JCommander.newBuilder().addObject(obj).build();
}
public boolean parse(String[] args) {
try {
jc.parse(args);
return true;
} catch (ParameterException e) {
System.err.println("Arguments parse error: " + e.getMessage());
printUsage();
return false;
}
}
public void overrideProvided(T obj) {
List<ParameterDescription> fieldsParams = jc.getParameters();
List<ParameterDescription> parameters = new ArrayList<>(1 + fieldsParams.size());
parameters.add(jc.getMainParameterValue());
parameters.addAll(fieldsParams);
for (ParameterDescription parameter : parameters) {
if (parameter.isAssigned()) {
// copy assigned field value to obj
Parameterized parameterized = parameter.getParameterized();
Object val = parameterized.get(parameter.getObject());
parameterized.set(obj, val);
}
}
}
public void printUsage() {
// print usage in not sorted fields order (by default its sorted by description)
PrintStream out = System.out;
out.println();
out.println("jadx - dex to java decompiler, version: " + JadxDecompiler.getVersion());
out.println();
out.println("usage: jadx [options] " + jc.getMainParameterDescription());
out.println("options:");
List<ParameterDescription> params = jc.getParameters();
Map<String, ParameterDescription> paramsMap = new LinkedHashMap<>(params.size());
int maxNamesLen = 0;
for (ParameterDescription p : params) {
paramsMap.put(p.getParameterized().getName(), p);
int len = p.getNames().length();
if (len > maxNamesLen) {
maxNamesLen = len;
}
}
JadxCLIArgs args = (JadxCLIArgs) jc.getObjects().get(0);
for (Field f : getFields(args.getClass())) {
String name = f.getName();
ParameterDescription p = paramsMap.get(name);
if (p == null) {
continue;
}
StringBuilder opt = new StringBuilder();
opt.append(" ").append(p.getNames());
addSpaces(opt, maxNamesLen - opt.length() + 3);
opt.append("- ").append(p.getDescription());
String defaultValue = getDefaultValue(args, f, opt);
if (defaultValue != null) {
opt.append(", default: ").append(defaultValue);
}
out.println(opt);
}
out.println("Example:");
out.println(" jadx -d out classes.dex");
}
/**
* Get all declared fields of the specified class and all super classes
*
* @param clazz
* @return
*/
private List<Field> getFields(Class<?> clazz) {
List<Field> fieldList = new LinkedList<>();
while (clazz != null) {
fieldList.addAll(Arrays.asList(clazz.getDeclaredFields()));
clazz = clazz.getSuperclass();
}
return fieldList;
}
@Nullable
private String getDefaultValue(JadxCLIArgs args, Field f, StringBuilder opt) {
try {
Class<?> fieldType = f.getType();
if (fieldType == int.class) {
return Integer.toString(f.getInt(args));
}
if (fieldType == String.class) {
return (String) f.get(args);
}
if (Enum.class.isAssignableFrom(fieldType)) {
Enum<?> val = (Enum<?>) f.get(args);
if (val != null) {
return val.name();
}
}
} catch (Exception e) {
// ignore
}
return null;
}
private static void addSpaces(StringBuilder str, int count) {
for (int i = 0; i < count; i++) {
str.append(' ');
}
}
}
+28 -19
View File
@@ -3,8 +3,11 @@ package jadx.cli;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs;
import jadx.api.JadxDecompiler;
import jadx.api.impl.NoOpCodeCache;
import jadx.core.utils.exceptions.JadxArgsValidateException;
import jadx.core.utils.files.FileUtils;
public class JadxCLI {
private static final Logger LOG = LoggerFactory.getLogger(JadxCLI.class);
@@ -12,34 +15,40 @@ public class JadxCLI {
public static void main(String[] args) {
int result = 0;
try {
JadxCLIArgs jadxArgs = new JadxCLIArgs();
if (jadxArgs.processArgs(args)) {
result = processAndSave(jadxArgs);
}
result = execute(args);
} catch (JadxArgsValidateException e) {
LOG.error("Incorrect arguments: {}", e.getMessage());
result = 1;
} catch (Exception e) {
LOG.error("jadx error: {}", e.getMessage(), e);
result = 1;
} finally {
FileUtils.deleteTempRootDir();
System.exit(result);
}
}
static int processAndSave(JadxCLIArgs inputArgs) {
JadxDecompiler jadx = new JadxDecompiler(inputArgs.toJadxArgs());
try {
public static int execute(String[] args) {
JadxCLIArgs jadxArgs = new JadxCLIArgs();
if (jadxArgs.processArgs(args)) {
return processAndSave(jadxArgs.toJadxArgs());
}
return 0;
}
private static int processAndSave(JadxArgs jadxArgs) {
jadxArgs.setCodeCache(new NoOpCodeCache());
try (JadxDecompiler jadx = new JadxDecompiler(jadxArgs)) {
jadx.load();
} catch (JadxArgsValidateException e) {
LOG.error("Incorrect arguments: {}", e.getMessage());
return 1;
jadx.save();
int errorsCount = jadx.getErrorsCount();
if (errorsCount != 0) {
jadx.printErrorsReport();
LOG.error("finished with errors, count: {}", errorsCount);
} else {
LOG.info("done");
}
}
jadx.save();
int errorsCount = jadx.getErrorsCount();
if (errorsCount != 0) {
jadx.printErrorsReport();
LOG.error("finished with errors");
} else {
LOG.info("done");
}
return errorsCount;
return 0;
}
}
+165 -115
View File
@@ -1,120 +1,160 @@
package jadx.cli;
import java.io.PrintStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Locale;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.IStringConverter;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.ParameterDescription;
import com.beust.jcommander.ParameterException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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 files> (.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 = {"--deobf"}, description = "activate deobfuscation")
@Parameter(names = { "--respect-bytecode-access-modifiers" }, description = "don't change original access modifiers")
protected boolean respectBytecodeAccessModifiers = false;
@Parameter(names = { "--deobf" }, description = "activate deobfuscation")
protected boolean deobfuscationOn = false;
@Parameter(names = {"--deobf-min"}, description = "min length of name, renamed if shorter")
@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 = { "--deobf-parse-kotlin-metadata" }, description = "parse kotlin metadata to class and package names")
protected boolean deobfuscationParseKotlinMetadata = false;
@Parameter(
names = { "--rename-flags" },
description = "what to rename, comma-separated,"
+ " 'case' for system case sensitivity,"
+ " 'valid' for java identifiers,"
+ " 'printable' characters,"
+ " 'none' or 'all' (default)",
converter = RenameConverter.class
)
protected Set<RenameEnum> renameFlags = EnumSet.allOf(RenameEnum.class);
@Parameter(names = { "--fs-case-sensitive" }, description = "treat filesystem as case sensitive, false by default")
protected boolean fsCaseSensitive = false;
@Parameter(names = { "--cfg" }, description = "save methods control flow graph to dot file")
protected boolean cfgOutput = false;
@Parameter(names = {"--raw-cfg"}, description = "save methods control flow graph (use raw instructions)")
@Parameter(names = { "--raw-cfg" }, description = "save methods control flow graph (use raw instructions)")
protected boolean rawCfgOutput = false;
@Parameter(names = {"-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 (set --log-level to DEBUG)")
protected boolean verbose = false;
@Parameter(names = {"--version"}, description = "print jadx version")
@Parameter(names = { "-q", "--quiet" }, description = "turn off output (set --log-level to QUIET)")
protected boolean quiet = false;
@Parameter(
names = { "--log-level" },
description = "set log level, values: QUIET, PROGRESS, ERROR, WARN, INFO, DEBUG",
converter = LogHelper.LogLevelConverter.class
)
protected LogHelper.LogLevelEnum logLevel = LogHelper.LogLevelEnum.PROGRESS;
@Parameter(names = { "--version" }, description = "print jadx version")
protected boolean printVersion = false;
@Parameter(names = {"-h", "--help"}, description = "print this help", help = true)
@Parameter(names = { "-h", "--help" }, description = "print this help", help = true)
protected boolean printHelp = false;
public boolean processArgs(String[] args) {
return parse(args) && process();
JCommanderWrapper<JadxCLIArgs> jcw = new JCommanderWrapper<>(this);
return jcw.parse(args) && process(jcw);
}
private boolean parse(String[] args) {
try {
makeJCommander().parse(args);
return true;
} catch (ParameterException e) {
System.err.println("Arguments parse error: " + e.getMessage());
printUsage();
/**
* Set values only for options provided in cmd.
* Used to merge saved options and options passed in command line.
*/
public boolean overrideProvided(String[] args) {
JCommanderWrapper<JadxCLIArgs> jcw = new JCommanderWrapper<>(newInstance());
if (!jcw.parse(args)) {
return false;
}
jcw.overrideProvided(this);
return process(jcw);
}
private JCommander makeJCommander() {
return JCommander.newBuilder().addObject(this).build();
protected JadxCLIArgs newInstance() {
return new JadxCLIArgs();
}
private boolean process() {
private boolean process(JCommanderWrapper<JadxCLIArgs> jcw) {
if (printHelp) {
printUsage();
jcw.printUsage();
return false;
}
if (printVersion) {
@@ -125,88 +165,27 @@ public class JadxCLIArgs {
if (threadsCount <= 0) {
throw new JadxException("Threads count must be positive, got: " + threadsCount);
}
if (verbose) {
ch.qos.logback.classic.Logger rootLogger =
(ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
// remove INFO ThresholdFilter
Appender<ILoggingEvent> appender = rootLogger.getAppender("STDOUT");
if (appender != null) {
appender.clearAllFilters();
}
}
LogHelper.setLogLevelFromArgs(this);
} catch (JadxException e) {
System.err.println("ERROR: " + e.getMessage());
printUsage();
jcw.printUsage();
return false;
}
return true;
}
public void printUsage() {
JCommander jc = makeJCommander();
// print usage in not sorted fields order (by default its sorted by description)
PrintStream out = System.out;
out.println();
out.println("jadx - dex to java decompiler, version: " + JadxDecompiler.getVersion());
out.println();
out.println("usage: jadx [options] " + jc.getMainParameterDescription());
out.println("options:");
List<ParameterDescription> params = jc.getParameters();
Map<String, ParameterDescription> paramsMap = new LinkedHashMap<>(params.size());
int maxNamesLen = 0;
for (ParameterDescription p : params) {
paramsMap.put(p.getParameterized().getName(), p);
int len = p.getNames().length();
if (len > maxNamesLen) {
maxNamesLen = len;
}
}
JadxCLIArgs args = new JadxCLIArgs();
Field[] fields = args.getClass().getDeclaredFields();
for (Field f : fields) {
String name = f.getName();
ParameterDescription p = paramsMap.get(name);
if (p == null) {
continue;
}
StringBuilder opt = new StringBuilder();
opt.append(" ").append(p.getNames());
addSpaces(opt, maxNamesLen - opt.length() + 3);
opt.append("- ").append(p.getDescription());
addDefaultValue(args, f, opt);
out.println(opt);
}
out.println("Example:");
out.println(" jadx -d out classes.dex");
}
private void addDefaultValue(JadxCLIArgs args, Field f, StringBuilder opt) {
Class<?> fieldType = f.getType();
if (fieldType == int.class) {
try {
int val = f.getInt(args);
opt.append(" (default: ").append(val).append(")");
} catch (Exception e) {
// ignore
}
}
}
private static void addSpaces(StringBuilder str, int count) {
for (int i = 0; i < count; i++) {
str.append(' ');
}
}
public JadxArgs toJadxArgs() {
JadxArgs args = new JadxArgs();
args.setInputFiles(files.stream().map(FileUtils::toFile).collect(Collectors.toList()));
args.setOutDir(FileUtils.toFile(outDir));
args.setOutDirSrc(FileUtils.toFile(outDirSrc));
args.setOutDirRes(FileUtils.toFile(outDirRes));
args.setOutputFormat(JadxArgs.OutputFormatEnum.valueOf(outputFormat.toUpperCase()));
args.setThreadsCount(threadsCount);
args.setSkipSources(skipSources);
if (singleClass != null) {
args.setClassFilter(className -> singleClass.equals(className));
}
args.setSkipResources(skipResources);
args.setFallbackMode(fallbackMode);
args.setShowInconsistentCode(showInconsistentCode);
@@ -218,9 +197,17 @@ public class JadxCLIArgs {
args.setDeobfuscationMinLength(deobfuscationMinLength);
args.setDeobfuscationMaxLength(deobfuscationMaxLength);
args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias);
args.setParseKotlinMetadata(deobfuscationParseKotlinMetadata);
args.setEscapeUnicode(escapeUnicode);
args.setRespectBytecodeAccModifiers(respectBytecodeAccessModifiers);
args.setExportAsGradleProject(exportAsGradleProject);
args.setUseImports(useImports);
args.setDebugInfo(debugInfo);
args.setInlineAnonymousClasses(inlineAnonymousClasses);
args.setRenameCaseSensitive(isRenameCaseSensitive());
args.setRenameValid(isRenameValid());
args.setRenamePrintable(isRenamePrintable());
args.setFsCaseSensitive(fsCaseSensitive);
return args;
}
@@ -264,6 +251,14 @@ public class JadxCLIArgs {
return useImports;
}
public boolean isDebugInfo() {
return debugInfo;
}
public boolean isInlineAnonymousClasses() {
return inlineAnonymousClasses;
}
public boolean isDeobfuscationOn() {
return deobfuscationOn;
}
@@ -284,8 +279,8 @@ public class JadxCLIArgs {
return deobfuscationUseSourceNameAsAlias;
}
public boolean escapeUnicode() {
return escapeUnicode;
public boolean isDeobfuscationParseKotlinMetadata() {
return deobfuscationParseKotlinMetadata;
}
public boolean isEscapeUnicode() {
@@ -304,7 +299,62 @@ public class JadxCLIArgs {
return replaceConsts;
}
public boolean isRespectBytecodeAccessModifiers() {
return respectBytecodeAccessModifiers;
}
public boolean isExportAsGradleProject() {
return exportAsGradleProject;
}
public boolean isRenameCaseSensitive() {
return renameFlags.contains(RenameEnum.CASE);
}
public boolean isRenameValid() {
return renameFlags.contains(RenameEnum.VALID);
}
public boolean isRenamePrintable() {
return renameFlags.contains(RenameEnum.PRINTABLE);
}
public boolean isFsCaseSensitive() {
return fsCaseSensitive;
}
static class RenameConverter implements IStringConverter<Set<RenameEnum>> {
private final String paramName;
RenameConverter(String paramName) {
this.paramName = paramName;
}
@Override
public Set<RenameEnum> convert(String value) {
if (value.equalsIgnoreCase("NONE")) {
return EnumSet.noneOf(RenameEnum.class);
}
if (value.equalsIgnoreCase("ALL")) {
return EnumSet.allOf(RenameEnum.class);
}
Set<RenameEnum> set = EnumSet.noneOf(RenameEnum.class);
for (String s : value.split(",")) {
try {
set.add(RenameEnum.valueOf(s.toUpperCase(Locale.ROOT)));
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException(
'\'' + s + "' is unknown for parameter " + paramName
+ ", possible values are " + enumValuesString(RenameEnum.values()));
}
}
return set;
}
}
public static String enumValuesString(Enum<?>[] values) {
return Stream.of(values)
.map(v -> '\'' + v.name().toLowerCase(Locale.ROOT) + '\'')
.collect(Collectors.joining(", "));
}
}
@@ -0,0 +1,93 @@
package jadx.cli;
import org.slf4j.LoggerFactory;
import com.beust.jcommander.IStringConverter;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import jadx.api.JadxDecompiler;
public class LogHelper {
private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(LogHelper.class);
public enum LogLevelEnum {
QUIET(Level.OFF),
PROGRESS(Level.OFF),
ERROR(Level.ERROR),
WARN(Level.WARN),
INFO(Level.INFO),
DEBUG(Level.DEBUG);
private final Level level;
LogLevelEnum(Level level) {
this.level = level;
}
public Level getLevel() {
return level;
}
}
public static void setLogLevelFromArgs(JadxCLIArgs args) {
if (isCustomLogConfig()) {
return;
}
LogLevelEnum logLevel = args.logLevel;
if (args.quiet) {
logLevel = LogLevelEnum.QUIET;
} else if (args.verbose) {
logLevel = LogLevelEnum.DEBUG;
}
applyLogLevel(logLevel);
}
public static void applyLogLevel(LogLevelEnum logLevel) {
Logger rootLogger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
rootLogger.setLevel(logLevel.getLevel());
if (logLevel != LogLevelEnum.QUIET) {
// show progress for all levels except quiet
setLevelForClass(JadxCLI.class, Level.INFO);
setLevelForClass(JadxDecompiler.class, Level.INFO);
}
}
private static void setLevelForClass(Class<?> cls, Level level) {
((Logger) LoggerFactory.getLogger(cls)).setLevel(level);
}
/**
* Try to detect if user provide custom logback config via -Dlogback.configurationFile=
*/
private static boolean isCustomLogConfig() {
try {
String logbackConfig = System.getProperty("logback.configurationFile");
if (logbackConfig == null) {
return false;
}
LOG.debug("Use custom log config: {}", logbackConfig);
return true;
} catch (Exception e) {
LOG.error("Failed to detect custom log config", e);
}
return false;
}
public static class LogLevelConverter implements IStringConverter<LogLevelEnum> {
@Override
public LogLevelEnum convert(String value) {
try {
return LogLevelEnum.valueOf(value.toUpperCase());
} catch (Exception e) {
throw new IllegalArgumentException(
'\'' + value + "' is unknown log level, possible values are "
+ JadxCLIArgs.enumValuesString(LogLevelEnum.values()));
}
}
}
}
@@ -0,0 +1,58 @@
package jadx.cli.clst;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs;
import jadx.api.plugins.JadxPluginManager;
import jadx.api.plugins.input.JadxInputPlugin;
import jadx.api.plugins.input.data.ILoadResult;
import jadx.core.clsp.ClsSet;
import jadx.core.dex.nodes.RootNode;
/**
* Utility class for convert dex or jar to jadx classes set (.jcst)
*/
public class ConvertToClsSet {
private static final Logger LOG = LoggerFactory.getLogger(ConvertToClsSet.class);
public static void usage() {
LOG.info("<output .jcst or .jar file> <several input dex or jar files> ");
}
public static void main(String[] args) throws IOException {
if (args.length < 2) {
usage();
System.exit(1);
}
List<Path> inputPaths = Stream.of(args).map(Paths::get).collect(Collectors.toList());
Path output = inputPaths.remove(0);
JadxPluginManager pluginManager = new JadxPluginManager();
List<ILoadResult> loadedInputs = new ArrayList<>();
for (JadxInputPlugin inputPlugin : pluginManager.getInputPlugins()) {
loadedInputs.add(inputPlugin.loadFiles(inputPaths));
}
JadxArgs jadxArgs = new JadxArgs();
jadxArgs.setRenameFlags(EnumSet.noneOf(JadxArgs.RenameEnum.class));
RootNode root = new RootNode(jadxArgs);
root.loadClasses(loadedInputs);
ClsSet set = new ClsSet(root);
set.loadFrom(root);
set.save(output);
LOG.info("Output: {}, file size: {}B", output, output.toFile().length());
LOG.info("done");
}
}
+8 -13
View File
@@ -1,16 +1,11 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%-5level - %msg%n</pattern>
</encoder>
</appender>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<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="INFO">
<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 {
@@ -30,11 +30,38 @@ public class JadxCLIArgsTest {
assertThat(parse("").isSkipSources(), is(false));
}
@Test
public void testOptionsOverride() {
assertThat(override(new JadxCLIArgs(), "--no-imports").isUseImports(), is(false));
assertThat(override(new JadxCLIArgs(), "--no-debug-info").isDebugInfo(), is(false));
assertThat(override(new JadxCLIArgs(), "").isUseImports(), is(true));
JadxCLIArgs args = new JadxCLIArgs();
args.useImports = false;
assertThat(override(args, "--no-imports").isUseImports(), is(false));
args.debugInfo = false;
assertThat(override(args, "--no-debug-info").isDebugInfo(), is(false));
args = new JadxCLIArgs();
args.useImports = false;
assertThat(override(args, "").isUseImports(), is(false));
}
private JadxCLIArgs parse(String... args) {
JadxCLIArgs jadxArgs = new JadxCLIArgs();
return parse(new JadxCLIArgs(), args);
}
private JadxCLIArgs parse(JadxCLIArgs jadxArgs, String... args) {
boolean res = jadxArgs.processArgs(args);
assertThat(res, is(true));
LOG.info("Jadx args: {}", jadxArgs.toJadxArgs());
return jadxArgs;
}
private JadxCLIArgs override(JadxCLIArgs jadxArgs, String... args) {
boolean res = jadxArgs.overrideProvided(args);
assertThat(res, is(true));
LOG.info("Jadx args: {}", jadxArgs.toJadxArgs());
return jadxArgs;
}
}
@@ -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());
}
}
@@ -0,0 +1,94 @@
package jadx.cli;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.utils.files.FileUtils;
import static org.assertj.core.api.Assertions.assertThat;
public class TestInput {
private static final Logger LOG = LoggerFactory.getLogger(TestInput.class);
@Test
public void testDexInput() throws Exception {
decompile("dex", "samples/hello.dex");
}
@Test
public void testSmaliInput() throws Exception {
decompile("smali", "samples/HelloWorld.smali");
}
@Test
public void testClassInput() throws Exception {
decompile("class", "samples/HelloWorld.class");
}
@Test
public void testMultipleInput() throws Exception {
decompile("multi", "samples/hello.dex", "samples/HelloWorld.smali");
}
private void decompile(String tmpDirName, String... inputSamples) throws URISyntaxException, IOException {
StringBuilder args = new StringBuilder();
Path tempDir = FileUtils.createTempDir(tmpDirName);
args.append("-v");
args.append(" -d ").append(tempDir.toAbsolutePath());
for (String inputSample : inputSamples) {
URL resource = getClass().getClassLoader().getResource(inputSample);
assertThat(resource).isNotNull();
String sampleFile = resource.toURI().getRawPath();
args.append(' ').append(sampleFile);
}
int result = JadxCLI.execute(args.toString().split(" "));
assertThat(result).isEqualTo(0);
List<Path> resultJavaFiles = collectJavaFilesInDir(tempDir);
assertThat(resultJavaFiles).isNotEmpty();
// do not copy input files as resources
PathMatcher logAllFiles = path -> {
LOG.debug("File in result dir: {}", path);
return true;
};
for (Path path : collectFilesInDir(tempDir, logAllFiles)) {
for (String inputSample : inputSamples) {
assertThat(path.toAbsolutePath().toString()).doesNotContain(inputSample);
}
}
}
private static List<Path> collectJavaFilesInDir(Path dir) throws IOException {
PathMatcher javaMatcher = dir.getFileSystem().getPathMatcher("glob:**.java");
return collectFilesInDir(dir, javaMatcher);
}
private static List<Path> collectFilesInDir(Path dir, PathMatcher matcher) throws IOException {
try (Stream<Path> pathStream = Files.walk(dir)) {
return pathStream
.filter(p -> Files.isRegularFile(p, LinkOption.NOFOLLOW_LINKS))
.filter(matcher::matches)
.collect(Collectors.toList());
}
}
@AfterAll
public static void cleanup() {
FileUtils.clearTempRootDir();
}
}
Binary file not shown.
@@ -0,0 +1,26 @@
.class Lsmali/HelloWorld;
.super Ljava/lang/Object;
.source "HelloWorld.java"
.method constructor <init>()V
.registers 1
.line 1
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
return-void
.end method
.method public static main([Ljava/lang/String;)V
.registers 2
.line 3
sget-object p0, Ljava/lang/System;->out:Ljava/io/PrintStream;
const-string v0, "Hello, World"
invoke-virtual {p0, v0}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
.line 4
return-void
.end method
Binary file not shown.
+20 -15
View File
@@ -1,17 +1,22 @@
ext.jadxClasspath = 'clsp-data/android-5.1.jar'
dependencies {
runtime files(jadxClasspath)
compile files('lib/dx-1.14.jar')
compile 'commons-io:commons-io:2.6'
compile 'org.ow2.asm:asm:6.2'
compile 'org.jetbrains:annotations:16.0.2'
compile 'uk.com.robust-it:cloning:1.9.10'
testCompile 'org.smali:smali:2.2.4'
testCompile 'org.smali:baksmali:2.2.4'
testCompile 'org.apache.commons:commons-lang3:3.7'
plugins {
id 'java-library'
}
dependencies {
runtimeOnly files('clsp-data/android-29-clst.jar')
runtimeOnly files('clsp-data/android-29-res.jar')
api(project(':jadx-plugins:jadx-plugins-api'))
implementation 'com.google.code.gson:gson:2.8.6'
testImplementation 'org.apache.commons:commons-lang3:3.11'
testRuntimeOnly(project(':jadx-plugins:jadx-dex-input'))
testRuntimeOnly(project(':jadx-plugins:jadx-smali-input'))
testRuntimeOnly(project(':jadx-plugins:jadx-java-convert'))
}
test {
exclude '**/tmp/*'
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
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();
}
}
@@ -0,0 +1,13 @@
package jadx.api;
import org.jetbrains.annotations.Nullable;
public interface ICodeCache {
void add(String clsFullName, ICodeInfo codeInfo);
void remove(String clsFullName);
@Nullable
ICodeInfo get(String clsFullName);
}
@@ -0,0 +1,16 @@
package jadx.api;
import java.util.Map;
import jadx.api.impl.SimpleCodeInfo;
public interface ICodeInfo {
ICodeInfo EMPTY = new SimpleCodeInfo("");
String getCodeStr();
Map<Integer, Integer> getLineMapping();
Map<CodePosition, Object> getAnnotations();
}
+182 -35
View File
@@ -2,7 +2,13 @@ package jadx.api;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import jadx.api.impl.InMemoryCodeCache;
public class JadxArgs {
@@ -18,6 +24,8 @@ public class JadxArgs {
private File outDirSrc;
private File outDirRes;
private ICodeCache codeCache = new InMemoryCodeCache();
private int threadsCount = DEFAULT_THREADS_COUNT;
private boolean cfgOutput = false;
@@ -27,21 +35,44 @@ 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 boolean parseKotlinMetadata = false;
private int deobfuscationMinLength = 0;
private int deobfuscationMaxLength = Integer.MAX_VALUE;
private boolean escapeUnicode = false;
private boolean replaceConsts = true;
private boolean respectBytecodeAccModifiers = false;
private boolean exportAsGradleProject = false;
private boolean fsCaseSensitive;
public enum RenameEnum {
CASE, VALID, PRINTABLE
}
private Set<RenameEnum> renameFlags = EnumSet.allOf(RenameEnum.class);
public enum OutputFormatEnum {
JAVA, JSON
}
private OutputFormatEnum outputFormat = OutputFormatEnum.JAVA;
public JadxArgs() {
// use default options
}
@@ -56,6 +87,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;
}
@@ -132,36 +167,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() {
@@ -172,6 +231,14 @@ public class JadxArgs {
this.useSourceNameAsClassAlias = useSourceNameAsClassAlias;
}
public boolean isParseKotlinMetadata() {
return parseKotlinMetadata;
}
public void setParseKotlinMetadata(boolean parseKotlinMetadata) {
this.parseKotlinMetadata = parseKotlinMetadata;
}
public int getDeobfuscationMinLength() {
return deobfuscationMinLength;
}
@@ -204,6 +271,14 @@ public class JadxArgs {
this.replaceConsts = replaceConsts;
}
public boolean isRespectBytecodeAccModifiers() {
return respectBytecodeAccModifiers;
}
public void setRespectBytecodeAccModifiers(boolean respectBytecodeAccModifiers) {
this.respectBytecodeAccModifiers = respectBytecodeAccModifiers;
}
public boolean isExportAsGradleProject() {
return exportAsGradleProject;
}
@@ -212,30 +287,102 @@ public class JadxArgs {
this.exportAsGradleProject = exportAsGradleProject;
}
public boolean isFsCaseSensitive() {
return fsCaseSensitive;
}
public void setFsCaseSensitive(boolean fsCaseSensitive) {
this.fsCaseSensitive = fsCaseSensitive;
}
public boolean isRenameCaseSensitive() {
return renameFlags.contains(RenameEnum.CASE);
}
public void setRenameCaseSensitive(boolean renameCaseSensitive) {
updateRenameFlag(renameCaseSensitive, RenameEnum.CASE);
}
public boolean isRenameValid() {
return renameFlags.contains(RenameEnum.VALID);
}
public void setRenameValid(boolean renameValid) {
updateRenameFlag(renameValid, RenameEnum.VALID);
}
public boolean isRenamePrintable() {
return renameFlags.contains(RenameEnum.PRINTABLE);
}
public void setRenamePrintable(boolean renamePrintable) {
updateRenameFlag(renamePrintable, RenameEnum.PRINTABLE);
}
private void updateRenameFlag(boolean enabled, RenameEnum flag) {
if (enabled) {
renameFlags.add(flag);
} else {
renameFlags.remove(flag);
}
}
public void setRenameFlags(Set<RenameEnum> renameFlags) {
this.renameFlags = renameFlags;
}
public Set<RenameEnum> getRenameFlags() {
return renameFlags;
}
public OutputFormatEnum getOutputFormat() {
return outputFormat;
}
public boolean isJsonOutput() {
return outputFormat == OutputFormatEnum.JSON;
}
public void setOutputFormat(OutputFormatEnum outputFormat) {
this.outputFormat = outputFormat;
}
public ICodeCache getCodeCache() {
return codeCache;
}
public void setCodeCache(ICodeCache codeCache) {
this.codeCache = codeCache;
}
@Override
public String toString() {
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(", exportAsGradleProject=").append(exportAsGradleProject);
sb.append('}');
return sb.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
+ ", parseKotlinMetadata=" + parseKotlinMetadata
+ ", deobfuscationMinLength=" + deobfuscationMinLength
+ ", deobfuscationMaxLength=" + deobfuscationMaxLength
+ ", escapeUnicode=" + escapeUnicode
+ ", replaceConsts=" + replaceConsts
+ ", respectBytecodeAccModifiers=" + respectBytecodeAccModifiers
+ ", exportAsGradleProject=" + exportAsGradleProject
+ ", fsCaseSensitive=" + fsCaseSensitive
+ ", renameFlags=" + renameFlags
+ ", outputFormat=" + outputFormat
+ ", codeCache=" + codeCache
+ '}';
}
}
@@ -27,14 +27,11 @@ public class JadxArgsValidator {
if (inputFiles.isEmpty()) {
throw new JadxArgsValidateException("Please specify input file");
}
if (inputFiles.size() > 1) {
for (File inputFile : inputFiles) {
String fileName = inputFile.getName();
if (fileName.startsWith("--")) {
throw new JadxArgsValidateException("Unknown argument: " + fileName);
}
for (File inputFile : inputFiles) {
String fileName = inputFile.getName();
if (fileName.startsWith("--")) {
throw new JadxArgsValidateException("Unknown argument: " + fileName);
}
throw new JadxArgsValidateException("Only one input file supported");
}
for (File file : inputFiles) {
checkFile(file);
@@ -53,13 +50,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 +74,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 +90,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,74 +1,87 @@
package jadx.api;
import java.io.Closeable;
import java.io.File;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
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 java.util.stream.Collectors;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.plugins.JadxPlugin;
import jadx.api.plugins.JadxPluginManager;
import jadx.api.plugins.input.JadxInputPlugin;
import jadx.api.plugins.input.data.ILoadResult;
import jadx.core.Jadx;
import jadx.core.ProcessClass;
import jadx.core.codegen.CodeGen;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.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;
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"));
*
* JadxDecompiler jadx = new JadxDecompiler(args);
* jadx.load();
* jadx.save();
* </code></pre>
*
* try (JadxDecompiler jadx = new JadxDecompiler(args)) {
* jadx.load();
* jadx.save();
* }
* </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 {
public final class JadxDecompiler implements Closeable {
private static final Logger LOG = LoggerFactory.getLogger(JadxDecompiler.class);
private JadxArgs args;
private final List<InputFile> inputFiles = new ArrayList<>();
private final JadxArgs args;
private final JadxPluginManager pluginManager = new JadxPluginManager();
private final List<ILoadResult> loadedInputs = new ArrayList<>();
private RootNode root;
private List<IDexTreeVisitor> passes;
private CodeGen codeGen;
private List<JavaClass> classes;
private List<ResourceFile> resources;
private BinaryXMLParser xmlParser;
private Map<ClassNode, JavaClass> classesMap = new ConcurrentHashMap<>();
private Map<MethodNode, JavaMethod> methodsMap = new ConcurrentHashMap<>();
private Map<FieldNode, JavaField> fieldsMap = new ConcurrentHashMap<>();
private final Map<ClassNode, JavaClass> classesMap = new ConcurrentHashMap<>();
private final Map<MethodNode, JavaMethod> methodsMap = new ConcurrentHashMap<>();
private final Map<FieldNode, JavaField> fieldsMap = new ConcurrentHashMap<>();
public JadxDecompiler() {
this(new JadxArgs());
@@ -81,53 +94,65 @@ public final class JadxDecompiler {
public void load() {
reset();
JadxArgsValidator.validate(args);
init();
LOG.info("loading ...");
loadFiles(args.getInputFiles());
loadInputFiles();
root = new RootNode(args);
root.load(inputFiles);
root.loadClasses(loadedInputs);
root.initClassPath();
root.loadResources(getResources());
root.initAppResClass();
initVisitors();
root.runPreDecompileStage();
root.initPasses();
}
void init() {
this.passes = Jadx.getPassesList(args);
this.codeGen = new CodeGen();
private void loadInputFiles() {
loadedInputs.clear();
List<Path> inputPaths = Utils.collectionMap(args.getInputFiles(), File::toPath);
for (JadxInputPlugin inputPlugin : pluginManager.getInputPlugins()) {
ILoadResult loadResult = inputPlugin.loadFiles(inputPaths);
if (loadResult != null && !loadResult.isEmpty()) {
loadedInputs.add(loadResult);
}
}
}
void reset() {
private void reset() {
root = null;
classes = null;
resources = null;
xmlParser = null;
root = null;
passes = null;
codeGen = null;
classesMap.clear();
methodsMap.clear();
fieldsMap.clear();
closeInputs();
}
private void closeInputs() {
loadedInputs.forEach(load -> {
try {
load.close();
} catch (Exception e) {
LOG.error("Failed to close input", e);
}
});
loadedInputs.clear();
}
@Override
public void close() {
reset();
}
public void registerPlugin(JadxPlugin plugin) {
pluginManager.register(plugin);
}
public static String getVersion() {
return Jadx.getVersion();
}
private void loadFiles(List<File> files) {
if (files.isEmpty()) {
throw new JadxRuntimeException("Empty file list");
}
inputFiles.clear();
for (File file : files) {
try {
InputFile.addFilesFrom(file, inputFiles, args.isSkipSources());
} catch (Exception e) {
throw new JadxRuntimeException("Error load file: " + file, e);
}
}
}
public void save() {
save(!args.isSkipSources(), !args.isSkipResources());
}
@@ -176,30 +201,40 @@ public final class JadxDecompiler {
sourcesOutDir = args.getOutDirSrc();
resOutDir = args.getOutDirRes();
}
if (saveSources) {
appendSourcesSave(executor, sourcesOutDir);
}
if (saveResources) {
appendResourcesSave(executor, resOutDir);
}
if (saveSources) {
appendSourcesSave(executor, sourcesOutDir);
}
return executor;
}
private void appendResourcesSave(ExecutorService executor, File outDir) {
Set<String> inputFileNames = args.getInputFiles().stream().map(File::getAbsolutePath).collect(Collectors.toSet());
for (ResourceFile resourceFile : getResources()) {
if (resourceFile.getType() != ResourceType.ARSC
&& inputFileNames.contains(resourceFile.getOriginalName())) {
// ignore resource made from input file
continue;
}
executor.execute(new ResourcesSaver(outDir, resourceFile));
}
}
private void appendSourcesSave(ExecutorService executor, File outDir) {
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());
ICodeInfo code = cls.getCodeInfo();
SaveCode.save(outDir, cls.getClassNode(), code);
} catch (Exception e) {
LOG.error("Error saving class: {}", cls.getFullName(), e);
}
@@ -216,9 +251,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);
}
@@ -230,7 +267,7 @@ public final class JadxDecompiler {
if (root == null) {
return Collections.emptyList();
}
resources = new ResourcesLoader(this).load(inputFiles);
resources = new ResourcesLoader(this).load();
}
return resources;
}
@@ -252,7 +289,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);
}
@@ -279,21 +316,10 @@ public final class JadxDecompiler {
root.getErrorsCounter().printReport();
}
private void initVisitors() {
for (IDexTreeVisitor pass : passes) {
try {
pass.init(root);
} catch (Exception e) {
LOG.error("Visitor init failed: {}", pass.getClass().getSimpleName(), e);
}
}
}
void processClass(ClassNode cls) {
ProcessClass.process(cls, passes, codeGen);
}
RootNode getRoot() {
/**
* Internal API. Not Stable!
*/
public RootNode getRoot() {
return root;
}
@@ -304,16 +330,140 @@ public final class JadxDecompiler {
return xmlParser;
}
Map<ClassNode, JavaClass> getClassesMap() {
return classesMap;
private void loadJavaClass(JavaClass javaClass) {
javaClass.getMethods().forEach(mth -> methodsMap.put(mth.getMethodNode(), mth));
javaClass.getFields().forEach(fld -> fieldsMap.put(fld.getFieldNode(), fld));
for (JavaClass innerCls : javaClass.getInnerClasses()) {
classesMap.put(innerCls.getClassNode(), innerCls);
loadJavaClass(innerCls);
}
}
Map<MethodNode, JavaMethod> getMethodsMap() {
return methodsMap;
@Nullable("For not generated classes")
private JavaClass getJavaClassByNode(ClassNode cls) {
JavaClass javaClass = classesMap.get(cls);
if (javaClass != null) {
return javaClass;
}
// load parent class if inner
ClassNode parentClass = cls.getTopParentClass();
if (parentClass.contains(AFlag.DONT_GENERATE)) {
return null;
}
if (parentClass != cls) {
JavaClass parentJavaClass = classesMap.get(parentClass);
if (parentJavaClass == null) {
getClasses();
parentJavaClass = classesMap.get(parentClass);
}
loadJavaClass(parentJavaClass);
javaClass = classesMap.get(cls);
if (javaClass != null) {
return javaClass;
}
}
// class or parent classes can be excluded from generation
if (cls.hasNotGeneratedParent()) {
return null;
}
throw new JadxRuntimeException("JavaClass not found by ClassNode: " + cls);
}
Map<FieldNode, JavaField> getFieldsMap() {
return fieldsMap;
@Nullable
private JavaMethod getJavaMethodByNode(MethodNode mth) {
JavaMethod javaMethod = methodsMap.get(mth);
if (javaMethod != null) {
return javaMethod;
}
// parent class not loaded yet
JavaClass javaClass = getJavaClassByNode(mth.getParentClass().getTopParentClass());
if (javaClass == null) {
return null;
}
loadJavaClass(javaClass);
javaMethod = methodsMap.get(mth);
if (javaMethod != null) {
return javaMethod;
}
if (mth.getParentClass().hasNotGeneratedParent()) {
return null;
}
throw new JadxRuntimeException("JavaMethod not found by MethodNode: " + mth);
}
@Nullable
private JavaField getJavaFieldByNode(FieldNode fld) {
JavaField javaField = fieldsMap.get(fld);
if (javaField != null) {
return javaField;
}
// parent class not loaded yet
JavaClass javaClass = getJavaClassByNode(fld.getParentClass().getTopParentClass());
if (javaClass == null) {
return null;
}
loadJavaClass(javaClass);
javaField = fieldsMap.get(fld);
if (javaField != null) {
return javaField;
}
if (fld.getParentClass().hasNotGeneratedParent()) {
return null;
}
throw new JadxRuntimeException("JavaField not found by FieldNode: " + fld);
}
@Nullable
JavaNode convertNode(Object obj) {
if (!(obj instanceof LineAttrNode)) {
return null;
}
LineAttrNode node = (LineAttrNode) obj;
if (node.contains(AFlag.DONT_GENERATE)) {
return null;
}
if (obj instanceof ClassNode) {
return getJavaClassByNode((ClassNode) obj);
}
if (obj instanceof MethodNode) {
return getJavaMethodByNode(((MethodNode) obj));
}
if (obj instanceof FieldNode) {
return getJavaFieldByNode((FieldNode) obj);
}
throw new JadxRuntimeException("Unexpected node type: " + obj);
}
List<JavaNode> convertNodes(Collection<?> nodesList) {
return nodesList.stream()
.map(this::convertNode)
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
@Nullable
public JavaNode getJavaNodeAtPosition(ICodeInfo codeInfo, int line, int offset) {
Map<CodePosition, Object> map = codeInfo.getAnnotations();
if (map.isEmpty()) {
return null;
}
Object obj = map.get(new CodePosition(line, offset));
if (obj == null) {
return null;
}
return convertNode(obj);
}
@Nullable
public CodePosition getDefinitionPosition(JavaNode javaNode) {
JavaClass jCls = javaNode.getTopParentClass();
jCls.decompile();
int defLine = javaNode.getDecompiledLine();
if (defLine == 0) {
return null;
}
return new CodePosition(jCls, defLine, 0);
}
public JadxArgs getArgs() {
+54 -78
View File
@@ -9,9 +9,7 @@ import java.util.Map;
import org.jetbrains.annotations.Nullable;
import jadx.core.codegen.CodeWriter;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
@@ -26,6 +24,7 @@ public final class JavaClass implements JavaNode {
private List<JavaClass> innerClasses = Collections.emptyList();
private List<JavaField> fields = Collections.emptyList();
private List<JavaMethod> methods = Collections.emptyList();
private boolean listsLoaded;
JavaClass(ClassNode classNode, JadxDecompiler decompiler) {
this.decompiler = decompiler;
@@ -43,46 +42,57 @@ public final class JavaClass implements JavaNode {
}
public String getCode() {
CodeWriter code = cls.getCode();
ICodeInfo code = getCodeInfo();
if (code == null) {
decompile();
code = cls.getCode();
if (code == null) {
return "";
}
return "";
}
return code.getCodeStr();
}
public synchronized void decompile() {
if (decompiler == null) {
return;
}
if (cls.getCode() == null) {
decompiler.processClass(cls);
load();
}
public ICodeInfo getCodeInfo() {
return cls.decompile();
}
public void decompile() {
cls.decompile();
}
public synchronized void reload() {
listsLoaded = false;
cls.reloadCode();
}
public synchronized String getSmali() {
return cls.getSmali();
}
public synchronized void unload() {
cls.unload();
listsLoaded = false;
}
ClassNode getClassNode() {
/**
* Internal API. Not Stable!
*/
public ClassNode getClassNode() {
return cls;
}
private void load() {
JadxDecompiler rootDecompiler = getRootDecompiler();
private synchronized void loadLists() {
if (listsLoaded) {
return;
}
listsLoaded = true;
decompile();
int inClsCount = cls.getInnerClasses().size();
if (inClsCount != 0) {
List<JavaClass> list = new ArrayList<>(inClsCount);
for (ClassNode inner : cls.getInnerClasses()) {
if (!inner.contains(AFlag.DONT_GENERATE)) {
JavaClass javaClass = new JavaClass(inner, this);
javaClass.load();
javaClass.loadLists();
list.add(javaClass);
rootDecompiler.getClassesMap().put(inner, javaClass);
}
}
this.innerClasses = Collections.unmodifiableList(list);
@@ -95,7 +105,6 @@ public final class JavaClass implements JavaNode {
if (!f.contains(AFlag.DONT_GENERATE)) {
JavaField javaField = new JavaField(f, this);
flds.add(javaField);
rootDecompiler.getFieldsMap().put(f, javaField);
}
}
this.fields = Collections.unmodifiableList(flds);
@@ -108,7 +117,6 @@ public final class JavaClass implements JavaNode {
if (!m.contains(AFlag.DONT_GENERATE)) {
JavaMethod javaMethod = new JavaMethod(this, m);
mths.add(javaMethod);
rootDecompiler.getMethodsMap().put(m, javaMethod);
}
}
mths.sort(Comparator.comparing(JavaMethod::getName));
@@ -116,7 +124,7 @@ public final class JavaClass implements JavaNode {
}
}
private JadxDecompiler getRootDecompiler() {
protected JadxDecompiler getRootDecompiler() {
if (parent != null) {
return parent.getRootDecompiler();
}
@@ -124,8 +132,7 @@ public final class JavaClass implements JavaNode {
}
private Map<CodePosition, Object> getCodeAnnotations() {
decompile();
CodeWriter code = cls.getCode();
ICodeInfo code = getCodeInfo();
if (code == null) {
return Collections.emptyMap();
}
@@ -141,69 +148,33 @@ public final class JavaClass implements JavaNode {
for (Map.Entry<CodePosition, Object> entry : map.entrySet()) {
CodePosition codePosition = entry.getKey();
Object obj = entry.getValue();
if (obj instanceof LineAttrNode) {
JavaNode node = convertNode(obj);
if (node != null) {
resultMap.put(codePosition, node);
}
JavaNode node = getRootDecompiler().convertNode(obj);
if (node != null) {
resultMap.put(codePosition, node);
}
}
return resultMap;
}
@Nullable
private JavaNode convertNode(Object obj) {
if (!(obj instanceof LineAttrNode)) {
return null;
}
if (obj instanceof ClassNode) {
return getRootDecompiler().getClassesMap().get(obj);
}
if (obj instanceof MethodNode) {
return getRootDecompiler().getMethodsMap().get(obj);
}
if (obj instanceof FieldNode) {
return getRootDecompiler().getFieldsMap().get(obj);
}
return null;
@Override
public List<JavaNode> getUseIn() {
return getRootDecompiler().convertNodes(cls.getUseIn());
}
@Nullable
@Deprecated
public JavaNode getJavaNodeAtPosition(int line, int offset) {
Map<CodePosition, Object> map = getCodeAnnotations();
if (map.isEmpty()) {
return null;
}
Object obj = map.get(new CodePosition(line, offset));
if (obj == null) {
return null;
}
return convertNode(obj);
return getRootDecompiler().getJavaNodeAtPosition(getCodeInfo(), line, offset);
}
@Nullable
public CodePosition getDefinitionPosition(int line, int offset) {
JavaNode javaNode = getJavaNodeAtPosition(line, offset);
if (javaNode == null) {
return null;
}
return getDefinitionPosition(javaNode);
}
@Nullable
public CodePosition getDefinitionPosition(JavaNode javaNode) {
JavaClass jCls = javaNode.getTopParentClass();
jCls.decompile();
int defLine = javaNode.getDecompiledLine();
if (defLine == 0) {
return null;
}
return new CodePosition(jCls, defLine, 0);
@Deprecated
public CodePosition getDefinitionPosition() {
return getRootDecompiler().getDefinitionPosition(this);
}
public Integer getSourceLine(int decompiledLine) {
decompile();
return cls.getCode().getLineMapping().get(decompiledLine);
return getCodeInfo().getLineMapping().get(decompiledLine);
}
@Override
@@ -216,6 +187,10 @@ public final class JavaClass implements JavaNode {
return cls.getFullName();
}
public String getRawName() {
return cls.getRawName();
}
public String getPackage() {
return cls.getPackage();
}
@@ -235,20 +210,21 @@ public final class JavaClass implements JavaNode {
}
public List<JavaClass> getInnerClasses() {
decompile();
loadLists();
return innerClasses;
}
public List<JavaField> getFields() {
decompile();
loadLists();
return fields;
}
public List<JavaMethod> getMethods() {
decompile();
loadLists();
return methods;
}
@Override
public int getDecompiledLine() {
return cls.getDecompiledLine();
}
@@ -265,6 +241,6 @@ public final class JavaClass implements JavaNode {
@Override
public String toString() {
return cls.getFullName() + "[ " + getFullName() + " ]";
return getFullName();
}
}
@@ -1,5 +1,7 @@
package jadx.api;
import java.util.List;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.FieldNode;
@@ -21,7 +23,7 @@ public final class JavaField implements JavaNode {
@Override
public String getFullName() {
return parent.getFullName() + "." + getName();
return parent.getFullName() + '.' + getName();
}
@Override
@@ -39,13 +41,26 @@ public final class JavaField implements JavaNode {
}
public ArgType getType() {
return field.getType();
return ArgType.tryToResolveClassAlias(field.root(), field.getType());
}
@Override
public int getDecompiledLine() {
return field.getDecompiledLine();
}
@Override
public List<JavaNode> getUseIn() {
return getDeclaringClass().getRootDecompiler().convertNodes(field.getUseIn());
}
/**
* Internal API. Not Stable!
*/
public FieldNode getFieldNode() {
return field;
}
@Override
public int hashCode() {
return field.hashCode();
@@ -1,10 +1,12 @@
package jadx.api;
import java.util.Collections;
import java.util.List;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.Utils;
public final class JavaMethod implements JavaNode {
private final MethodNode mth;
@@ -40,11 +42,23 @@ public final class JavaMethod implements JavaNode {
}
public List<ArgType> getArguments() {
return mth.getMethodInfo().getArgumentsTypes();
List<ArgType> infoArgTypes = mth.getMethodInfo().getArgumentsTypes();
if (infoArgTypes.isEmpty()) {
return Collections.emptyList();
}
List<ArgType> arguments = mth.getArgTypes();
return Utils.collectionMap(arguments,
type -> ArgType.tryToResolveClassAlias(mth.root(), type));
}
public ArgType getReturnType() {
return mth.getReturnType();
ArgType retType = mth.getReturnType();
return ArgType.tryToResolveClassAlias(mth.root(), retType);
}
@Override
public List<JavaNode> getUseIn() {
return getDeclaringClass().getRootDecompiler().convertNodes(mth.getUseIn());
}
public boolean isConstructor() {
@@ -55,10 +69,18 @@ public final class JavaMethod implements JavaNode {
return mth.getMethodInfo().isClassInit();
}
@Override
public int getDecompiledLine() {
return mth.getDecompiledLine();
}
/**
* Internal API. Not Stable!
*/
public MethodNode getMethodNode() {
return mth;
}
@Override
public int hashCode() {
return mth.hashCode();
@@ -1,5 +1,7 @@
package jadx.api;
import java.util.List;
public interface JavaNode {
String getName();
@@ -11,4 +13,6 @@ public interface JavaNode {
JavaClass getTopParentClass();
int getDecompiledLine();
List<JavaNode> getUseIn();
}
@@ -1,5 +1,6 @@
package jadx.api;
import java.util.Collections;
import java.util.List;
import org.jetbrains.annotations.NotNull;
@@ -43,6 +44,11 @@ public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
return 0;
}
@Override
public List<JavaNode> getUseIn() {
return Collections.emptyList();
}
@Override
public int compareTo(@NotNull JavaPackage o) {
return name.compareTo(o.name);
@@ -2,8 +2,9 @@ package jadx.api;
import java.io.File;
import jadx.core.utils.files.ZipSecurity;
import jadx.api.plugins.utils.ZipSecurity;
import jadx.core.xmlgen.ResContainer;
import jadx.core.xmlgen.entry.ResourceEntry;
public class ResourceFile {
@@ -34,6 +35,14 @@ public class ResourceFile {
private final String name;
private final ResourceType type;
private ZipRef zipRef;
private String deobfName;
public static ResourceFile createResourceFile(JadxDecompiler decompiler, String name, ResourceType type) {
if (!ZipSecurity.isValidZipEntryName(name)) {
return null;
}
return new ResourceFile(decompiler, name, type);
}
protected ResourceFile(JadxDecompiler decompiler, String name, ResourceType type) {
this.decompiler = decompiler;
@@ -41,10 +50,14 @@ public class ResourceFile {
this.type = type;
}
public String getName() {
public String getOriginalName() {
return name;
}
public String getDeobfName() {
return deobfName != null ? deobfName : name;
}
public ResourceType getType() {
return type;
}
@@ -57,19 +70,21 @@ public class ResourceFile {
this.zipRef = zipRef;
}
public void setAlias(ResourceEntry ri) {
int index = name.lastIndexOf('.');
deobfName = String.format("res/%s%s/%s%s",
ri.getTypeName(),
ri.getConfig(),
ri.getKeyName(),
index == -1 ? "" : name.substring(index));
}
public ZipRef getZipRef() {
return zipRef;
}
@Override
public String toString() {
return "ResourceFile{name='" + name + '\'' + ", type=" + type + "}";
}
public static ResourceFile createResourceFileInstance(JadxDecompiler decompiler, String name, ResourceType type) {
if(!ZipSecurity.isValidZipEntryName(name)) {
return null;
}
return new ResourceFile(decompiler, name, type);
return "ResourceFile{name='" + name + '\'' + ", type=" + type + '}';
}
}
@@ -1,27 +1,17 @@
package jadx.api;
import jadx.core.codegen.CodeWriter;
import jadx.core.utils.files.ZipSecurity;
import jadx.core.xmlgen.ResContainer;
public class ResourceFileContent extends ResourceFile {
private final ICodeInfo content;
private final CodeWriter content;
private ResourceFileContent(String name, ResourceType type, CodeWriter content) {
public ResourceFileContent(String name, ResourceType type, ICodeInfo content) {
super(null, name, type);
this.content = content;
}
@Override
public ResContainer loadContent() {
return ResContainer.singleFile(getName(), content);
}
public static ResourceFileContent createResourceFileContentInstance(String name, ResourceType type, CodeWriter content) {
if(!ZipSecurity.isValidZipEntryName(name)) {
return null;
}
return new ResourceFileContent(name, type, content);
return ResContainer.textResource(getDeobfName(), content);
}
}
@@ -1,13 +1,20 @@
package jadx.api;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import jadx.core.utils.exceptions.JadxRuntimeException;
public enum ResourceType {
CODE(".dex", ".jar", ".class"),
MANIFEST("AndroidManifest.xml"),
XML(".xml"),
ARSC(".arsc"),
FONT(".ttf"),
FONT(".ttf", ".otf"),
IMG(".png", ".gif", ".jpg"),
MEDIA(".mp3", ".wav"),
LIB(".so"),
MANIFEST,
UNKNOWN;
private final String[] exts;
@@ -20,31 +27,31 @@ public enum ResourceType {
return exts;
}
public static ResourceType getFileType(String fileName) {
private static final Map<String, ResourceType> EXT_MAP = new HashMap<>();
static {
for (ResourceType type : ResourceType.values()) {
for (String ext : type.getExts()) {
if (fileName.endsWith(ext)) {
return type;
ResourceType prev = EXT_MAP.put(ext, type);
if (prev != null) {
throw new JadxRuntimeException("Duplicate extension in ResourceType: " + ext);
}
}
}
}
public static ResourceType getFileType(String fileName) {
int dot = fileName.lastIndexOf('.');
if (dot != -1) {
String ext = fileName.substring(dot).toLowerCase(Locale.ROOT);
ResourceType resType = EXT_MAP.get(ext);
if (resType != null) {
if (resType == XML && fileName.equals("AndroidManifest.xml")) {
return MANIFEST;
}
return resType;
}
}
return UNKNOWN;
}
public static boolean isSupportedForUnpack(ResourceType type) {
switch (type) {
case CODE:
case LIB:
case FONT:
case UNKNOWN:
return false;
case MANIFEST:
case XML:
case ARSC:
case IMG:
return true;
}
return false;
}
}
@@ -7,7 +7,6 @@ import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
@@ -16,11 +15,13 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.ResourceFile.ZipRef;
import jadx.api.impl.SimpleCodeInfo;
import jadx.api.plugins.utils.ZipSecurity;
import jadx.core.codegen.CodeWriter;
import jadx.core.utils.Utils;
import jadx.core.utils.android.Res9patchStreamDecoder;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.files.InputFile;
import jadx.core.utils.files.ZipSecurity;
import jadx.core.utils.files.FileUtils;
import jadx.core.xmlgen.ResContainer;
import jadx.core.xmlgen.ResTableParser;
@@ -31,31 +32,30 @@ import static jadx.core.utils.files.FileUtils.copyStream;
public final class ResourcesLoader {
private static final Logger LOG = LoggerFactory.getLogger(ResourcesLoader.class);
private static final int LOAD_SIZE_LIMIT = 10 * 1024 * 1024;
private final JadxDecompiler jadxRef;
ResourcesLoader(JadxDecompiler jadxRef) {
this.jadxRef = jadxRef;
}
List<ResourceFile> load(List<InputFile> inputFiles) {
List<ResourceFile> load() {
List<File> inputFiles = jadxRef.getArgs().getInputFiles();
List<ResourceFile> list = new ArrayList<>(inputFiles.size());
for (InputFile file : inputFiles) {
loadFile(list, file.getFile());
for (File file : inputFiles) {
loadFile(list, file);
}
return list;
}
public interface ResourceDecoder {
ResContainer decode(long size, InputStream is) throws IOException;
public interface ResourceDecoder<T> {
T decode(long size, InputStream is) throws IOException;
}
public static ResContainer decodeStream(ResourceFile rf, ResourceDecoder decoder) throws JadxException {
public static <T> T decodeStream(ResourceFile rf, ResourceDecoder<T> decoder) throws JadxException {
try {
ZipRef zipRef = rf.getZipRef();
if (zipRef == null) {
File file = new File(rf.getName());
File file = new File(rf.getOriginalName());
try (InputStream inputStream = new BufferedInputStream(new FileInputStream(file))) {
return decoder.decode(file.length(), inputStream);
}
@@ -68,66 +68,68 @@ public final class ResourcesLoader {
if (!ZipSecurity.isValidZipEntry(entry)) {
return null;
}
try (InputStream inputStream = new BufferedInputStream(zipFile.getInputStream(entry))) {
try (InputStream inputStream = ZipSecurity.getInputStreamForEntry(zipFile, entry)) {
return decoder.decode(entry.getSize(), inputStream);
}
}
}
} catch (Exception e) {
throw new JadxException("Error decode: " + rf.getName(), e);
throw new JadxException("Error decode: " + rf.getDeobfName(), e);
}
}
static ResContainer loadContent(JadxDecompiler jadxRef, ResourceFile rf) {
try {
return decodeStream(rf, (size, is) -> loadContent(jadxRef, rf, is, size));
return decodeStream(rf, (size, is) -> loadContent(jadxRef, rf, is));
} catch (JadxException e) {
LOG.error("Decode error", e);
CodeWriter cw = new CodeWriter();
cw.add("Error decode ").add(rf.getType().toString().toLowerCase());
cw.startLine(Utils.getStackTrace(e.getCause()));
return ResContainer.singleFile(rf.getName(), cw);
Utils.appendStackTrace(cw, e.getCause());
return ResContainer.textResource(rf.getDeobfName(), cw.finish());
}
}
private static ResContainer loadContent(JadxDecompiler jadxRef, ResourceFile rf,
InputStream inputStream, long size) throws IOException {
InputStream inputStream) throws IOException {
switch (rf.getType()) {
case MANIFEST:
case XML:
return ResContainer.singleFile(rf.getName(),
jadxRef.getXmlParser().parse(inputStream));
ICodeInfo content = jadxRef.getXmlParser().parse(inputStream);
return ResContainer.textResource(rf.getDeobfName(), content);
case ARSC:
return new ResTableParser()
.decodeFiles(inputStream);
return new ResTableParser(jadxRef.getRoot()).decodeFiles(inputStream);
case IMG:
return ResContainer.singleImageFile(rf.getName(), inputStream);
return decodeImage(rf, inputStream);
default:
if (size > LOAD_SIZE_LIMIT) {
return ResContainer.singleFile(rf.getName(),
new CodeWriter().add("File too big, size: " + String.format("%.2f KB", size / 1024.)));
}
return ResContainer.singleFile(rf.getName(), loadToCodeWriter(inputStream));
return ResContainer.resourceFileLink(rf);
}
}
private static ResContainer decodeImage(ResourceFile rf, InputStream inputStream) {
String name = rf.getOriginalName();
if (name.endsWith(".9.png")) {
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
Res9patchStreamDecoder decoder = new Res9patchStreamDecoder();
decoder.decode(inputStream, os);
return ResContainer.decodedData(rf.getDeobfName(), os.toByteArray());
} catch (Exception e) {
LOG.error("Failed to decode 9-patch png image, path: {}", name, e);
}
}
return ResContainer.resourceFileLink(rf);
}
private void loadFile(List<ResourceFile> list, File file) {
if (file == null) {
return;
}
try (ZipFile zip = new ZipFile(file)) {
Enumeration<? extends ZipEntry> entries = zip.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
if (ZipSecurity.isValidZipEntry(entry)) {
addEntry(list, file, entry);
}
}
} catch (Exception e) {
LOG.debug("Not a zip file: {}", file.getAbsolutePath());
if (FileUtils.isZipFile(file)) {
ZipSecurity.visitZipEntries(file, (zipFile, entry) -> addEntry(list, file, entry));
} else {
addResourceFile(list, file);
}
}
@@ -135,7 +137,7 @@ public final class ResourcesLoader {
private void addResourceFile(List<ResourceFile> list, File file) {
String name = file.getAbsolutePath();
ResourceType type = ResourceType.getFileType(name);
ResourceFile rf = ResourceFile.createResourceFileInstance(jadxRef, name, type);
ResourceFile rf = ResourceFile.createResourceFile(jadxRef, name, type);
if (rf != null) {
list.add(rf);
}
@@ -147,18 +149,16 @@ public final class ResourcesLoader {
}
String name = entry.getName();
ResourceType type = ResourceType.getFileType(name);
ResourceFile rf = ResourceFile.createResourceFileInstance(jadxRef, name, type);
ResourceFile rf = ResourceFile.createResourceFile(jadxRef, name, type);
if (rf != null) {
rf.setZipRef(new ZipRef(zipFile, name));
list.add(rf);
}
}
public static CodeWriter loadToCodeWriter(InputStream is) throws IOException {
CodeWriter cw = new CodeWriter();
public static ICodeInfo loadToCodeWriter(InputStream is) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream(READ_BUFFER_SIZE);
copyStream(is, baos);
cw.add(baos.toString("UTF-8"));
return cw;
return new SimpleCodeInfo(baos.toString("UTF-8"));
}
}
@@ -0,0 +1,34 @@
package jadx.api.impl;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.jetbrains.annotations.Nullable;
import jadx.api.ICodeCache;
import jadx.api.ICodeInfo;
public class InMemoryCodeCache implements ICodeCache {
private final Map<String, ICodeInfo> storage = new ConcurrentHashMap<>();
@Override
public void add(String clsFullName, ICodeInfo codeInfo) {
storage.put(clsFullName, codeInfo);
}
@Override
public void remove(String clsFullName) {
storage.remove(clsFullName);
}
@Override
public @Nullable ICodeInfo get(String clsFullName) {
return storage.get(clsFullName);
}
@Override
public String toString() {
return "InMemoryCodeCache";
}
}
@@ -0,0 +1,29 @@
package jadx.api.impl;
import org.jetbrains.annotations.Nullable;
import jadx.api.ICodeCache;
import jadx.api.ICodeInfo;
public class NoOpCodeCache implements ICodeCache {
@Override
public void add(String clsFullName, ICodeInfo codeInfo) {
// do nothing
}
@Override
public void remove(String clsFullName) {
// do nothing
}
@Override
public @Nullable ICodeInfo get(String clsFullName) {
return null;
}
@Override
public String toString() {
return "NoOpCodeCache";
}
}
@@ -0,0 +1,48 @@
package jadx.api.impl;
import java.util.Collections;
import java.util.Map;
import jadx.api.CodePosition;
import jadx.api.ICodeInfo;
public class SimpleCodeInfo implements ICodeInfo {
private final String code;
private final Map<Integer, Integer> lineMapping;
private final Map<CodePosition, Object> annotations;
public SimpleCodeInfo(String code) {
this(code, Collections.emptyMap(), Collections.emptyMap());
}
public SimpleCodeInfo(ICodeInfo codeInfo) {
this(codeInfo.getCodeStr(), codeInfo.getLineMapping(), codeInfo.getAnnotations());
}
public SimpleCodeInfo(String code, Map<Integer, Integer> lineMapping, Map<CodePosition, Object> annotations) {
this.code = code;
this.lineMapping = lineMapping;
this.annotations = annotations;
}
@Override
public String getCodeStr() {
return code;
}
@Override
public Map<Integer, Integer> getLineMapping() {
return lineMapping;
}
@Override
public Map<CodePosition, Object> getAnnotations() {
return annotations;
}
@Override
public String toString() {
return code;
}
}
+10 -5
View File
@@ -2,20 +2,25 @@ package jadx.core;
public class Consts {
public static final boolean DEBUG = false;
public static final boolean DEBUG_WITH_ERRORS = false; // TODO: fix errors
public static final boolean DEBUG_USAGE = false;
public static final boolean DEBUG_TYPE_INFERENCE = false;
public static final boolean DEBUG_OVERLOADED_CASTS = false;
public static final String CLASS_OBJECT = "java.lang.Object";
public static final String CLASS_STRING = "java.lang.String";
public static final String CLASS_CLASS = "java.lang.Class";
public static final String CLASS_THROWABLE = "java.lang.Throwable";
public static final String CLASS_EXCEPTION = "java.lang.Exception";
public static final String CLASS_ENUM = "java.lang.Enum";
public static final String CLASS_STRING_BUILDER = "java.lang.StringBuilder";
public static final String DALVIK_ANNOTATION_PKG = "dalvik.annotation.";
public static final String DALVIK_SIGNATURE = "dalvik.annotation.Signature";
public static final String DALVIK_INNER_CLASS = "dalvik.annotation.InnerClass";
public static final String DALVIK_THROWS = "dalvik.annotation.Throws";
public static final String DALVIK_ANNOTATION_DEFAULT = "dalvik.annotation.AnnotationDefault";
public static final String DALVIK_ANNOTATION_PKG = "Ldalvik/annotation/";
public static final String DALVIK_SIGNATURE = "Ldalvik/annotation/Signature;";
public static final String DALVIK_INNER_CLASS = "Ldalvik/annotation/InnerClass;";
public static final String DALVIK_THROWS = "Ldalvik/annotation/Throws;";
public static final String DALVIK_ANNOTATION_DEFAULT = "Ldalvik/annotation/AnnotationDefault;";
public static final String DEFAULT_PACKAGE_NAME = "defpackage";
public static final String ANONYMOUS_CLASS_PREFIX = "AnonymousClass";
+103 -56
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;
@@ -10,37 +11,52 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs;
import jadx.core.dex.visitors.AttachMethodDetails;
import jadx.core.dex.visitors.AttachTryCatchVisitor;
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.DependencyCollector;
import jadx.core.dex.visitors.ConstructorVisitor;
import jadx.core.dex.visitors.DeboxingVisitor;
import jadx.core.dex.visitors.DotGraphVisitor;
import jadx.core.dex.visitors.EnumVisitor;
import jadx.core.dex.visitors.ExtractFieldInit;
import jadx.core.dex.visitors.FallbackModeVisitor;
import jadx.core.dex.visitors.FixAccessModifiers;
import jadx.core.dex.visitors.GenericTypesVisitor;
import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.dex.visitors.MethodInlineVisitor;
import jadx.core.dex.visitors.InitCodeVariables;
import jadx.core.dex.visitors.InlineMethods;
import jadx.core.dex.visitors.MarkFinallyVisitor;
import jadx.core.dex.visitors.MarkMethodsForInline;
import jadx.core.dex.visitors.MethodInvokeVisitor;
import jadx.core.dex.visitors.ModVisitor;
import jadx.core.dex.visitors.MoveInlineVisitor;
import jadx.core.dex.visitors.OverrideMethodVisitor;
import jadx.core.dex.visitors.PrepareForCodeGen;
import jadx.core.dex.visitors.ProcessAnonymous;
import jadx.core.dex.visitors.ProcessInstructionsVisitor;
import jadx.core.dex.visitors.ReSugarCode;
import jadx.core.dex.visitors.RenameVisitor;
import jadx.core.dex.visitors.ShadowFieldVisitor;
import jadx.core.dex.visitors.SignatureProcessor;
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.DebugInfoAttachVisitor;
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;
import jadx.core.dex.visitors.usage.UsageInfoVisitor;
public class Jadx {
private static final Logger LOG = LoggerFactory.getLogger(Jadx.class);
@@ -54,61 +70,90 @@ public class Jadx {
}
}
public static List<IDexTreeVisitor> getPassesList(JadxArgs args) {
public static List<IDexTreeVisitor> getFallbackPassesList() {
List<IDexTreeVisitor> passes = new ArrayList<>(3);
passes.add(new AttachTryCatchVisitor());
passes.add(new ProcessInstructionsVisitor());
passes.add(new FallbackModeVisitor());
return passes;
}
public static List<IDexTreeVisitor> getPreDecompilePassesList() {
List<IDexTreeVisitor> passes = new ArrayList<>();
passes.add(new SignatureProcessor());
passes.add(new RenameVisitor());
passes.add(new UsageInfoVisitor());
return passes;
}
public static List<IDexTreeVisitor> getPassesList(JadxArgs args) {
if (args.isFallbackMode()) {
passes.add(new FallbackModeVisitor());
} else {
passes.add(new BlockSplitter());
passes.add(new BlockProcessor());
passes.add(new BlockExceptionHandler());
passes.add(new BlockFinallyExtract());
passes.add(new BlockFinish());
return getFallbackPassesList();
}
passes.add(new SSATransform());
passes.add(new DebugInfoVisitor());
passes.add(new TypeInference());
List<IDexTreeVisitor> passes = new ArrayList<>();
if (args.isDebugInfo()) {
passes.add(new DebugInfoAttachVisitor());
}
passes.add(new AttachTryCatchVisitor());
passes.add(new ProcessInstructionsVisitor());
if (args.isRawCFGOutput()) {
passes.add(DotGraphVisitor.dumpRaw());
}
passes.add(new BlockSplitter());
if (args.isRawCFGOutput()) {
passes.add(DotGraphVisitor.dumpRaw());
}
passes.add(new BlockProcessor());
passes.add(new BlockExceptionHandler());
passes.add(new BlockFinish());
passes.add(new ConstInlineVisitor());
passes.add(new FinishTypeInference());
passes.add(new EliminatePhiNodes());
passes.add(new AttachMethodDetails());
passes.add(new OverrideMethodVisitor());
passes.add(new ModVisitor());
passes.add(new SSATransform());
passes.add(new MoveInlineVisitor());
passes.add(new ConstructorVisitor());
passes.add(new InitCodeVariables());
passes.add(new MarkFinallyVisitor());
passes.add(new ConstInlineVisitor());
passes.add(new TypeInferenceVisitor());
if (args.isDebugInfo()) {
passes.add(new DebugInfoApplyVisitor());
}
passes.add(new CodeShrinker());
passes.add(new ReSugarCode());
passes.add(new InlineMethods());
passes.add(new GenericTypesVisitor());
passes.add(new ShadowFieldVisitor());
passes.add(new DeboxingVisitor());
passes.add(new ModVisitor());
passes.add(new CodeShrinkVisitor());
passes.add(new ReSugarCode());
if (args.isCfgOutput()) {
passes.add(DotGraphVisitor.dump());
}
if (args.isCfgOutput()) {
passes.add(DotGraphVisitor.dump());
}
passes.add(new RegionMakerVisitor());
passes.add(new IfRegionVisitor());
passes.add(new ReturnVisitor());
passes.add(new CleanRegions());
passes.add(new RegionMakerVisitor());
passes.add(new IfRegionVisitor());
passes.add(new ReturnVisitor());
passes.add(new CodeShrinkVisitor());
passes.add(new MethodInvokeVisitor());
passes.add(new SimplifyVisitor());
passes.add(new CheckRegions());
passes.add(new CodeShrinker());
passes.add(new SimplifyVisitor());
passes.add(new CheckRegions());
passes.add(new EnumVisitor());
passes.add(new ExtractFieldInit());
passes.add(new FixAccessModifiers());
passes.add(new ProcessAnonymous());
passes.add(new ClassModifier());
passes.add(new LoopRegionVisitor());
passes.add(new MethodInlineVisitor());
passes.add(new ExtractFieldInit());
passes.add(new ClassModifier());
passes.add(new EnumVisitor());
passes.add(new PrepareForCodeGen());
passes.add(new LoopRegionVisitor());
passes.add(new ProcessVariables());
passes.add(new MarkMethodsForInline());
if (args.isCfgOutput()) {
passes.add(DotGraphVisitor.dumpRegions());
}
passes.add(new DependencyCollector());
passes.add(new RenameVisitor());
passes.add(new ProcessVariables());
passes.add(new PrepareForCodeGen());
if (args.isCfgOutput()) {
passes.add(DotGraphVisitor.dumpRegions());
}
return passes;
}
@@ -119,10 +164,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;
}
}
}
}
@@ -1,53 +1,97 @@
package jadx.core;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import jadx.api.ICodeInfo;
import jadx.core.codegen.CodeGen;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.LoadStage;
import jadx.core.dex.visitors.DepthTraversal;
import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.exceptions.JadxRuntimeException;
import static jadx.core.dex.nodes.ProcessState.GENERATED_AND_UNLOADED;
import static jadx.core.dex.nodes.ProcessState.LOADED;
import static jadx.core.dex.nodes.ProcessState.NOT_LOADED;
import static jadx.core.dex.nodes.ProcessState.PROCESSED;
import static jadx.core.dex.nodes.ProcessState.STARTED;
import static jadx.core.dex.nodes.ProcessState.PROCESS_COMPLETE;
import static jadx.core.dex.nodes.ProcessState.PROCESS_STARTED;
public final class ProcessClass {
private ProcessClass() {
}
public static void process(ClassNode cls, List<IDexTreeVisitor> passes, @Nullable CodeGen codeGen) {
if (codeGen == null && cls.getState() == PROCESSED) {
return;
@Nullable
private static ICodeInfo process(ClassNode cls, boolean codegen) {
if (!codegen && cls.getState() == PROCESS_COMPLETE) {
// nothing to do
return null;
}
synchronized (getSyncObj(cls)) {
synchronized (cls.getClassInfo()) {
try {
if (cls.contains(AFlag.CLASS_DEEP_RELOAD)) {
cls.remove(AFlag.CLASS_DEEP_RELOAD);
cls.unload();
cls.deepUnload();
cls.root().runPreDecompileStageForClass(cls);
}
if (codegen) {
if (cls.getState() == GENERATED_AND_UNLOADED) {
// allow to run code generation again
cls.setState(NOT_LOADED);
}
cls.setLoadStage(LoadStage.CODEGEN_STAGE);
if (cls.contains(AFlag.RELOAD_AT_CODEGEN_STAGE)) {
cls.remove(AFlag.RELOAD_AT_CODEGEN_STAGE);
cls.unload();
}
} else {
cls.setLoadStage(LoadStage.PROCESS_STAGE);
}
if (cls.getState() == NOT_LOADED) {
cls.load();
cls.setState(STARTED);
for (IDexTreeVisitor visitor : passes) {
}
if (cls.getState() == LOADED) {
cls.setState(PROCESS_STARTED);
for (IDexTreeVisitor visitor : cls.root().getPasses()) {
DepthTraversal.visit(visitor, cls);
}
cls.setState(PROCESSED);
cls.setState(PROCESS_COMPLETE);
}
if (cls.getState() == PROCESSED && codeGen != null) {
processDependencies(cls, passes);
codeGen.visit(cls);
if (codegen) {
ICodeInfo code = CodeGen.generate(cls);
if (!cls.contains(AFlag.DONT_UNLOAD_CLASS)) {
cls.unload();
cls.setState(GENERATED_AND_UNLOADED);
}
return code;
}
} catch (Exception e) {
ErrorsCounter.classError(cls, e.getClass().getSimpleName(), e);
} catch (Throwable e) {
cls.addError("Class process error: " + e.getClass().getSimpleName(), e);
}
return null;
}
}
public static Object getSyncObj(ClassNode cls) {
return cls.getClassInfo();
}
private static void processDependencies(ClassNode cls, List<IDexTreeVisitor> passes) {
cls.getDependencies().forEach(depCls -> process(depCls, passes, null));
@NotNull
public static ICodeInfo generateCode(ClassNode cls) {
ClassNode topParentClass = cls.getTopParentClass();
if (topParentClass != cls) {
return generateCode(topParentClass);
}
try {
for (ClassNode depCls : cls.getDependencies()) {
process(depCls, false);
}
ICodeInfo code = process(cls, true);
if (code == null) {
throw new JadxRuntimeException("Codegen failed");
}
return code;
} catch (Throwable e) {
throw new JadxRuntimeException("Failed to generate code for class: " + cls.getFullName(), e);
}
}
}
+423 -131
View File
@@ -1,34 +1,45 @@
package jadx.core.clsp;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Stream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.plugins.utils.ZipSecurity;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType;
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;
import static jadx.core.utils.Utils.notEmpty;
/**
* Classes list for import into classpath graph
@@ -41,141 +52,300 @@ 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 = 3;
private static final String STRING_CHARSET = "US-ASCII";
private NClass[] classes;
private static final ArgType[] EMPTY_ARGTYPE_ARRAY = new ArgType[0];
public void load(RootNode root) {
List<ClassNode> list = root.getClasses(true);
Map<String, NClass> names = new HashMap<>(list.size());
int k = 0;
for (ClassNode cls : list) {
String clsRawName = cls.getRawName();
if (cls.getAccessFlags().isPublic()) {
NClass nClass = new NClass(clsRawName, k);
if (names.put(clsRawName, nClass) != null) {
throw new JadxRuntimeException("Duplicate class: " + clsRawName);
}
k++;
} else {
names.put(clsRawName, null);
}
}
classes = new NClass[k];
k = 0;
for (ClassNode cls : list) {
if (cls.getAccessFlags().isPublic()) {
NClass nClass = getCls(cls.getRawName(), names);
if (nClass == null) {
throw new JadxRuntimeException("Missing class: " + cls);
}
nClass.setParents(makeParentsArray(cls, names));
classes[k] = nClass;
k++;
}
}
private final RootNode root;
public ClsSet(RootNode root) {
this.root = root;
}
public static NClass[] makeParentsArray(ClassNode cls, Map<String, NClass> names) {
List<NClass> parents = new ArrayList<>(1 + cls.getInterfaces().size());
ArgType superClass = cls.getSuperClass();
if (superClass != null) {
NClass c = getCls(superClass.getObject(), names);
if (c != null) {
parents.add(c);
}
}
for (ArgType iface : cls.getInterfaces()) {
NClass c = getCls(iface.getObject(), names);
if (c != null) {
parents.add(c);
}
}
return parents.toArray(new NClass[parents.size()]);
private enum TypeEnum {
WILDCARD,
GENERIC,
GENERIC_TYPE_VARIABLE,
OUTER_GENERIC,
OBJECT,
ARRAY,
PRIMITIVE
}
private static NClass getCls(String fullName, Map<String, NClass> names) {
NClass id = names.get(fullName);
if (id == null && !names.containsKey(fullName)) {
LOG.debug("Class not found: {}", fullName);
}
return id;
}
private ClspClass[] classes;
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)) {
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);
}
}
}
public void save(OutputStream output) throws IOException {
try (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());
}
for (NClass cls : classes) {
NClass[] parents = cls.getParents();
out.writeByte(parents.length);
for (NClass parent : parents) {
out.writeInt(parent.getId());
}
}
}
}
public void load() throws IOException, DecodeException {
public void loadFromClstFile() throws IOException, DecodeException {
long startTime = System.currentTimeMillis();
try (InputStream input = getClass().getResourceAsStream(CLST_FILENAME)) {
if (input == null) {
throw new JadxRuntimeException("Can't load classpath file: " + CLST_FILENAME);
}
load(input);
}
if (LOG.isDebugEnabled()) {
long time = System.currentTimeMillis() - startTime;
int methodsCount = Stream.of(classes).mapToInt(clspClass -> clspClass.getMethodsMap().size()).sum();
LOG.debug("Clst file loaded in {}ms, classes: {}, methods: {}", time, classes.length, methodsCount);
}
}
public void load(File input) throws IOException, DecodeException {
String name = input.getName();
try (InputStream inputStream = new FileInputStream(input)) {
if (name.endsWith(CLST_EXTENSION)) {
load(inputStream);
} else if (name.endsWith(".jar")) {
try (ZipInputStream in = new ZipInputStream(inputStream)) {
ZipEntry entry = in.getNextEntry();
while (entry != null) {
if (entry.getName().endsWith(CLST_EXTENSION) && ZipSecurity.isValidZipEntry(entry)) {
load(in);
}
entry = in.getNextEntry();
public void loadFrom(RootNode root) {
List<ClassNode> list = root.getClasses(true);
Map<String, ClspClass> names = new HashMap<>(list.size());
int k = 0;
for (ClassNode cls : list) {
ArgType clsType = cls.getClassInfo().getType();
String clsRawName = clsType.getObject();
cls.load();
ClspClass nClass = new ClspClass(clsType, k);
if (names.put(clsRawName, nClass) != null) {
throw new JadxRuntimeException("Duplicate class: " + clsRawName);
}
k++;
nClass.setTypeParameters(cls.getGenericTypeParameters());
nClass.setMethods(getMethodsDetails(cls));
}
classes = new ClspClass[k];
k = 0;
for (ClassNode cls : list) {
ClspClass nClass = getCls(cls, names);
if (nClass == null) {
throw new JadxRuntimeException("Missing class: " + cls);
}
nClass.setParents(makeParentsArray(cls));
classes[k] = nClass;
k++;
}
}
private List<ClspMethod> getMethodsDetails(ClassNode cls) {
List<MethodNode> methodsList = cls.getMethods();
List<ClspMethod> methods = new ArrayList<>(methodsList.size());
for (MethodNode mth : methodsList) {
processMethodDetails(mth, methods);
}
return methods;
}
private void processMethodDetails(MethodNode mth, List<ClspMethod> methods) {
AccessInfo accessFlags = mth.getAccessFlags();
if (accessFlags.isPrivate()) {
return;
}
ArgType genericRetType = mth.getReturnType();
boolean varArgs = accessFlags.isVarArgs();
List<ArgType> throwList = mth.getThrows();
List<ArgType> typeParameters = mth.getTypeParameters();
// add only methods with additional info
if (varArgs
|| notEmpty(throwList)
|| notEmpty(typeParameters)
|| genericRetType.containsGeneric()
|| mth.containsGenericArgs()
|| mth.isArgsOverloaded()) {
ClspMethod clspMethod = new ClspMethod(mth.getMethodInfo(),
mth.getArgTypes(), genericRetType,
typeParameters, varArgs, throwList);
methods.add(clspMethod);
}
}
public static ArgType[] makeParentsArray(ClassNode cls) {
ArgType superClass = cls.getSuperClass();
if (superClass == null) {
// cls is java.lang.Object
return EMPTY_ARGTYPE_ARRAY;
}
ArgType[] parents = new ArgType[1 + cls.getInterfaces().size()];
parents[0] = superClass;
int k = 1;
for (ArgType iface : cls.getInterfaces()) {
parents[k] = iface;
k++;
}
return parents;
}
private static ClspClass getCls(ClassNode cls, Map<String, ClspClass> names) {
return getCls(cls.getRawName(), names);
}
private static ClspClass getCls(ArgType clsType, Map<String, ClspClass> names) {
return getCls(clsType.getObject(), names);
}
private static ClspClass getCls(String fullName, Map<String, ClspClass> names) {
ClspClass cls = names.get(fullName);
if (cls == null) {
LOG.debug("Class not found: {}", fullName);
}
return cls;
}
public 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")) {
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;
boolean clstReplaced = false;
ZipEntry entry = in.getNextEntry();
while (entry != null) {
String entryName = entry.getName();
ZipEntry copyEntry = new ZipEntry(entryName);
copyEntry.setLastModifiedTime(entry.getLastModifiedTime()); // preserve modified time
out.putNextEntry(copyEntry);
if (entryName.equals(clst)) {
save(out);
clstReplaced = true;
} else {
FileUtils.copyStream(in, out);
}
entry = in.getNextEntry();
}
} else {
throw new JadxRuntimeException("Unknown file format: " + name);
if (!clstReplaced) {
out.putNextEntry(new ZipEntry(clst));
save(out);
}
}
} else {
throw new JadxRuntimeException("Unknown file format: " + outputName);
}
}
private void save(OutputStream output) throws IOException {
DataOutputStream out = new DataOutputStream(output);
out.writeBytes(JADX_CLS_SET_HEADER);
out.writeByte(VERSION);
Map<String, ClspClass> names = new HashMap<>(classes.length);
out.writeInt(classes.length);
for (ClspClass cls : classes) {
String clsName = cls.getName();
writeString(out, clsName);
names.put(clsName, cls);
}
for (ClspClass cls : classes) {
writeArgTypesArray(out, cls.getParents(), names);
writeArgTypesList(out, cls.getTypeParameters(), names);
List<ClspMethod> methods = cls.getSortedMethodsList();
out.writeShort(methods.size());
for (ClspMethod method : methods) {
writeMethod(out, method, names);
}
}
int methodsCount = Stream.of(classes).mapToInt(c -> c.getMethodsMap().size()).sum();
LOG.info("Classes: {}, methods: {}, file size: {}B", classes.length, methodsCount, out.size());
}
private static void writeMethod(DataOutputStream out, ClspMethod method, Map<String, ClspClass> names) throws IOException {
MethodInfo methodInfo = method.getMethodInfo();
writeString(out, methodInfo.getName());
writeArgTypesList(out, methodInfo.getArgumentsTypes(), names);
writeArgType(out, methodInfo.getReturnType(), names);
writeArgTypesList(out, method.containsGenericArgs() ? method.getArgTypes() : Collections.emptyList(), names);
writeArgType(out, method.getReturnType(), names);
writeArgTypesList(out, method.getTypeParameters(), names);
out.writeBoolean(method.isVarArg());
writeArgTypesList(out, method.getThrows(), names);
}
private static void writeArgTypesList(DataOutputStream out, List<ArgType> list, Map<String, ClspClass> names) throws IOException {
int size = list.size();
writeUnsignedByte(out, size);
if (size != 0) {
for (ArgType type : list) {
writeArgType(out, type, names);
}
}
}
public void load(InputStream input) throws IOException, DecodeException {
try (DataInputStream in = new DataInputStream(input)) {
private static void writeArgTypesArray(DataOutputStream out, @Nullable ArgType[] arr, Map<String, ClspClass> names) throws IOException {
if (arr == null) {
out.writeByte(-1);
return;
}
int size = arr.length;
out.writeByte(size);
if (size != 0) {
for (ArgType type : arr) {
writeArgType(out, type, names);
}
}
}
private static void writeArgType(DataOutputStream out, ArgType argType, Map<String, ClspClass> names) throws IOException {
if (argType == null) {
out.writeByte(-1);
return;
}
if (argType.isPrimitive()) {
out.writeByte(TypeEnum.PRIMITIVE.ordinal());
out.writeByte(argType.getPrimitiveType().getShortName().charAt(0));
} else if (argType.getOuterType() != null) {
out.writeByte(TypeEnum.OUTER_GENERIC.ordinal());
writeArgType(out, argType.getOuterType(), names);
writeArgType(out, argType.getInnerType(), names);
} else if (argType.getWildcardType() != null) {
out.writeByte(TypeEnum.WILDCARD.ordinal());
ArgType.WildcardBound bound = argType.getWildcardBound();
out.writeByte(bound.getNum());
if (bound != ArgType.WildcardBound.UNBOUND) {
writeArgType(out, argType.getWildcardType(), names);
}
} else if (argType.isGeneric()) {
out.writeByte(TypeEnum.GENERIC.ordinal());
out.writeInt(getCls(argType, names).getId());
writeArgTypesList(out, argType.getGenericTypes(), names);
} else if (argType.isGenericType()) {
out.writeByte(TypeEnum.GENERIC_TYPE_VARIABLE.ordinal());
writeString(out, argType.getObject());
writeArgTypesList(out, argType.getExtendTypes(), names);
} else if (argType.isObject()) {
out.writeByte(TypeEnum.OBJECT.ordinal());
out.writeInt(getCls(argType, names).getId());
} else if (argType.isArray()) {
out.writeByte(TypeEnum.ARRAY.ordinal());
writeArgType(out, argType.getArrayElement(), names);
} else {
throw new JadxRuntimeException("Cannot save type: " + argType);
}
}
private void load(File input) throws IOException, DecodeException {
String name = input.getName();
if (name.endsWith(CLST_EXTENSION)) {
try (InputStream inputStream = new FileInputStream(input)) {
load(inputStream);
}
} else if (name.endsWith(".jar")) {
ZipSecurity.readZipEntries(input, (entry, in) -> {
if (entry.getName().endsWith(CLST_EXTENSION)) {
try {
load(in);
} catch (Exception e) {
throw new JadxRuntimeException("Failed to load jadx class set");
}
}
});
} else {
throw new JadxRuntimeException("Unknown file format: " + name);
}
}
private void load(InputStream input) throws IOException, DecodeException {
try (DataInputStream in = new DataInputStream(new BufferedInputStream(input))) {
byte[] header = new byte[JADX_CLS_SET_HEADER.length()];
int readHeaderLength = in.read(header);
int version = in.readByte();
@@ -184,31 +354,142 @@ public class ClsSet {
|| version != VERSION) {
throw new DecodeException("Wrong jadx class set header");
}
int count = in.readInt();
classes = new NClass[count];
for (int i = 0; i < count; i++) {
int clsCount = in.readInt();
classes = new ClspClass[clsCount];
for (int i = 0; i < clsCount; i++) {
String name = readString(in);
classes[i] = new NClass(name, i);
classes[i] = new ClspClass(ArgType.object(name), i);
}
for (int i = 0; i < count; i++) {
int pCount = in.readByte();
NClass[] parents = new NClass[pCount];
for (int j = 0; j < pCount; j++) {
parents[j] = classes[in.readInt()];
}
classes[i].setParents(parents);
for (int i = 0; i < clsCount; i++) {
ClspClass nClass = classes[i];
ClassInfo clsInfo = ClassInfo.fromType(root, nClass.getClsType());
nClass.setParents(readArgTypesArray(in));
nClass.setTypeParameters(readArgTypesList(in));
nClass.setMethods(readClsMethods(in, clsInfo));
}
}
}
private void writeString(DataOutputStream out, String name) throws IOException {
private List<ClspMethod> readClsMethods(DataInputStream in, ClassInfo clsInfo) throws IOException {
int mCount = in.readShort();
List<ClspMethod> methods = new ArrayList<>(mCount);
for (int j = 0; j < mCount; j++) {
methods.add(readMethod(in, clsInfo));
}
return methods;
}
private ClspMethod readMethod(DataInputStream in, ClassInfo clsInfo) throws IOException {
String name = readString(in);
List<ArgType> argTypes = readArgTypesList(in);
ArgType retType = readArgType(in);
List<ArgType> genericArgTypes = readArgTypesList(in);
if (genericArgTypes.isEmpty() || Objects.equals(genericArgTypes, argTypes)) {
genericArgTypes = argTypes;
}
ArgType genericRetType = readArgType(in);
if (Objects.equals(genericRetType, retType)) {
genericRetType = retType;
}
List<ArgType> typeParameters = readArgTypesList(in);
boolean varArgs = in.readBoolean();
List<ArgType> throwList = readArgTypesList(in);
MethodInfo methodInfo = MethodInfo.fromDetails(root, clsInfo, name, argTypes, retType);
return new ClspMethod(methodInfo,
genericArgTypes, genericRetType,
typeParameters, varArgs, throwList);
}
private List<ArgType> readArgTypesList(DataInputStream in) throws IOException {
int count = in.readByte();
if (count == 0) {
return Collections.emptyList();
}
List<ArgType> list = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
list.add(readArgType(in));
}
return list;
}
@Nullable
private ArgType[] readArgTypesArray(DataInputStream in) throws IOException {
int count = in.readByte();
if (count == -1) {
return null;
}
if (count == 0) {
return EMPTY_ARGTYPE_ARRAY;
}
ArgType[] arr = new ArgType[count];
for (int i = 0; i < count; i++) {
arr[i] = readArgType(in);
}
return arr;
}
private ArgType readArgType(DataInputStream in) throws IOException {
int ordinal = in.readByte();
if (ordinal == -1) {
return null;
}
if (ordinal >= TypeEnum.values().length) {
throw new JadxRuntimeException("Incorrect ordinal for type enum: " + ordinal);
}
switch (TypeEnum.values()[ordinal]) {
case WILDCARD:
ArgType.WildcardBound bound = ArgType.WildcardBound.getByNum(in.readByte());
if (bound == ArgType.WildcardBound.UNBOUND) {
return ArgType.WILDCARD;
}
ArgType objType = readArgType(in);
return ArgType.wildcard(objType, bound);
case OUTER_GENERIC:
ArgType outerType = readArgType(in);
ArgType innerType = readArgType(in);
return ArgType.outerGeneric(outerType, innerType);
case GENERIC:
ArgType clsType = classes[in.readInt()].getClsType();
return ArgType.generic(clsType, readArgTypesList(in));
case GENERIC_TYPE_VARIABLE:
String typeVar = readString(in);
List<ArgType> extendTypes = readArgTypesList(in);
return ArgType.genericType(typeVar, extendTypes);
case OBJECT:
return classes[in.readInt()].getClsType();
case ARRAY:
return ArgType.array(readArgType(in));
case PRIMITIVE:
char shortName = (char) in.readByte();
return ArgType.parse(shortName);
default:
throw new JadxRuntimeException("Unsupported Arg Type: " + ordinal);
}
}
private static void writeString(DataOutputStream out, String name) throws IOException {
byte[] bytes = name.getBytes(STRING_CHARSET);
out.writeByte(bytes.length);
int len = bytes.length;
if (len >= 0xFF) {
throw new JadxRuntimeException("String is too long: " + name);
}
writeUnsignedByte(out, bytes.length);
out.write(bytes);
}
private static String readString(DataInputStream in) throws IOException {
int len = in.readByte();
int len = readUnsignedByte(in);
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) {
@@ -222,12 +503,23 @@ public class ClsSet {
return new String(bytes, STRING_CHARSET);
}
private static void writeUnsignedByte(DataOutputStream out, int value) throws IOException {
if (value < 0 || value >= 0xFF) {
throw new JadxRuntimeException("Unsigned byte value is too big: " + value);
}
out.writeByte(value);
}
private static int readUnsignedByte(DataInputStream in) throws IOException {
return ((int) in.readByte()) & 0xFF;
}
public int getClassesCount() {
return classes.length;
}
public void addToMap(Map<String, NClass> nameMap) {
for (NClass cls : classes) {
public void addToMap(Map<String, ClspClass> nameMap) {
for (ClspClass cls : classes) {
nameMap.put(cls.getName(), cls);
}
}
@@ -0,0 +1,100 @@
package jadx.core.clsp;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import jadx.core.dex.instructions.args.ArgType;
/**
* Class node in classpath graph
*/
public class ClspClass {
private final ArgType clsType;
private final int id;
private ArgType[] parents;
private Map<String, ClspMethod> methodsMap = Collections.emptyMap();
private List<ArgType> typeParameters = Collections.emptyList();
public ClspClass(ArgType clsType, int id) {
this.clsType = clsType;
this.id = id;
}
public String getName() {
return clsType.getObject();
}
public ArgType getClsType() {
return clsType;
}
public int getId() {
return id;
}
public ArgType[] getParents() {
return parents;
}
public void setParents(ArgType[] parents) {
this.parents = parents;
}
public Map<String, ClspMethod> getMethodsMap() {
return methodsMap;
}
public List<ClspMethod> getSortedMethodsList() {
List<ClspMethod> list = new ArrayList<>(methodsMap.size());
list.addAll(methodsMap.values());
Collections.sort(list);
return list;
}
public void setMethodsMap(Map<String, ClspMethod> methodsMap) {
this.methodsMap = Objects.requireNonNull(methodsMap);
}
public void setMethods(List<ClspMethod> methods) {
Map<String, ClspMethod> map = new HashMap<>(methods.size());
for (ClspMethod mth : methods) {
map.put(mth.getMethodInfo().getShortId(), mth);
}
setMethodsMap(map);
}
public List<ArgType> getTypeParameters() {
return typeParameters;
}
public void setTypeParameters(List<ArgType> typeParameters) {
this.typeParameters = typeParameters;
}
@Override
public int hashCode() {
return clsType.hashCode();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ClspClass nClass = (ClspClass) o;
return clsType.equals(nClass.clsType);
}
@Override
public String toString() {
return clsType.toString();
}
}
@@ -10,27 +10,38 @@ import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.IMethodDetails;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxRuntimeException;
/**
* Classes hierarchy graph
* Classes hierarchy graph with methods additional info
*/
public class ClspGraph {
private static final Logger LOG = LoggerFactory.getLogger(ClspGraph.class);
private final Map<String, Set<String>> ancestorCache = Collections.synchronizedMap(new WeakHashMap<String, Set<String>>());
private Map<String, NClass> nameMap;
private final RootNode root;
private final Map<String, Set<String>> superTypesCache = Collections.synchronizedMap(new WeakHashMap<>());
private Map<String, ClspClass> nameMap;
private final Set<String> missingClasses = new HashSet<>();
public ClspGraph(RootNode rootNode) {
this.root = rootNode;
}
public void load() throws IOException, DecodeException {
ClsSet set = new ClsSet();
set.load();
ClsSet set = new ClsSet(root);
set.loadFromClstFile();
addClasspath(set);
}
@@ -47,34 +58,78 @@ public class ClspGraph {
if (nameMap == null) {
throw new JadxRuntimeException("Classpath must be loaded first");
}
int size = classes.size();
NClass[] nClasses = new NClass[size];
int k = 0;
for (ClassNode cls : classes) {
nClasses[k++] = addClass(cls);
}
for (int i = 0; i < size; i++) {
nClasses[i].setParents(ClsSet.makeParentsArray(classes.get(i), nameMap));
addClass(cls);
}
}
private NClass addClass(ClassNode cls) {
String rawName = cls.getRawName();
NClass nClass = new NClass(rawName, -1);
nameMap.put(rawName, nClass);
return nClass;
public boolean isClsKnown(String fullName) {
return nameMap.containsKey(fullName);
}
public ClspClass getClsDetails(ArgType type) {
return nameMap.get(type.getObject());
}
@Nullable
public IMethodDetails getMethodDetails(MethodInfo methodInfo) {
ClspClass cls = nameMap.get(methodInfo.getDeclClass().getRawName());
if (cls == null) {
return null;
}
ClspMethod clspMethod = getMethodFromClass(cls, methodInfo);
if (clspMethod != null) {
return clspMethod;
}
// deep search
for (ArgType parent : cls.getParents()) {
ClspClass clspParent = getClspClass(parent);
if (clspParent != null) {
ClspMethod methodFromParent = getMethodFromClass(clspParent, methodInfo);
if (methodFromParent != null) {
return methodFromParent;
}
}
}
// all other methods in known ClspClass are 'simple'
return new SimpleMethodDetails(methodInfo);
}
private ClspMethod getMethodFromClass(ClspClass cls, MethodInfo methodInfo) {
return cls.getMethodsMap().get(methodInfo.getShortId());
}
private void addClass(ClassNode cls) {
ArgType clsType = cls.getClassInfo().getType();
String rawName = clsType.getObject();
ClspClass clspClass = new ClspClass(clsType, -1);
clspClass.setParents(ClsSet.makeParentsArray(cls));
nameMap.put(rawName, clspClass);
}
/**
* @return {@code clsName} instanceof {@code implClsName}
*/
public boolean isImplements(String clsName, String implClsName) {
Set<String> anc = getAncestors(clsName);
Set<String> anc = getSuperTypes(clsName);
return anc.contains(implClsName);
}
public List<String> getImplementations(String clsName) {
List<String> list = new ArrayList<>();
for (String cls : nameMap.keySet()) {
if (isImplements(cls, clsName)) {
list.add(cls);
}
}
return list;
}
public String getCommonAncestor(String clsName, String implClsName) {
if (clsName.equals(implClsName)) {
return clsName;
}
NClass cls = nameMap.get(implClsName);
ClspClass cls = nameMap.get(implClsName);
if (cls == null) {
missingClasses.add(clsName);
return null;
@@ -82,52 +137,79 @@ public class ClspGraph {
if (isImplements(clsName, implClsName)) {
return implClsName;
}
Set<String> anc = getAncestors(clsName);
Set<String> anc = getSuperTypes(clsName);
return searchCommonParent(anc, cls);
}
private String searchCommonParent(Set<String> anc, NClass cls) {
for (NClass p : cls.getParents()) {
String name = p.getName();
private String searchCommonParent(Set<String> anc, ClspClass cls) {
for (ArgType p : cls.getParents()) {
String name = p.getObject();
if (anc.contains(name)) {
return name;
}
String r = searchCommonParent(anc, p);
if (r != null) {
return r;
ClspClass nCls = getClspClass(p);
if (nCls != null) {
String r = searchCommonParent(anc, nCls);
if (r != null) {
return r;
}
}
}
return null;
}
private Set<String> getAncestors(String clsName) {
Set<String> result = ancestorCache.get(clsName);
if (result != null) {
return result;
public Set<String> getSuperTypes(String clsName) {
Set<String> fromCache = superTypesCache.get(clsName);
if (fromCache != null) {
return fromCache;
}
NClass cls = nameMap.get(clsName);
ClspClass cls = nameMap.get(clsName);
if (cls == null) {
missingClasses.add(clsName);
return Collections.emptySet();
}
result = new HashSet<>();
addAncestorsNames(cls, result);
Set<String> result = new HashSet<>();
addSuperTypes(cls, result);
return putInSuperTypesCache(clsName, result);
}
@NotNull
private Set<String> putInSuperTypesCache(String clsName, Set<String> result) {
if (result.isEmpty()) {
result = Collections.emptySet();
Set<String> empty = Collections.emptySet();
superTypesCache.put(clsName, result);
return empty;
}
ancestorCache.put(clsName, result);
superTypesCache.put(clsName, result);
return result;
}
private void addAncestorsNames(NClass cls, Set<String> result) {
boolean isNew = result.add(cls.getName());
if (isNew) {
for (NClass p : cls.getParents()) {
addAncestorsNames(p, result);
private void addSuperTypes(ClspClass cls, Set<String> result) {
for (ArgType parentType : cls.getParents()) {
if (parentType == null) {
continue;
}
ClspClass parentCls = getClspClass(parentType);
if (parentCls != null) {
boolean isNew = result.add(parentCls.getName());
if (isNew) {
addSuperTypes(parentCls, result);
}
}
}
}
@Nullable
private ClspClass getClspClass(ArgType clsType) {
ClspClass clspClass = nameMap.get(clsType.getObject());
if (clspClass == null) {
if (LOG.isDebugEnabled()) {
LOG.debug("External class not found: {}", clsType.getObject());
}
}
return clspClass;
}
public void printMissingClasses() {
int count = missingClasses.size();
if (count == 0) {
@@ -0,0 +1,121 @@
package jadx.core.clsp;
import java.util.List;
import java.util.Objects;
import org.jetbrains.annotations.NotNull;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.IMethodDetails;
import jadx.core.utils.Utils;
/**
* Method node in classpath graph.
*/
public class ClspMethod implements IMethodDetails, Comparable<ClspMethod> {
private final MethodInfo methodInfo;
private final List<ArgType> argTypes;
private final ArgType returnType;
private final List<ArgType> typeParameters;
private final List<ArgType> throwList;
private final boolean varArg;
public ClspMethod(MethodInfo methodInfo,
List<ArgType> argTypes, ArgType returnType,
List<ArgType> typeParameters,
boolean varArgs, List<ArgType> throwList) {
this.methodInfo = methodInfo;
this.argTypes = argTypes;
this.returnType = returnType;
this.typeParameters = typeParameters;
this.throwList = throwList;
this.varArg = varArgs;
}
@Override
public MethodInfo getMethodInfo() {
return methodInfo;
}
@Override
public ArgType getReturnType() {
return returnType;
}
@Override
public List<ArgType> getArgTypes() {
return argTypes;
}
public boolean containsGenericArgs() {
return !Objects.equals(argTypes, methodInfo.getArgumentsTypes());
}
public int getArgsCount() {
return argTypes.size();
}
@Override
public List<ArgType> getTypeParameters() {
return typeParameters;
}
@Override
public List<ArgType> getThrows() {
return throwList;
}
@Override
public boolean isVarArg() {
return varArg;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof ClspMethod)) {
return false;
}
ClspMethod other = (ClspMethod) o;
return methodInfo.equals(other.methodInfo);
}
@Override
public int hashCode() {
return methodInfo.hashCode();
}
@Override
public int compareTo(@NotNull ClspMethod other) {
return this.methodInfo.compareTo(other.methodInfo);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("ClspMth{");
if (Utils.notEmpty(getTypeParameters())) {
sb.append('<');
sb.append(Utils.listToString(getTypeParameters()));
sb.append("> ");
}
sb.append(getMethodInfo().getFullName());
sb.append('(');
sb.append(Utils.listToString(getArgTypes()));
sb.append("):");
sb.append(getReturnType());
if (isVarArg()) {
sb.append(" VARARG");
}
List<ArgType> throwsList = getThrows();
if (Utils.notEmpty(throwsList)) {
sb.append(" throws ").append(Utils.listToString(throwsList));
}
sb.append('}');
return sb.toString();
}
}
@@ -1,73 +0,0 @@
package jadx.core.clsp;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.files.InputFile;
/**
* Utility class for convert dex or jar to jadx classes set (.jcst)
*/
public class ConvertToClsSet {
private static final Logger LOG = LoggerFactory.getLogger(ConvertToClsSet.class);
public static void usage() {
LOG.info("<output .jcst or .jar file> <several input dex or jar files> ");
}
public static void main(String[] args) throws IOException, DecodeException {
if (args.length < 2) {
usage();
System.exit(1);
}
File output = new File(args[0]);
List<InputFile> inputFiles = new ArrayList<>(args.length - 1);
for (int i = 1; i < args.length; i++) {
File f = new File(args[i]);
if (f.isDirectory()) {
addFilesFromDirectory(f, inputFiles);
} else {
InputFile.addFilesFrom(f, inputFiles);
}
}
for (InputFile inputFile : inputFiles) {
LOG.info("Loaded: {}", inputFile.getFile());
}
RootNode root = new RootNode(new JadxArgs());
root.load(inputFiles);
ClsSet set = new ClsSet();
set.load(root);
set.save(output);
LOG.info("Output: {}", output);
LOG.info("done");
}
private static void addFilesFromDirectory(File dir, List<InputFile> inputFiles) {
File[] files = dir.listFiles();
if (files == null) {
return;
}
for (File file : files) {
if (file.isDirectory()) {
addFilesFromDirectory(file, inputFiles);
} else {
try {
InputFile.addFilesFrom(file, inputFiles);
} catch (Exception e) {
LOG.warn("Skip file: {}, load error: {}", file, e.getMessage());
}
}
}
}
}
@@ -1,58 +0,0 @@
package jadx.core.clsp;
/**
* Class node in classpath graph
*/
public class NClass {
private final String name;
private NClass[] parents;
private int id;
public NClass(String name, int id) {
this.name = name;
this.id = id;
}
public String getName() {
return name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public NClass[] getParents() {
return parents;
}
public void setParents(NClass[] parents) {
this.parents = parents;
}
@Override
public int hashCode() {
return name.hashCode();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
NClass nClass = (NClass) o;
return name.equals(nClass.name);
}
@Override
public String toString() {
return name;
}
}
@@ -0,0 +1,52 @@
package jadx.core.clsp;
import java.util.Collections;
import java.util.List;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.IMethodDetails;
public class SimpleMethodDetails implements IMethodDetails {
private final MethodInfo methodInfo;
public SimpleMethodDetails(MethodInfo methodInfo) {
this.methodInfo = methodInfo;
}
@Override
public MethodInfo getMethodInfo() {
return methodInfo;
}
@Override
public ArgType getReturnType() {
return methodInfo.getReturnType();
}
@Override
public List<ArgType> getArgTypes() {
return methodInfo.getArgumentsTypes();
}
@Override
public List<ArgType> getTypeParameters() {
return Collections.emptyList();
}
@Override
public List<ArgType> getThrows() {
return Collections.emptyList();
}
@Override
public boolean isVarArg() {
return false;
}
@Override
public String toString() {
return "SimpleMethodDetails{" + methodInfo + '}';
}
}
@@ -5,10 +5,14 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.jetbrains.annotations.Nullable;
import jadx.api.plugins.input.data.IFieldData;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.api.plugins.input.data.annotations.IAnnotation;
import jadx.core.Consts;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttributeNode;
import jadx.core.dex.attributes.annotations.Annotation;
import jadx.core.dex.attributes.annotations.AnnotationsList;
import jadx.core.dex.attributes.annotations.MethodParameters;
import jadx.core.dex.info.FieldInfo;
@@ -16,6 +20,7 @@ import jadx.core.dex.instructions.args.ArgType;
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.StringUtils;
import jadx.core.utils.exceptions.JadxRuntimeException;
@@ -50,7 +55,7 @@ public class AnnotationGen {
if (aList == null || aList.isEmpty()) {
return;
}
for (Annotation a : aList.getAll()) {
for (IAnnotation a : aList.getAll()) {
formatAnnotation(code, a);
code.add(' ');
}
@@ -61,50 +66,61 @@ public class AnnotationGen {
if (aList == null || aList.isEmpty()) {
return;
}
for (Annotation a : aList.getAll()) {
for (IAnnotation 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);
}
}
}
private void formatAnnotation(CodeWriter code, Annotation a) {
private void formatAnnotation(CodeWriter code, IAnnotation a) {
code.add('@');
classGen.useType(code, a.getType());
Map<String, Object> vl = a.getValues();
ClassNode annCls = cls.root().resolveClass(a.getAnnotationClass());
if (annCls != null) {
classGen.useClass(code, annCls);
} else {
classGen.useClass(code, a.getAnnotationClass());
}
Map<String, EncodedValue> 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, EncodedValue>> it = vl.entrySet().iterator(); it.hasNext();) {
Entry<String, EncodedValue> 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(cls.root(), code, e.getValue());
if (it.hasNext()) {
code.add(", ");
}
}
code.add(')');
}
}
@SuppressWarnings("unchecked")
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;
}
public void addThrows(MethodNode mth, CodeWriter code) {
Annotation an = mth.getAnnotation(Consts.DALVIK_THROWS);
if (an != null) {
Object exs = an.getDefaultValue();
List<ArgType> throwList = mth.getThrows();
if (!throwList.isEmpty()) {
code.add(" throws ");
for (Iterator<ArgType> it = ((List<ArgType>) exs).iterator(); it.hasNext(); ) {
for (Iterator<ArgType> it = throwList.iterator(); it.hasNext();) {
ArgType ex = it.next();
classGen.useType(code, ex);
if (it.hasNext()) {
@@ -114,66 +130,97 @@ public class AnnotationGen {
}
}
public Object getAnnotationDefaultValue(String name) {
Annotation an = cls.getAnnotation(Consts.DALVIK_ANNOTATION_DEFAULT);
public EncodedValue getAnnotationDefaultValue(String name) {
IAnnotation an = cls.getAnnotation(Consts.DALVIK_ANNOTATION_DEFAULT);
if (an != null) {
Annotation defAnnotation = (Annotation) an.getDefaultValue();
return defAnnotation.getValues().get(name);
EncodedValue defValue = an.getDefaultValue();
if (defValue != null) {
IAnnotation defAnnotation = (IAnnotation) defValue.getValue();
return defAnnotation.getValues().get(name);
}
}
return null;
}
// TODO: refactor this boilerplate code
public void encodeValue(CodeWriter code, Object val) {
if (val == null) {
public void encodeValue(RootNode root, CodeWriter code, EncodedValue encodedValue) {
if (encodedValue == null) {
code.add("null");
return;
}
if (val instanceof String) {
code.add(getStringUtils().unescapeString((String) val));
} else if (val instanceof Integer) {
code.add(TypeGen.formatInteger((Integer) val));
} else if (val instanceof Character) {
code.add(getStringUtils().unescapeChar((Character) val));
} else if (val instanceof Boolean) {
code.add(Boolean.TRUE.equals(val) ? "true" : "false");
} else if (val instanceof Float) {
code.add(TypeGen.formatFloat((Float) val));
} else if (val instanceof Double) {
code.add(TypeGen.formatDouble((Double) val));
} else if (val instanceof Long) {
code.add(TypeGen.formatLong((Long) val));
} else if (val instanceof Short) {
code.add(TypeGen.formatShort((Short) val));
} else if (val instanceof Byte) {
code.add(TypeGen.formatByte((Byte) val));
} else if (val instanceof ArgType) {
classGen.useType(code, (ArgType) val);
code.add(".class");
} else if (val instanceof FieldInfo) {
// must be a static field
FieldInfo field = (FieldInfo) val;
InsnGen.makeStaticFieldAccess(code, field, classGen);
} else if (val instanceof Iterable) {
code.add('{');
Iterator<?> it = ((Iterable) val).iterator();
while (it.hasNext()) {
Object obj = it.next();
encodeValue(code, obj);
if (it.hasNext()) {
code.add(", ");
Object value = encodedValue.getValue();
switch (encodedValue.getType()) {
case ENCODED_NULL:
code.add("null");
break;
case ENCODED_BOOLEAN:
code.add(Boolean.TRUE.equals(value) ? "true" : "false");
break;
case ENCODED_BYTE:
code.add(TypeGen.formatByte((Byte) value, false));
break;
case ENCODED_SHORT:
code.add(TypeGen.formatShort((Short) value, false));
break;
case ENCODED_CHAR:
code.add(getStringUtils().unescapeChar((Character) value));
break;
case ENCODED_INT:
code.add(TypeGen.formatInteger((Integer) value, false));
break;
case ENCODED_LONG:
code.add(TypeGen.formatLong((Long) value, false));
break;
case ENCODED_FLOAT:
code.add(TypeGen.formatFloat((Float) value));
break;
case ENCODED_DOUBLE:
code.add(TypeGen.formatDouble((Double) value));
break;
case ENCODED_STRING:
code.add(getStringUtils().unescapeString((String) value));
break;
case ENCODED_TYPE:
classGen.useType(code, ArgType.parse((String) value));
code.add(".class");
break;
case ENCODED_ENUM:
case ENCODED_FIELD:
// must be a static field
if (value instanceof IFieldData) {
FieldInfo field = FieldInfo.fromData(root, (IFieldData) value);
InsnGen.makeStaticFieldAccess(code, field, classGen);
} else if (value instanceof FieldInfo) {
InsnGen.makeStaticFieldAccess(code, (FieldInfo) value, classGen);
} else {
throw new JadxRuntimeException("Unexpected field type class: " + value.getClass());
}
}
code.add('}');
} else if (val instanceof Annotation) {
formatAnnotation(code, (Annotation) val);
} else {
// TODO: also can be method values
throw new JadxRuntimeException("Can't decode value: " + val + " (" + val.getClass() + ")");
break;
case ENCODED_METHOD:
// TODO
break;
case ENCODED_ARRAY:
code.add('{');
Iterator<?> it = ((Iterable<?>) value).iterator();
while (it.hasNext()) {
EncodedValue v = (EncodedValue) it.next();
encodeValue(cls.root(), code, v);
if (it.hasNext()) {
code.add(", ");
}
}
code.add('}');
break;
case ENCODED_ANNOTATION:
formatAnnotation(code, (IAnnotation) value);
break;
default:
throw new JadxRuntimeException("Can't decode value: " + encodedValue.getType() + " (" + encodedValue + ')');
}
}
private StringUtils getStringUtils() {
return cls.dex().root().getStringUtils();
return cls.root().getStringUtils();
}
}
@@ -1,42 +1,49 @@
package jadx.core.codegen;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Stream;
import com.android.dx.rop.code.AccessFlags;
import org.jetbrains.annotations.Nullable;
import jadx.api.ICodeInfo;
import jadx.api.JadxArgs;
import jadx.api.plugins.input.data.AccessFlags;
import jadx.api.plugins.input.data.annotations.EncodedType;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.core.Consts;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.AttrNode;
import jadx.core.dex.attributes.FieldInitAttr;
import jadx.core.dex.attributes.FieldInitAttr.InitType;
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.attributes.nodes.SkipMethodArgsAttr;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.PrimitiveType;
import jadx.core.dex.instructions.mods.ConstructorInsn;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.parser.FieldInitAttr;
import jadx.core.dex.nodes.parser.FieldInitAttr.InitType;
import jadx.core.dex.nodes.RootNode;
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 {
@@ -50,6 +57,8 @@ public class ClassGen {
private final Set<ClassInfo> imports = new HashSet<>();
private int clsDeclLine;
private boolean bodyGenStarted;
public ClassGen(ClassNode cls, JadxArgs jadxArgs) {
this(cls, null, jadxArgs.isUseImports(), jadxArgs.isFallbackMode(), jadxArgs.isShowInconsistentCode());
}
@@ -72,7 +81,7 @@ public class ClassGen {
return cls;
}
public CodeWriter makeClass() throws CodegenException {
public ICodeInfo makeClass() throws CodegenException {
CodeWriter clsBody = new CodeWriter();
addClassCode(clsBody);
@@ -83,31 +92,33 @@ public class ClassGen {
}
int importsCount = imports.size();
if (importsCount != 0) {
List<String> sortImports = new ArrayList<>(importsCount);
for (ClassInfo ic : imports) {
sortImports.add(ic.getAlias().getFullName());
}
Collections.sort(sortImports);
for (String imp : sortImports) {
clsCode.startLine("import ").add(imp).add(';');
}
List<ClassInfo> sortedImports = new ArrayList<>(imports);
sortedImports.sort(Comparator.comparing(ClassInfo::getAliasFullName));
sortedImports.forEach(classInfo -> {
clsCode.startLine("import ");
ClassNode classNode = cls.root().resolveClass(classInfo);
if (classNode != null) {
clsCode.attachAnnotation(classNode);
}
clsCode.add(classInfo.getAliasFullName());
clsCode.add(';');
});
clsCode.newLine();
sortImports.clear();
imports.clear();
}
clsCode.add(clsBody);
return clsCode;
return clsCode.finish();
}
public void addClassCode(CodeWriter code) throws CodegenException {
if (cls.contains(AFlag.DONT_GENERATE)) {
return;
}
if (cls.contains(AFlag.INCONSISTENT_CODE)) {
code.startLine("// jadx: inconsistent code");
if (Consts.DEBUG_USAGE) {
addClassUsageInfo(code, cls);
}
CodeGenUtils.addComments(code, cls);
insertDecompilationProblems(code, cls);
addClassDeclaration(code);
addClassBody(code);
}
@@ -115,23 +126,24 @@ public class ClassGen {
public void addClassDeclaration(CodeWriter clsCode) {
AccessInfo af = cls.getAccessFlags();
if (af.isInterface()) {
af = af.remove(AccessFlags.ACC_ABSTRACT)
.remove(AccessFlags.ACC_STATIC);
af = af.remove(AccessFlags.ABSTRACT)
.remove(AccessFlags.STATIC);
} else if (af.isEnum()) {
af = af.remove(AccessFlags.ACC_FINAL)
.remove(AccessFlags.ACC_ABSTRACT)
.remove(AccessFlags.ACC_STATIC);
af = af.remove(AccessFlags.FINAL)
.remove(AccessFlags.ABSTRACT)
.remove(AccessFlags.STATIC);
}
// 'static' and 'private' modifier not allowed for top classes (not inner)
if (!cls.getAlias().isInner()) {
af = af.remove(AccessFlags.ACC_STATIC).remove(AccessFlags.ACC_PRIVATE);
if (!cls.getClassInfo().isInner()) {
af = af.remove(AccessFlags.STATIC).remove(AccessFlags.PRIVATE);
}
annotationGen.addForClass(clsCode);
insertSourceFileInfo(clsCode, cls);
insertRenameInfo(clsCode, cls);
clsCode.startLine(af.makeString());
CodeGenUtils.addSourceFileInfo(clsCode, cls);
clsCode.startLineWithNum(cls.getSourceLine());
clsCode.add(af.makeString());
if (af.isInterface()) {
if (af.isAnnotation()) {
clsCode.add('@');
@@ -143,15 +155,15 @@ public class ClassGen {
clsCode.add("class ");
}
clsCode.attachDefinition(cls);
clsCode.add(cls.getShortName());
clsCode.add(cls.getClassInfo().getAliasShortName());
addGenericMap(clsCode, cls.getGenericMap());
addGenericTypeParameters(clsCode, cls.getGenericTypeParameters(), true);
clsCode.add(' ');
ArgType sup = cls.getSuperClass();
if (sup != null
&& !sup.equals(ArgType.OBJECT)
&& !sup.getObject().equals(ArgType.ENUM.getObject())) {
&& !cls.isEnum()) {
clsCode.add("extends ");
useClass(clsCode, sup);
clsCode.add(' ');
@@ -163,7 +175,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()) {
@@ -176,31 +188,35 @@ public class ClassGen {
}
}
public boolean addGenericMap(CodeWriter code, Map<ArgType, List<ArgType>> gmap) {
if (gmap == null || gmap.isEmpty()) {
public boolean addGenericTypeParameters(CodeWriter code, List<ArgType> generics, boolean classDeclaration) {
if (generics == null || generics.isEmpty()) {
return false;
}
code.add('<');
int i = 0;
for (Entry<ArgType, List<ArgType>> e : gmap.entrySet()) {
ArgType type = e.getKey();
List<ArgType> list = e.getValue();
for (ArgType genericInfo : generics) {
if (i != 0) {
code.add(", ");
}
if (type.isGenericType()) {
code.add(type.getObject());
if (genericInfo.isGenericType()) {
code.add(genericInfo.getObject());
} else {
useClass(code, type);
useClass(code, genericInfo);
}
List<ArgType> list = genericInfo.getExtendTypes();
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(" & ");
@@ -214,26 +230,50 @@ public class ClassGen {
}
public void addClassBody(CodeWriter clsCode) throws CodegenException {
addClassBody(clsCode, false);
}
/**
* @param printClassName allows to print the original class name as comment (e.g. for inlined
* classes)
*/
public void addClassBody(CodeWriter clsCode, boolean printClassName) throws CodegenException {
clsCode.add('{');
setBodyGenStarted(true);
clsDeclLine = clsCode.getLine();
clsCode.incIndent();
if (printClassName) {
clsCode.startLine();
clsCode.add("/* class " + cls.getFullName() + " */");
}
addFields(clsCode);
addInnerClasses(clsCode, cls);
addMethods(clsCode);
addInnerClsAndMethods(clsCode);
clsCode.decIndent();
clsCode.startLine('}');
}
private void addInnerClasses(CodeWriter code, ClassNode cls) throws CodegenException {
for (ClassNode innerCls : cls.getInnerClasses()) {
if (innerCls.contains(AFlag.DONT_GENERATE)
|| innerCls.contains(AFlag.ANONYMOUS_CLASS)) {
continue;
}
private void addInnerClsAndMethods(CodeWriter clsCode) {
Stream.of(cls.getInnerClasses(), cls.getMethods())
.flatMap(Collection::stream)
.filter(node -> !node.contains(AFlag.DONT_GENERATE))
.sorted(Comparator.comparingInt(LineAttrNode::getSourceLine))
.forEach(node -> {
if (node instanceof ClassNode) {
addInnerClass(clsCode, (ClassNode) node);
} else {
addMethod(clsCode, (MethodNode) node);
}
});
}
private void addInnerClass(CodeWriter code, ClassNode innerCls) {
try {
ClassGen inClGen = new ClassGen(innerCls, getParentGen());
code.newLine();
inClGen.addClassCode(code);
imports.addAll(inClGen.getImports());
} catch (Exception e) {
innerCls.addError("Inner class code generation error", e);
}
}
@@ -246,32 +286,24 @@ public class ClassGen {
return false;
}
private void addMethods(CodeWriter code) {
List<MethodNode> methods = sortMethodsByLine(cls.getMethods());
for (MethodNode mth : methods) {
if (mth.contains(AFlag.DONT_GENERATE)) {
continue;
}
if (code.getLine() != clsDeclLine) {
code.newLine();
}
int savedIndent = code.getIndent();
try {
addMethod(code, mth);
} catch (Exception e) {
code.newLine().add("/*");
code.newLine().add(ErrorsCounter.methodError(mth, "Method generation error", e));
code.newLine().add(Utils.getStackTrace(e));
code.newLine().add("*/");
code.setIndent(savedIndent);
}
private void addMethod(CodeWriter code, MethodNode mth) {
if (code.getLine() != clsDeclLine) {
code.newLine();
}
int savedIndent = code.getIndent();
try {
addMethodCode(code, mth);
} catch (Exception e) {
if (mth.getParentClass().getTopParentClass().contains(AFlag.RESTART_CODEGEN)) {
throw new JadxRuntimeException("Method generation error", e);
}
code.newLine().add("/*");
code.newLine().addMultiLine(ErrorsCounter.error(mth, "Method generation error", e));
Utils.appendStackTrace(code, e);
code.newLine().add("*/");
code.setIndent(savedIndent);
mth.addError("Method generation error: " + e.getMessage(), e);
}
}
private static List<MethodNode> sortMethodsByLine(List<MethodNode> methods) {
List<MethodNode> out = new ArrayList<>(methods);
out.sort(Comparator.comparingInt(LineAttrNode::getSourceLine));
return out;
}
private boolean isMethodsPresents() {
@@ -283,30 +315,21 @@ public class ClassGen {
return false;
}
private void addMethod(CodeWriter code, MethodNode mth) throws CodegenException {
if (mth.getAccessFlags().isAbstract() || mth.getAccessFlags().isNative()) {
public void addMethodCode(CodeWriter code, MethodNode mth) throws CodegenException {
CodeGenUtils.addComments(code, mth);
if (mth.isNoCode()) {
MethodGen mthGen = new MethodGen(this, mth);
mthGen.addDefinition(code);
if (cls.getAccessFlags().isAnnotation()) {
Object def = annotationGen.getAnnotationDefaultValue(mth.getName());
if (def != null) {
code.add(" default ");
annotationGen.encodeValue(code, def);
}
}
code.add(';');
} else {
insertDecompilationProblems(code, mth);
boolean badCode = mth.contains(AFlag.INCONSISTENT_CODE);
if (badCode) {
if (showInconsistentCode) {
code.startLine("/* Code decompiled incorrectly, please refer to instructions dump. */");
mth.remove(AFlag.INCONSISTENT_CODE);
badCode = false;
}
if (badCode && showInconsistentCode) {
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);
@@ -316,22 +339,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, MethodNode mth) {
List<JadxError> errors = mth.getAll(AType.JADX_ERROR);
List<JadxWarn> warns = mth.getAll(AType.JADX_WARN);
public void insertDecompilationProblems(CodeWriter code, AttrNode node) {
List<JadxError> errors = node.getAll(AType.JADX_ERROR);
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) {
@@ -342,45 +359,57 @@ public class ClassGen {
code.add("*/");
});
}
List<String> warns = node.getAll(AType.JADX_WARN);
if (!warns.isEmpty()) {
warns.forEach(warn -> code.startLine("/* JADX WARNING: ").add(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;
}
annotationGen.addForField(code, f);
if (f.getFieldInfo().isRenamed()) {
code.startLine("/* renamed from: ").add(f.getName()).add(" */");
}
code.startLine(f.getAccessFlags().makeString());
useType(code, f.getType());
code.add(' ');
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());
}
}
}
code.add(';');
addField(code, f);
}
}
public void addField(CodeWriter code, FieldNode f) {
if (f.contains(AFlag.DONT_GENERATE)) {
return;
}
if (Consts.DEBUG_USAGE) {
addFieldUsageInfo(code, f);
}
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.getValueType() == InitType.CONST) {
EncodedValue encodedValue = fv.getEncodedValue();
if (encodedValue.getType() == EncodedType.ENCODED_NULL) {
code.add(TypeGen.literalToString(0, f.getType(), cls, fallback));
} else {
annotationGen.encodeValue(cls.root(), code, encodedValue);
}
} else if (fv.getValueType() == InitType.INSN) {
InsnGen insnGen = makeInsnGen(fv.getInsnMth());
addInsnBody(insnGen, code, fv.getInsn());
}
}
code.add(';');
}
private boolean isFieldsPresents() {
for (FieldNode field : cls.getFields()) {
if (!field.contains(AFlag.DONT_GENERATE)) {
@@ -396,16 +425,17 @@ 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();
if (constrInsn.getArgsCount() > f.getStartArg()) {
MethodNode callMth = cls.root().resolveMethod(constrInsn.getCallMth());
int skipCount = getEnumCtrSkipArgsCount(callMth);
if (constrInsn.getArgsCount() > skipCount) {
if (igen == null) {
igen = makeInsnGen(enumFields.getStaticMethod());
}
MethodNode callMth = cls.dex().resolveMethod(constrInsn.getCallMth());
igen.generateMethodArguments(code, constrInsn, f.getStartArg(), callMth);
igen.generateMethodArguments(code, constrInsn, 0, callMth);
}
if (f.getCls() != null) {
code.add(' ');
@@ -426,6 +456,16 @@ public class ClassGen {
}
}
private int getEnumCtrSkipArgsCount(@Nullable MethodNode callMth) {
if (callMth != null) {
SkipMethodArgsAttr skipArgsAttr = callMth.get(AType.SKIP_MTH_ARGS);
if (skipArgsAttr != null) {
return skipArgsAttr.getSkipCount();
}
}
return 0;
}
private InsnGen makeInsnGen(MethodNode mth) {
MethodGen mthGen = new MethodGen(this, mth);
return new InsnGen(mthGen, false);
@@ -435,7 +475,7 @@ public class ClassGen {
try {
insnGen.makeInsn(insn, code, InsnGen.Flags.BODY_ONLY_NOWRAP);
} catch (Exception e) {
ErrorsCounter.classError(cls, "Failed to generate init code", e);
cls.addError("Failed to generate init code", e);
}
}
@@ -457,23 +497,35 @@ public class ClassGen {
}
}
public void useClass(CodeWriter code, String rawCls) {
useClass(code, ArgType.object(rawCls));
}
public void useClass(CodeWriter code, ArgType type) {
useClass(code, ClassInfo.extCls(cls.root(), type));
ArgType[] generics = type.getGenericTypes();
ArgType outerType = type.getOuterType();
if (outerType != null) {
useClass(code, outerType);
code.add('.');
// import not needed, force use short name
useClassShortName(code, type.getObject());
return;
}
useClass(code, ClassInfo.fromType(cls.root(), type));
List<ArgType> generics = type.getGenericTypes();
if (generics != null) {
code.add('<');
int len = generics.length;
int len = generics.size();
for (int i = 0; i < len; i++) {
if (i != 0) {
code.add(", ");
}
ArgType gt = generics[i];
ArgType gt = generics.get(i);
ArgType wt = gt.getWildcardType();
if (wt != null) {
code.add('?');
int bounds = gt.getWildcardBounds();
if (bounds != 0) {
code.add(bounds == -1 ? " super " : " extends ");
ArgType.WildcardBound bound = gt.getWildcardBound();
code.add(bound.getStr());
if (bound != ArgType.WildcardBound.UNBOUND) {
useType(code, wt);
}
} else {
@@ -484,27 +536,49 @@ public class ClassGen {
}
}
public void useClass(CodeWriter code, ClassInfo classInfo) {
ClassNode classNode = cls.dex().resolveClass(classInfo);
private void useClassShortName(CodeWriter code, String object) {
ClassInfo classInfo = ClassInfo.fromName(cls.root(), object);
ClassNode classNode = cls.root().resolveClass(classInfo);
if (classNode != null) {
code.attachAnnotation(classNode);
}
String baseClass = useClassInternal(cls.getAlias(), classInfo.getAlias());
code.add(baseClass);
code.add(classInfo.getAliasShortName());
}
public void useClass(CodeWriter code, ClassInfo classInfo) {
ClassNode classNode = cls.root().resolveClass(classInfo);
if (classNode != null) {
useClass(code, classNode);
} else {
addClsName(code, classInfo);
}
}
public void useClass(CodeWriter code, ClassNode classNode) {
code.attachAnnotation(classNode);
addClsName(code, classNode.getClassInfo());
}
private void addClsName(CodeWriter code, ClassInfo classInfo) {
String clsName = useClassInternal(cls.getClassInfo(), classInfo);
code.add(clsName);
}
private String useClassInternal(ClassInfo useCls, ClassInfo extClsInfo) {
String fullName = extClsInfo.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;
}
if (isClassInnerFor(useCls, extClsInfo)) {
return shortName;
}
if (extClsInfo.isInner()) {
return expandInnerClassName(useCls, extClsInfo);
}
if (isBothClassesInOneTopClass(useCls, extClsInfo)) {
return shortName;
}
@@ -512,27 +586,22 @@ public class ClassGen {
if (extClsInfo.getPackage().equals(useCls.getPackage()) && !extClsInfo.isInner()) {
return shortName;
}
// don't add import if class not public (must be accessed using inheritance)
ClassNode classNode = cls.dex().resolveClass(extClsInfo);
if (classNode != null && !classNode.getAccessFlags().isPublic()) {
return shortName;
}
if (searchCollision(cls.dex(), useCls, extClsInfo)) {
if (searchCollision(cls.root(), useCls, extClsInfo)) {
return fullName;
}
// ignore classes from default package
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;
}
@@ -542,15 +611,35 @@ public class ClassGen {
return shortName;
}
private String expandInnerClassName(ClassInfo useCls, ClassInfo extClsInfo) {
List<ClassInfo> clsList = new ArrayList<>();
clsList.add(extClsInfo);
ClassInfo parentCls = extClsInfo.getParentClass();
boolean addImport = true;
while (parentCls != null) {
if (parentCls == useCls || isClassInnerFor(useCls, parentCls)) {
addImport = false;
break;
}
clsList.add(parentCls);
parentCls = parentCls.getParentClass();
}
Collections.reverse(clsList);
if (addImport) {
addImport(clsList.get(0));
}
return Utils.listToString(clsList, ".", ClassInfo::getAliasShortName);
}
private void addImport(ClassInfo classInfo) {
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 {
@@ -571,42 +660,69 @@ public class ClassGen {
private static boolean isClassInnerFor(ClassInfo inner, ClassInfo parent) {
if (inner.isInner()) {
ClassInfo p = inner.getParentClass();
return p.equals(parent) || isClassInnerFor(p, parent);
return Objects.equals(p, parent) || isClassInnerFor(p, parent);
}
return false;
}
private static boolean searchCollision(DexNode dex, ClassInfo useCls, ClassInfo searchCls) {
private static boolean searchCollision(RootNode root, ClassInfo useCls, ClassInfo searchCls) {
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);
ClassNode classNode = root.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;
}
}
}
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(" */");
}
return searchCollision(root, useCls.getParentClass(), searchCls);
}
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());
}
}
private static void addClassUsageInfo(CodeWriter code, ClassNode cls) {
List<ClassNode> deps = cls.getDependencies();
code.startLine("// deps - ").add(Integer.toString(deps.size()));
for (ClassNode depCls : deps) {
code.startLine("// ").add(depCls.getClassInfo().getFullName());
}
List<ClassNode> useIn = cls.getUseIn();
code.startLine("// use in - ").add(Integer.toString(useIn.size()));
for (ClassNode useCls : useIn) {
code.startLine("// ").add(useCls.getClassInfo().getFullName());
}
List<MethodNode> useInMths = cls.getUseInMth();
code.startLine("// use in methods - ").add(Integer.toString(useInMths.size()));
for (MethodNode useMth : useInMths) {
code.startLine("// ").add(useMth.toString());
}
}
static void addMthUsageInfo(CodeWriter code, MethodNode mth) {
List<MethodNode> useInMths = mth.getUseIn();
code.startLine("// use in methods - ").add(Integer.toString(useInMths.size()));
for (MethodNode useMth : useInMths) {
code.startLine("// ").add(useMth.toString());
}
}
private static void addFieldUsageInfo(CodeWriter code, FieldNode fieldNode) {
List<MethodNode> useInMths = fieldNode.getUseIn();
code.startLine("// use in methods - ").add(Integer.toString(useInMths.size()));
for (MethodNode useMth : useInMths) {
code.startLine("// ").add(useMth.toString());
}
}
@@ -621,4 +737,12 @@ public class ClassGen {
public boolean isFallbackMode() {
return fallback;
}
public boolean isBodyGenStarted() {
return bodyGenStarted;
}
public void setBodyGenStarted(boolean bodyGenStarted) {
this.bodyGenStarted = bodyGenStarted;
}
}
@@ -1,15 +1,62 @@
package jadx.core.codegen;
import java.util.concurrent.Callable;
import jadx.api.ICodeInfo;
import jadx.api.JadxArgs;
import jadx.api.impl.SimpleCodeInfo;
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 ICodeInfo generate(ClassNode cls) {
if (cls.contains(AFlag.DONT_GENERATE)) {
return ICodeInfo.EMPTY;
}
JadxArgs args = cls.root().getArgs();
switch (args.getOutputFormat()) {
case JAVA:
return generateJavaCode(cls, args);
case JSON:
return generateJson(cls);
default:
throw new JadxRuntimeException("Unknown output format");
}
}
private static ICodeInfo generateJavaCode(ClassNode cls, JadxArgs args) {
ClassGen clsGen = new ClassGen(cls, args);
return wrapCodeGen(cls, clsGen::makeClass);
}
private static ICodeInfo generateJson(ClassNode cls) {
JsonCodeGen codeGen = new JsonCodeGen(cls);
String clsJson = wrapCodeGen(cls, codeGen::process);
return new SimpleCodeInfo(clsJson);
}
private static <R> R wrapCodeGen(ClassNode cls, Callable<R> codeGenFunc) {
try {
return codeGenFunc.call();
} catch (Exception e) {
if (cls.contains(AFlag.RESTART_CODEGEN)) {
cls.remove(AFlag.RESTART_CODEGEN);
try {
return codeGenFunc.call();
} catch (Exception ex) {
throw new JadxRuntimeException("Code generation error after restart", ex);
}
} else {
throw new JadxRuntimeException("Code generation error", e);
}
}
}
private CodeGen() {
}
}
@@ -1,10 +1,7 @@
package jadx.core.codegen;
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;
@@ -13,10 +10,11 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.CodePosition;
import jadx.api.ICodeInfo;
import jadx.api.impl.SimpleCodeInfo;
import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.utils.StringUtils;
import jadx.core.utils.files.FileUtils;
import jadx.core.utils.files.ZipSecurity;
import jadx.core.utils.Utils;
public class CodeWriter {
private static final Logger LOG = LoggerFactory.getLogger(CodeWriter.class);
@@ -35,7 +33,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,10 +45,12 @@ public class CodeWriter {
private Map<Integer, Integer> lineMap = Collections.emptyMap();
public CodeWriter() {
this.buf = new StringBuilder();
this.indent = 0;
this.indentStr = "";
if (ADD_LINE_NUMBERS) {
incIndent(2);
incIndent(3);
add(indentStr);
}
}
@@ -95,10 +95,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;
}
@@ -157,11 +159,7 @@ public class CodeWriter {
if (curIndent < INDENT_CACHE.length) {
this.indentStr = INDENT_CACHE[curIndent];
} else {
StringBuilder s = new StringBuilder(curIndent * INDENT_STR.length());
for (int i = 0; i < curIndent; i++) {
s.append(INDENT_STR);
}
this.indentStr = s.toString();
this.indentStr = Utils.strRepeat(INDENT_STR, curIndent);
}
}
@@ -221,6 +219,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<>();
@@ -228,10 +230,6 @@ public class CodeWriter {
return annotations.put(pos, obj);
}
public Map<CodePosition, Object> getAnnotations() {
return annotations;
}
public void attachSourceLine(int sourceLine) {
if (sourceLine == 0) {
return;
@@ -246,71 +244,48 @@ public class CodeWriter {
lineMap.put(decompiledLine, sourceLine);
}
public Map<Integer, Integer> getLineMapping() {
return lineMap;
}
public void finish() {
public ICodeInfo finish() {
removeFirstEmptyLine();
buf.trimToSize();
processDefinitionAnnotations();
code = buf.toString();
buf = null;
Iterator<Map.Entry<CodePosition, Object>> it = annotations.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<CodePosition, Object> entry = it.next();
Object v = entry.getValue();
if (v instanceof DefinitionWrapper) {
LineAttrNode l = ((DefinitionWrapper) v).getNode();
l.setDecompiledLine(entry.getKey().getLine());
it.remove();
}
}
return new SimpleCodeInfo(code, lineMap, annotations);
}
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);
}
}
private void processDefinitionAnnotations() {
if (!annotations.isEmpty()) {
annotations.entrySet().removeIf(entry -> {
Object v = entry.getValue();
if (v instanceof DefinitionWrapper) {
LineAttrNode l = ((DefinitionWrapper) v).getNode();
l.setDecompiledLine(entry.getKey().getLine());
return true;
}
return false;
});
}
}
public int bufLength() {
return buf.length();
}
public String getCodeStr() {
if (code == null) {
finish();
}
return code;
}
@Override
public String toString() {
return buf == null ? code : buf.toString();
}
public void save(File dir, String subDir, String fileName) {
if(!ZipSecurity.isValidZipEntryName(subDir) || !ZipSecurity.isValidZipEntryName(fileName)) {
return;
}
save(dir, new File(subDir, fileName).getPath());
}
public void save(File dir, String fileName) {
if(!ZipSecurity.isValidZipEntryName(fileName)) {
return;
}
save(new File(dir, fileName));
}
public void save(File file) {
if (code == null) {
finish();
}
File outFile = FileUtils.prepareFile(file);
try (PrintWriter out = new PrintWriter(outFile, "UTF-8")) {
out.println(code);
} catch (Exception e) {
LOG.error("Save file error", e);
}
return code != null ? code : buf.toString();
}
}
@@ -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;
@@ -15,7 +16,6 @@ import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.regions.conditions.Compare;
import jadx.core.dex.regions.conditions.IfCondition;
import jadx.core.dex.regions.conditions.IfCondition.Mode;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.JadxRuntimeException;
@@ -122,7 +122,7 @@ public class ConditionGen extends InsnGen {
wrap(code, firstArg);
return;
}
ErrorsCounter.methodWarn(mth, "Unsupported boolean condition " + op.getSymbol());
mth.addWarn("Unsupported boolean condition " + op.getSymbol());
}
addArg(code, firstArg, isArgWrapNeeded(firstArg));
@@ -155,7 +155,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,5 @@
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,19 +9,22 @@ import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.deobf.NameMapper;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
import jadx.core.dex.attributes.nodes.GenericInfoAttr;
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
import jadx.core.dex.attributes.nodes.MethodInlineAttr;
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.ArithNode;
import jadx.core.dex.instructions.ArithOp;
import jadx.core.dex.instructions.BaseInvokeNode;
import jadx.core.dex.instructions.ConstClassNode;
import jadx.core.dex.instructions.ConstStringNode;
import jadx.core.dex.instructions.FillArrayNode;
import jadx.core.dex.instructions.FillArrayInsn;
import jadx.core.dex.instructions.FilledNewArrayNode;
import jadx.core.dex.instructions.GotoNode;
import jadx.core.dex.instructions.IfNode;
@@ -32,14 +33,15 @@ import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.InvokeNode;
import jadx.core.dex.instructions.InvokeType;
import jadx.core.dex.instructions.NewArrayNode;
import jadx.core.dex.instructions.SwitchNode;
import jadx.core.dex.instructions.SwitchInsn;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.FieldArg;
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.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 +49,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 +62,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 +73,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() {
@@ -97,22 +100,26 @@ public class InsnGen {
} else if (arg.isLiteral()) {
code.add(lit((LiteralArg) arg));
} else if (arg.isInsnWrap()) {
Flags flag = wrap ? Flags.BODY_ONLY : Flags.BODY_ONLY_NOWRAP;
makeInsn(((InsnWrapArg) arg).getWrapInsn(), code, flag);
addWrappedArg(code, (InsnWrapArg) arg, wrap);
} else if (arg.isNamed()) {
code.add(((Named) arg).getName());
} else if (arg.isField()) {
FieldArg f = (FieldArg) arg;
if (f.isStatic()) {
staticField(code, f.getField());
} else {
instanceField(code, f.getField(), f.getInstanceArg());
}
} else {
throw new CodegenException("Unknown arg type " + arg);
}
}
private void addWrappedArg(CodeWriter code, InsnWrapArg arg, boolean wrap) throws CodegenException {
InsnNode wrapInsn = arg.getWrapInsn();
if (wrapInsn.contains(AFlag.FORCE_ASSIGN_INLINE)) {
code.add('(');
makeInsn(wrapInsn, code, Flags.INLINE);
code.add(')');
} else {
Flags flags = wrap ? Flags.BODY_ONLY : Flags.BODY_ONLY_NOWRAP;
makeInsn(wrapInsn, code, flags);
}
}
public void assignVar(CodeWriter code, InsnNode insn) throws CodegenException {
RegisterArg arg = insn.getResult();
if (insn.contains(AFlag.DECLARE_VAR)) {
@@ -123,21 +130,25 @@ 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, mth, fallback);
}
private void instanceField(CodeWriter code, FieldInfo field, InsnArg arg) throws CodegenException {
ClassNode pCls = mth.getParentClass();
FieldNode fieldNode = pCls.dex().root().deepResolveField(field);
FieldNode fieldNode = pCls.root().deepResolveField(field);
if (fieldNode != null) {
FieldReplaceAttr replace = fieldNode.get(AType.FIELD_REPLACE);
if (replace != null) {
@@ -166,15 +177,16 @@ public class InsnGen {
public static void makeStaticFieldAccess(CodeWriter code, FieldInfo field, ClassGen clsGen) {
ClassInfo declClass = field.getDeclClass();
// TODO
boolean fieldFromThisClass = clsGen.getClassNode().getClassInfo().equals(declClass);
if (!fieldFromThisClass) {
if (!fieldFromThisClass || !clsGen.isBodyGenStarted()) {
// Android specific resources class handler
if (!handleAppResField(code, clsGen, declClass)) {
clsGen.useClass(code, declClass);
}
code.add('.');
}
FieldNode fieldNode = clsGen.getClassNode().dex().root().deepResolveField(field);
FieldNode fieldNode = clsGen.getClassNode().root().deepResolveField(field);
if (fieldNode != null) {
code.attachAnnotation(fieldNode);
}
@@ -201,40 +213,54 @@ 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 {
if (insn.getType() == InsnType.REGION_ARG) {
return;
}
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.contains(AFlag.COMMENT_OUT)) {
code.add("// ");
}
}
if (insn.getResult() != null && !insn.contains(AFlag.ARITH_ONEARG)) {
assignVar(code, insn);
code.add(" = ");
RegisterArg resArg = insn.getResult();
if (resArg != null) {
SSAVar var = resArg.getSVar();
if (var == null || var.getUseCount() != 0 || insn.getType() != InsnType.CONSTRUCTOR) {
assignVar(code, insn);
code.add(" = ");
}
}
makeInsnBody(code, insn, state);
makeInsnBody(code, insn, EMPTY_FLAGS);
if (flag != Flags.INLINE) {
code.add(';');
}
}
} catch (Throwable 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 {
switch (insn.getType()) {
case CONST_STR:
String str = ((ConstStringNode) insn).getString();
code.add(mth.dex().root().getStringUtils().unescapeString(str));
code.add(mth.root().getStringUtils().unescapeString(str));
break;
case CONST_CLASS:
@@ -277,7 +303,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 +390,17 @@ public class InsnGen {
filledNewArray((FilledNewArrayNode) insn, code);
break;
case FILL_ARRAY:
FillArrayInsn arrayNode = (FillArrayInsn) 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 +444,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(" + ");
@@ -428,7 +466,9 @@ public class InsnGen {
case MONITOR_EXIT:
if (isFallback()) {
code.add("monitor-exit(");
addArg(code, insn.getArg(0));
if (insn.getArgsCount() == 1) {
addArg(code, insn.getArg(0));
}
code.add(')');
}
break;
@@ -465,15 +505,16 @@ public class InsnGen {
case SWITCH:
fallbackOnlyInsn(insn);
SwitchNode sw = (SwitchNode) insn;
SwitchInsn sw = (SwitchInsn) insn;
code.add("switch(");
addArg(code, insn.getArg(0));
code.add(") {");
code.incIndent();
for (int i = 0; i < sw.getCasesCount(); i++) {
String key = sw.getKeys()[i].toString();
code.startLine("case ").add(key).add(": goto ");
code.add(MethodGen.getLabelName(sw.getTargets()[i])).add(';');
int[] keys = sw.getKeys();
int[] targets = sw.getTargets();
for (int i = 0; i < keys.length; i++) {
code.startLine("case ").add(Integer.toString(keys[i])).add(": goto ");
code.add(MethodGen.getLabelName(targets[i])).add(';');
}
code.startLine("default: goto ");
code.add(MethodGen.getLabelName(sw.getDefaultCaseOffset())).add(';');
@@ -481,40 +522,35 @@ 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;
case MOVE_RESULT:
fallbackOnlyInsn(insn);
code.add("move-result");
break;
case FILL_ARRAY_DATA:
fallbackOnlyInsn(insn);
code.add("fill-array " + insn.toString());
break;
case SWITCH_DATA:
fallbackOnlyInsn(insn);
code.add(insn.toString());
break;
default:
@@ -522,6 +558,34 @@ 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, FillArrayInsn arrayNode) throws CodegenException {
code.add("// fill-array-data instruction");
code.startLine();
InsnArg arrArg = arrayNode.getArg(0);
ArgType arrayType = arrArg.getType();
ArgType elemType;
if (arrayType.isTypeKnown() && arrayType.isArray()) {
elemType = arrayType.getArrayElement();
} else {
ArgType elementType = arrayNode.getElementType(); // unknown type
elemType = elementType.selectFirst();
}
List<LiteralArg> args = arrayNode.getLiteralArgs(elemType);
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 +600,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++) {
@@ -554,11 +624,12 @@ public class InsnGen {
code.add('}');
}
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);
private void makeConstructor(ConstructorInsn insn, CodeWriter code) throws CodegenException {
ClassNode cls = mth.root().resolveClass(insn.getClassType());
if (cls != null && cls.isAnonymous() && !fallback) {
cls.ensureProcessed();
inlineAnonymousConstructor(code, cls, insn);
mth.getParentClass().addInlinedClass(cls);
return;
}
if (insn.isSelf()) {
@@ -571,56 +642,66 @@ public class InsnGen {
} else {
code.add("new ");
useClass(code, insn.getClassType());
GenericInfoAttr genericInfoAttr = insn.get(AType.GENERIC_INFO);
if (genericInfoAttr != null) {
code.add('<');
if (genericInfoAttr.isExplicit()) {
boolean first = true;
for (ArgType type : genericInfoAttr.getGenericTypes()) {
if (!first) {
code.add(',');
} else {
first = false;
}
mgen.getClassGen().useType(code, type);
}
}
code.add('>');
}
}
MethodNode callMth = mth.dex().resolveMethod(insn.getCallMth());
MethodNode callMth = mth.root().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");
} else {
useClass(code, parent);
}
MethodNode callMth = mth.dex().resolveMethod(insn.getCallMth());
MethodNode callMth = mth.root().resolveMethod(insn.getCallMth());
generateMethodArguments(code, insn, 0, callMth);
code.add(' ');
new ClassGen(cls, mgen.getClassGen().getParentGen()).addClassBody(code);
new ClassGen(cls, mgen.getClassGen().getParentGen()).addClassBody(code, true);
}
private void makeInvoke(InvokeNode insn, CodeWriter code) throws CodegenException {
MethodInfo callMth = insn.getCallMth();
// inline method
MethodNode callMthNode = mth.root().deepResolveMethod(callMth);
if (callMthNode != null) {
if (inlineMethod(callMthNode, insn, code)) {
return;
}
callMth = callMthNode.getMethodInfo();
}
int k = 0;
InvokeType type = insn.getInvokeType();
@@ -637,13 +718,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,36 +739,68 @@ 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);
}
void generateMethodArguments(CodeWriter code, InsnNode insn, int startArgNum,
@Nullable MethodNode callMth) throws CodegenException {
@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, BaseInvokeNode insn, int startArgNum,
@Nullable MethodNode mthNode) throws CodegenException {
int k = startArgNum;
if (callMth != null && callMth.contains(AFlag.SKIP_FIRST_ARG)) {
if (mthNode != null && mthNode.contains(AFlag.SKIP_FIRST_ARG)) {
k++;
}
int argsCount = insn.getArgsCount();
code.add('(');
boolean firstArg = true;
if (k < argsCount) {
boolean overloaded = callMth != null && callMth.isArgsOverload();
for (int i = k; i < argsCount; i++) {
InsnArg arg = insn.getArg(i);
if (arg.contains(AFlag.SKIP_ARG)) {
continue;
}
RegisterArg callArg = getCallMthArg(callMth, i - startArgNum);
if (callArg != null && callArg.contains(AFlag.SKIP_ARG)) {
int argOrigPos = i - startArgNum;
if (SkipMethodArgsAttr.isSkip(mthNode, argOrigPos)) {
continue;
}
if (!firstArg) {
code.add(", ");
} else {
firstArg = false;
}
boolean cast = overloaded && processOverloadedArg(code, callMth, arg, i - startArgNum);
if (!cast && i == argsCount - 1 && processVarArg(code, callMth, arg)) {
if (i == argsCount - 1 && processVarArg(code, insn, arg)) {
continue;
}
addArg(code, arg, false);
@@ -692,91 +810,27 @@ public class InsnGen {
code.add(')');
}
private static RegisterArg getCallMthArg(@Nullable MethodNode callMth, int num) {
if (callMth == null) {
return null;
}
List<RegisterArg> args = callMth.getArguments(false);
if (args != null && num < args.size()) {
return args.get(num);
}
return null;
}
/**
* Add additional cast for overloaded method argument.
*/
private boolean processOverloadedArg(CodeWriter code, MethodNode callMth, InsnArg arg, int origPos) {
ArgType origType = callMth.getMethodInfo().getArgumentsTypes().get(origPos);
if (!arg.getType().equals(origType)) {
code.add('(');
useType(code, origType);
code.add(") ");
return true;
}
return false;
}
/**
* Expand varArgs from filled array.
*/
private boolean processVarArg(CodeWriter code, MethodNode callMth, InsnArg lastArg) throws CodegenException {
if (callMth == null || !callMth.getAccessFlags().isVarArgs()) {
private boolean processVarArg(CodeWriter code, BaseInvokeNode invokeInsn, InsnArg lastArg) throws CodegenException {
if (!invokeInsn.contains(AFlag.VARARG_CALL)) {
return false;
}
if (!lastArg.getType().isArray() || !lastArg.isInsnWrap()) {
return false;
}
InsnNode insn = ((InsnWrapArg) lastArg).getWrapInsn();
if (insn.getType() == InsnType.FILLED_NEW_ARRAY) {
int count = insn.getArgsCount();
for (int i = 0; i < count; i++) {
InsnArg elemArg = insn.getArg(i);
addArg(code, elemArg, false);
if (i < count - 1) {
code.add(", ");
}
}
return true;
}
return false;
}
private boolean inlineMethod(MethodNode callMthNode, InvokeNode insn, CodeWriter code) throws CodegenException {
MethodInlineAttr mia = callMthNode.get(AType.METHOD_INLINE);
if (mia == null) {
if (insn.getType() != InsnType.FILLED_NEW_ARRAY) {
return false;
}
InsnNode inl = mia.getInsn();
if (callMthNode.getMethodInfo().getArgumentsTypes().isEmpty()) {
makeInsn(inl, code, Flags.BODY_ONLY);
} else {
// remap args
InsnArg[] regs = new InsnArg[callMthNode.getRegsCount()];
List<RegisterArg> callArgs = callMthNode.getArguments(true);
for (int i = 0; i < callArgs.size(); i++) {
InsnArg arg = insn.getArg(i);
RegisterArg callArg = callArgs.get(i);
regs[callArg.getRegNum()] = arg;
int count = insn.getArgsCount();
for (int i = 0; i < count; i++) {
InsnArg elemArg = insn.getArg(i);
addArg(code, elemArg, false);
if (i < count - 1) {
code.add(", ");
}
// replace args
InsnNode inlCopy = inl.copy();
List<RegisterArg> inlArgs = new ArrayList<>();
inlCopy.getRegisterArgs(inlArgs);
for (RegisterArg r : inlArgs) {
int regNum = r.getRegNum();
if (regNum >= regs.length) {
LOG.warn("Unknown register number {} in method call: {} from {}", r, callMthNode, mth);
} else {
InsnArg repl = regs[regNum];
if (repl == null) {
LOG.warn("Not passed register {} in method call: {} from {}", r, callMthNode, mth);
} else {
inlCopy.replaceArg(r, repl);
}
}
}
makeInsn(inlCopy, code, Flags.BODY_ONLY);
}
return true;
}
@@ -789,11 +843,12 @@ public class InsnGen {
InsnArg first = insn.getArg(0);
InsnArg second = insn.getArg(1);
ConditionGen condGen = new ConditionGen(this);
if (first.equals(LiteralArg.TRUE) && second.equals(LiteralArg.FALSE)) {
if (first.isTrue() && second.isFalse()) {
condGen.add(code, insn.getCondition());
} else {
condGen.wrap(code, insn.getCondition());
code.add(" ? ");
addCastIfNeeded(code, first, second);
addArg(code, first, false);
code.add(" : ");
addArg(code, second, false);
@@ -803,6 +858,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);
@@ -825,19 +907,22 @@ public class InsnGen {
private void makeArithOneArg(ArithNode insn, CodeWriter code) throws CodegenException {
ArithOp op = insn.getOp();
InsnArg resArg = insn.getArg(0);
InsnArg arg = insn.getArg(1);
// "++" or "--"
if (arg.isLiteral() && (op == ArithOp.ADD || op == ArithOp.SUB)) {
LiteralArg lit = (LiteralArg) arg;
if (lit.isInteger() && lit.getLiteral() == 1) {
assignVar(code, insn);
if (lit.getLiteral() == 1 && lit.isInteger()) {
addArg(code, resArg, false);
String opSymbol = op.getSymbol();
code.add(opSymbol).add(opSymbol);
return;
}
}
// +=, -= ...
assignVar(code, insn);
// +=, -=, ...
addArg(code, resArg, false);
code.add(' ').add(op.getSymbol()).add("= ");
addArg(code, arg, false);
}
@@ -1,29 +1,45 @@
package jadx.core.codegen;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import com.android.dx.rop.code.AccessFlags;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.plugins.input.data.AccessFlags;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.core.Consts;
import jadx.core.Jadx;
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.attributes.nodes.MethodOverrideAttr;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.ConstStringNode;
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.IMethodDetails;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.trycatch.CatchAttr;
import jadx.core.dex.visitors.DepthTraversal;
import jadx.core.dex.visitors.FallbackModeVisitor;
import jadx.core.utils.ErrorsCounter;
import jadx.core.dex.visitors.IDexTreeVisitor;
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;
import static jadx.core.codegen.MethodGen.FallbackOption.BLOCK_DUMP;
import static jadx.core.codegen.MethodGen.FallbackOption.COMMENTED_DUMP;
import static jadx.core.codegen.MethodGen.FallbackOption.FALLBACK_MODE;
public class MethodGen {
private static final Logger LOG = LoggerFactory.getLogger(MethodGen.class);
@@ -64,30 +80,45 @@ public class MethodGen {
code.attachDefinition(mth);
return false;
}
if (Consts.DEBUG_USAGE) {
ClassGen.addMthUsageInfo(code, mth);
}
addOverrideAnnotation(code, mth);
annotationGen.addForMethod(code, mth);
AccessInfo clsAccFlags = mth.getParentClass().getAccessFlags();
AccessInfo ai = mth.getAccessFlags();
// don't add 'abstract' and 'public' to methods in interface
if (clsAccFlags.isInterface()) {
ai = ai.remove(AccessFlags.ACC_ABSTRACT);
ai = ai.remove(AccessFlags.ACC_PUBLIC);
ai = ai.remove(AccessFlags.ABSTRACT);
ai = ai.remove(AccessFlags.PUBLIC);
}
// don't add 'public' for annotations
if (clsAccFlags.isAnnotation()) {
ai = ai.remove(AccessFlags.ACC_PUBLIC);
ai = ai.remove(AccessFlags.PUBLIC);
}
if (mth.getMethodInfo().isRenamed()) {
code.startLine("/* renamed from: ").add(mth.getName()).add(" */");
if (mth.getMethodInfo().hasAlias() && !ai.isConstructor()) {
CodeGenUtils.addRenamedComment(code, mth, mth.getName());
}
if (mth.contains(AFlag.INCONSISTENT_CODE)) {
code.startLine("/* Code decompiled incorrectly, please refer to instructions dump. */");
}
code.startLineWithNum(mth.getSourceLine());
code.add(ai.makeString());
if (Consts.DEBUG) {
code.add(mth.isVirtual() ? "/* virtual */ " : "/* direct */ ");
}
if (clsAccFlags.isInterface() && !mth.isNoCode()) {
// add 'default' for method with code in interface
code.add("default ");
}
if (classGen.addGenericMap(code, mth.getGenericMap())) {
if (classGen.addGenericTypeParameters(code, mth.getTypeParameters(), false)) {
code.add(' ');
}
if (mth.getAccessFlags().isConstructor()) {
if (ai.isConstructor()) {
code.attachDefinition(mth);
code.add(classGen.getClassNode().getShortName()); // constructor
} else {
@@ -98,135 +129,276 @@ public class MethodGen {
}
code.add('(');
List<RegisterArg> args = mth.getArguments(false);
List<RegisterArg> args = mth.getArgRegs();
if (mth.getMethodInfo().isConstructor()
&& mth.getParentClass().contains(AType.ENUM_CLASS)) {
if (args.size() == 2) {
args.clear();
args = Collections.emptyList();
} else if (args.size() > 2) {
args = args.subList(2, args.size());
} else {
LOG.warn(ErrorsCounter.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()) {
EncodedValue def = annotationGen.getAnnotationDefaultValue(mth.getName());
if (def != null) {
code.add(" default ");
annotationGen.encodeValue(mth.root(), code, def);
}
}
return true;
}
private void addMethodArguments(CodeWriter argsCode, List<RegisterArg> args) {
private void addOverrideAnnotation(CodeWriter code, MethodNode mth) {
MethodOverrideAttr overrideAttr = mth.get(AType.METHOD_OVERRIDE);
if (overrideAttr == null) {
return;
}
code.startLine("@Override");
code.add(" // ");
Iterator<IMethodDetails> it = overrideAttr.getOverrideList().iterator();
while (it.hasNext()) {
IMethodDetails methodDetails = it.next();
code.add(methodDetails.getMethodInfo().getDeclClass().getAliasFullName());
if (it.hasNext()) {
code.add(", ");
}
}
}
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, classGen.isFallbackMode());
} 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;
ArgType varType = var.getType();
if (varType == null || varType == ArgType.UNKNOWN) {
// occur on decompilation errors
argType = mthArg.getInitType();
} else {
argType = varType;
}
if (!it.hasNext() && mth.getAccessFlags().isVarArgs()) {
// change last array argument to varargs
ArgType type = arg.getType();
if (type.isArray()) {
ArgType elType = type.getArrayElement();
classGen.useType(argsCode, elType);
argsCode.add("...");
if (argType.isArray()) {
ArgType elType = argType.getArrayElement();
classGen.useType(code, elType);
code.add("...");
} else {
LOG.warn(ErrorsCounter.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("/*");
addFallbackMethodCode(code);
code.startLine("*/");
code.startLine("throw new UnsupportedOperationException(\"Method not decompiled: ")
.add(mth.toString())
.add("\");");
if (mth.root().getArgs().isFallbackMode()) {
addFallbackMethodCode(code, FALLBACK_MODE);
} else if (classGen.isFallbackMode()) {
dumpInstructions(code);
} else {
RegionGen regionGen = new RegionGen(this);
regionGen.makeRegion(code, mth.getRegion());
addRegionInsns(code);
}
}
public void addFallbackMethodCode(CodeWriter code) {
if (mth.getInstructions() == null) {
// load original instructions
try {
mth.load();
DepthTraversal.visit(new FallbackModeVisitor(), mth);
} catch (DecodeException e) {
LOG.error("Error reload instructions in fallback mode:", e);
code.startLine("// Can't load method instructions: " + e.getMessage());
return;
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, COMMENTED_DUMP);
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, FallbackOption fallbackOption) {
// load original instructions
try {
mth.unload();
mth.load();
for (IDexTreeVisitor visitor : Jadx.getFallbackPassesList()) {
DepthTraversal.visit(visitor, mth);
}
} catch (DecodeException e) {
LOG.error("Error reload instructions in fallback mode:", e);
code.startLine("// Can't load method instructions: " + e.getMessage());
return;
}
InsnNode[] insnArr = mth.getInstructions();
if (insnArr == null) {
code.startLine("// Can't load method instructions.");
return;
}
if (insnArr.length > 100) {
code.startLine("// Method dump skipped, instructions count: " + insnArr.length);
return;
}
code.incIndent();
if (mth.getThisArg() != null) {
code.startLine(nameGen.useArg(mth.getThisArg())).add(" = this;");
}
addFallbackInsns(code, mth, insnArr, true);
addFallbackInsns(code, mth, insnArr, fallbackOption);
code.decIndent();
}
public static void addFallbackInsns(CodeWriter code, MethodNode mth, InsnNode[] insnArr, boolean addLabels) {
public enum FallbackOption {
FALLBACK_MODE,
BLOCK_DUMP,
COMMENTED_DUMP
}
public static void addFallbackInsns(CodeWriter code, MethodNode mth, InsnNode[] insnArr, FallbackOption option) {
int startIndent = code.getIndent();
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 (option != BLOCK_DUMP && 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);
boolean escapeComment = isCommentEscapeNeeded(insn, option);
if (escapeComment) {
code.decIndent();
code.startLine("*/");
code.startLine("// ");
} else {
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(' ');
}
}
} catch (CodegenException e) {
insnGen.makeInsn(insn, code, InsnGen.Flags.INLINE);
if (escapeComment) {
code.startLine("/*");
code.incIndent();
}
CatchAttr catchAttr = insn.get(AType.CATCH_BLOCK);
if (catchAttr != null) {
code.add(" // " + catchAttr);
}
} catch (Exception e) {
LOG.debug("Error generate fallback instruction: ", e.getCause());
code.setIndent(startIndent);
code.startLine("// error: " + insn);
}
prevInsn = insn;
}
}
private static boolean isCommentEscapeNeeded(InsnNode insn, FallbackOption option) {
if (option == COMMENTED_DUMP) {
if (insn.getType() == InsnType.CONST_STR) {
String str = ((ConstStringNode) insn).getString();
return str.contains("*/");
}
}
return false;
}
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,65 +1,85 @@
package jadx.core.codegen;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import jadx.core.Consts;
import jadx.core.deobf.NameMapper;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.InvokeNode;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.CodeVar;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.InsnWrapArg;
import jadx.core.dex.instructions.args.NamedArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.instructions.mods.ConstructorInsn;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.StringUtils;
import jadx.core.utils.Utils;
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;
static {
OBJ_ALIAS = new HashMap<>();
OBJ_ALIAS.put(Consts.CLASS_STRING, "str");
OBJ_ALIAS.put(Consts.CLASS_CLASS, "cls");
OBJ_ALIAS.put(Consts.CLASS_THROWABLE, "th");
OBJ_ALIAS.put(Consts.CLASS_OBJECT, "obj");
OBJ_ALIAS.put("java.util.Iterator", "it");
OBJ_ALIAS.put("java.lang.Boolean", "bool");
OBJ_ALIAS.put("java.lang.Short", "sh");
OBJ_ALIAS.put("java.lang.Integer", "num");
OBJ_ALIAS.put("java.lang.Character", "ch");
OBJ_ALIAS.put("java.lang.Byte", "b");
OBJ_ALIAS.put("java.lang.Float", "f");
OBJ_ALIAS.put("java.lang.Long", "l");
OBJ_ALIAS.put("java.lang.Double", "d");
OBJ_ALIAS = Utils.newConstStringMap(
Consts.CLASS_STRING, "str",
Consts.CLASS_CLASS, "cls",
Consts.CLASS_THROWABLE, "th",
Consts.CLASS_OBJECT, "obj",
"java.util.Iterator", "it",
"java.lang.Boolean", "bool",
"java.lang.Short", "sh",
"java.lang.Integer", "num",
"java.lang.Character", "ch",
"java.lang.Byte", "b",
"java.lang.Float", "f",
"java.lang.Long", "l",
"java.lang.Double", "d",
"java.lang.StringBuilder", "sb",
"java.lang.Exception", "exc");
}
public NameGen(MethodNode mth, boolean fallback) {
this.mth = mth;
this.fallback = fallback;
addNamesUsedInClass();
}
public String assignArg(RegisterArg arg) {
String name = makeArgName(arg);
if (fallback) {
return name;
private void addNamesUsedInClass() {
ClassNode parentClass = mth.getParentClass();
for (FieldNode field : parentClass.getFields()) {
varNames.add(field.getAlias());
}
name = getUniqueVarName(name);
arg.setName(name);
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) {
if (fallback) {
return getFallbackName(var);
}
if (var.isThis()) {
return RegisterArg.THIS_ARG_NAME;
}
String name = getUniqueVarName(makeArgName(var));
var.setName(name);
return name;
}
@@ -99,39 +119,50 @@ public class NameGen {
return r;
}
private String makeArgName(RegisterArg arg) {
if (fallback) {
return getFallbackName(arg);
private String makeArgName(CodeVar var) {
String name = var.getName();
if (name == null) {
name = guessName(var);
}
if (arg.isThis()) {
return RegisterArg.THIS_ARG_NAME;
if (!NameMapper.isValidAndPrintable(name)) {
name = getFallbackName(var);
}
String name = arg.getName();
String varName = name != null ? name : guessName(arg);
if (NameMapper.isReserved(varName)) {
return varName + "R";
if (Consts.DEBUG) {
name += '_' + getFallbackName(var);
}
return varName;
return name;
}
private String getFallbackName(CodeVar var) {
List<SSAVar> ssaVars = var.getSsaVars();
if (ssaVars.isEmpty()) {
return "v";
}
return getFallbackName(ssaVars.get(0).getAssign());
}
private String getFallbackName(RegisterArg arg) {
return "r" + arg.getRegNum();
}
private String guessName(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 +180,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 +261,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;
}
}
@@ -1,17 +1,23 @@
package jadx.core.codegen;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.FieldInitAttr;
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
import jadx.core.dex.attributes.nodes.ForceReturnAttr;
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
import jadx.core.dex.instructions.SwitchNode;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.instructions.SwitchInsn;
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.NamedArg;
import jadx.core.dex.instructions.args.RegisterArg;
@@ -21,9 +27,9 @@ import jadx.core.dex.nodes.IBlock;
import jadx.core.dex.nodes.IContainer;
import jadx.core.dex.nodes.IRegion;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.parser.FieldInitAttr;
import jadx.core.dex.regions.Region;
import jadx.core.dex.regions.SwitchRegion;
import jadx.core.dex.regions.SwitchRegion.CaseInfo;
import jadx.core.dex.regions.SynchronizedRegion;
import jadx.core.dex.regions.TryCatchRegion;
import jadx.core.dex.regions.conditions.IfCondition;
@@ -33,7 +39,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.ErrorsCounter;
import jadx.core.utils.BlockUtils;
import jadx.core.utils.RegionUtils;
import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.JadxRuntimeException;
@@ -73,7 +79,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(';');
@@ -95,8 +101,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);
}
}
@@ -112,21 +122,44 @@ 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);
}
}
}
boolean comment = region.contains(AFlag.COMMENT_OUT);
if (comment) {
code.add("// ");
}
code.add("if (");
new ConditionGen(this).add(code, region.getCondition());
code.add(") {");
makeRegionIndent(code, region.getThenRegion());
code.startLine('}');
if (comment) {
code.startLine("// }");
} else {
code.startLine('}');
}
IContainer els = region.getElseRegion();
if (els != null && RegionUtils.notEmpty(els)) {
if (RegionUtils.notEmpty(els)) {
code.add(" else ");
if (connectElseIf(code, els)) {
return;
}
code.add('{');
makeRegionIndent(code, els);
code.startLine('}');
if (comment) {
code.startLine("// }");
} else {
code.startLine('}');
}
}
}
@@ -149,18 +182,6 @@ public class RegionGen extends InsnGen {
}
private CodeWriter makeLoop(LoopRegion region, CodeWriter code) throws CodegenException {
BlockNode header = region.getHeader();
if (header != null) {
List<InsnNode> headerInsns = header.getInstructions();
if (headerInsns.size() > 1) {
ErrorsCounter.methodWarn(mth, "Found not inlined instructions from loop header");
int last = headerInsns.size() - 1;
for (int i = 0; i < last; i++) {
InsnNode insn = headerInsns.get(i);
makeInsn(insn, code);
}
}
}
LoopLabelAttr labelAttr = region.getInfo().getStart().get(AType.LOOP_LABEL);
if (labelAttr != null) {
code.startLine(mgen.getNameGen().getLoopLabel(labelAttr)).add(':');
@@ -230,49 +251,56 @@ public class RegionGen extends InsnGen {
}
private CodeWriter makeSwitch(SwitchRegion sw, CodeWriter code) throws CodegenException {
SwitchNode insn = (SwitchNode) sw.getHeader().getInstructions().get(0);
SwitchInsn insn = (SwitchInsn) 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);
code.add(") {");
code.incIndent();
int size = sw.getKeys().size();
for (int i = 0; i < size; i++) {
List<Object> keys = sw.getKeys().get(i);
IContainer c = sw.getCases().get(i);
for (CaseInfo caseInfo : sw.getCases()) {
List<Object> keys = caseInfo.getKeys();
IContainer c = caseInfo.getContainer();
for (Object k : keys) {
code.startLine("case ");
if (k instanceof FieldNode) {
FieldNode fn = (FieldNode) k;
if (fn.getParentClass().isEnum()) {
code.add(fn.getAlias());
} else {
staticField(code, fn.getFieldInfo());
// print original value, sometimes replace with incorrect field
FieldInitAttr valueAttr = fn.get(AType.FIELD_INIT);
if (valueAttr != null && valueAttr.getValue() != null) {
code.add(" /*").add(valueAttr.getValue().toString()).add("*/");
}
}
} else if (k instanceof Integer) {
code.add(TypeGen.literalToString((Integer) k, arg.getType(), mth));
if (k == SwitchRegion.DEFAULT_CASE_KEY) {
code.startLine("default:");
} else {
throw new JadxRuntimeException("Unexpected key in switch: " + (k != null ? k.getClass() : null));
code.startLine("case ");
addCaseKey(code, arg, k);
code.add(':');
}
code.add(':');
}
makeRegionIndent(code, c);
}
if (sw.getDefaultCase() != null) {
code.startLine("default:");
makeRegionIndent(code, sw.getDefaultCase());
}
code.decIndent();
code.startLine('}');
return code;
}
private void addCaseKey(CodeWriter code, InsnArg arg, Object k) {
if (k instanceof FieldNode) {
FieldNode fn = (FieldNode) k;
if (fn.getParentClass().isEnum()) {
code.add(fn.getAlias());
} else {
staticField(code, fn.getFieldInfo());
// print original value, sometimes replaced with incorrect field
FieldInitAttr valueAttr = fn.get(AType.FIELD_INIT);
if (valueAttr != null && valueAttr.getValueType() == FieldInitAttr.InitType.CONST) {
Object value = valueAttr.getEncodedValue();
if (value != null) {
code.add(" /*").add(value.toString()).add("*/");
}
}
}
} else if (k instanceof Integer) {
code.add(TypeGen.literalToString((Integer) k, arg.getType(), mth, fallback));
} else {
throw new JadxRuntimeException("Unexpected key in switch: " + (k != null ? k.getClass() : null));
}
}
private void makeTryCatch(TryCatchRegion region, CodeWriter code) throws CodegenException {
code.startLine("try {");
makeRegionIndent(code, region.getTryRegion());
@@ -306,17 +334,29 @@ public class RegionGen extends InsnGen {
return;
}
code.startLine("} catch (");
InsnArg arg = handler.getArg();
if (arg instanceof RegisterArg) {
declareVar(code, (RegisterArg) arg);
} else if (arg instanceof NamedArg) {
if (handler.isCatchAll()) {
code.add("Throwable");
} else {
useClass(code, handler.getCatchType());
if (handler.isCatchAll()) {
useClass(code, ArgType.THROWABLE);
} else {
Iterator<ClassInfo> it = handler.getCatchTypes().iterator();
if (it.hasNext()) {
useClass(code, it.next());
}
code.add(' ');
while (it.hasNext()) {
code.add(" | ");
useClass(code, it.next());
}
}
code.add(' ');
InsnArg arg = handler.getArg();
if (arg == null) {
code.add("unknown"); // throwing exception is too late at this point
} else if (arg instanceof RegisterArg) {
RegisterArg reg = (RegisterArg) arg;
code.add(mgen.getNameGen().assignArg(reg.getSVar().getCodeVar()));
} else if (arg instanceof NamedArg) {
code.add(mgen.getNameGen().assignNamedArg((NamedArg) arg));
} else {
throw new JadxRuntimeException("Unexpected arg type in catch block: " + arg + ", class: " + arg.getClass().getSimpleName());
}
code.add(") {");
makeRegionIndent(code, region);
@@ -3,9 +3,9 @@ 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.attributes.AFlag;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.PrimitiveType;
import jadx.core.dex.nodes.IDexNode;
import jadx.core.utils.StringUtils;
@@ -29,27 +29,39 @@ public class TypeGen {
return stype.getShortName();
}
/**
* Convert literal arg to string (preferred method)
*/
public static String literalToString(LiteralArg arg, IDexNode dexNode, boolean fallback) {
return literalToString(arg.getLiteral(), arg.getType(),
dexNode.root().getStringUtils(),
fallback,
arg.contains(AFlag.EXPLICIT_PRIMITIVE_TYPE));
}
/**
* Convert literal value to string according to value type
*
* @throws JadxRuntimeException for incorrect type or literal value
*/
public static String literalToString(long lit, ArgType type, 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, false);
}
@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, boolean cast) {
if (type == null || !type.isTypeKnown()) {
String n = Long.toString(lit);
if (Math.abs(lit) > 100) {
n += "; // 0x" + Long.toHexString(lit)
+ " float:" + Float.intBitsToFloat((int) lit)
+ " double:" + Double.longBitsToDouble(lit);
if (fallback && Math.abs(lit) > 100) {
StringBuilder sb = new StringBuilder();
sb.append(n).append("(0x").append(Long.toHexString(lit));
if (type == null || type.contains(PrimitiveType.FLOAT)) {
sb.append(", float:").append(Float.intBitsToFloat((int) lit));
}
if (type == null || type.contains(PrimitiveType.DOUBLE)) {
sb.append(", double:").append(Double.longBitsToDouble(lit));
}
sb.append(')');
return sb.toString();
}
return n;
}
@@ -58,19 +70,15 @@ public class TypeGen {
case BOOLEAN:
return lit == 0 ? "false" : "true";
case CHAR:
char ch = (char) lit;
if (!NameMapper.isPrintableChar(ch)) {
return Integer.toString(ch);
}
return stringUtils.unescapeChar(ch);
return stringUtils.unescapeChar((char) lit, cast);
case BYTE:
return formatByte((byte) lit);
return formatByte(lit, cast);
case SHORT:
return formatShort((short) lit);
return formatShort(lit, cast);
case INT:
return formatInteger((int) lit);
return formatInteger(lit, cast);
case LONG:
return formatLong(lit);
return formatLong(lit, cast);
case FLOAT:
return formatFloat(Float.intBitsToFloat((int) lit));
case DOUBLE:
@@ -89,37 +97,40 @@ public class TypeGen {
}
}
public static String formatShort(short s) {
if (s == Short.MAX_VALUE) {
public static String formatShort(long l, boolean cast) {
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);
String str = Long.toString(l);
return cast ? "(short) " + str : str;
}
public static String formatByte(byte b) {
if (b == Byte.MAX_VALUE) {
public static String formatByte(long l, boolean cast) {
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);
String str = Long.toString(l);
return cast ? "(byte) " + str : str;
}
public static String formatInteger(int i) {
if (i == Integer.MAX_VALUE) {
public static String formatInteger(long l, boolean cast) {
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);
String str = Long.toString(l);
return cast ? "(int) " + str : str;
}
public static String formatLong(long l) {
public static String formatLong(long l, boolean cast) {
if (l == Long.MAX_VALUE) {
return "Long.MAX_VALUE";
}
@@ -127,8 +138,8 @@ public class TypeGen {
return "Long.MIN_VALUE";
}
String str = Long.toString(l);
if (Math.abs(l) >= Integer.MAX_VALUE) {
str += "L";
if (cast || Math.abs(l) >= Integer.MAX_VALUE) {
return str + 'L';
}
return str;
}
@@ -152,7 +163,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 +185,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,225 @@
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.ICodeInfo;
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.getInputFileName());
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.getCodeStr());
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.getCodeStr());
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.getCodeStr());
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 cw = new CodeWriter();
try {
mthGen.addInstructions(cw);
} catch (Exception e) {
throw new JadxRuntimeException("Method generation error", e);
}
ICodeInfo code = cw.finish();
String codeStr = code.getCodeStr();
if (codeStr.isEmpty()) {
return Collections.emptyList();
}
String[] lines = codeStr.split(CodeWriter.NL);
Map<Integer, Integer> lineMapping = code.getLineMapping();
Map<CodePosition, Object> annotations = code.getAnnotations();
long mthCodeOffset = mth.getMethodCodeOffset() + 16;
int linesCount = lines.length;
List<JsonCodeLine> codeLines = new ArrayList<>(linesCount);
for (int i = 0; i < linesCount; i++) {
String codeLine = lines[i];
int line = i + 2;
JsonCodeLine jsonCodeLine = new JsonCodeLine();
jsonCodeLine.setCode(codeLine);
jsonCodeLine.setSourceLine(lineMapping.get(line));
Object obj = annotations.get(new CodePosition(line, 0));
if (obj instanceof InsnNode) {
long offset = ((InsnNode) obj).getOffset();
jsonCodeLine.setOffset("0x" + Long.toHexString(mthCodeOffset + offset * 2));
}
codeLines.add(jsonCodeLine);
}
return codeLines;
}
private String getTypeAlias(ArgType clsType) {
if (Objects.equals(clsType, ArgType.OBJECT)) {
return ArgType.OBJECT.getObject();
}
if (clsType.isObject()) {
ClassInfo classInfo = ClassInfo.fromType(root, clsType);
return classInfo.getAliasFullName();
}
return clsType.toString();
}
private String getClassTypeStr(ClassNode cls) {
if (cls.isEnum()) {
return "enum";
}
if (cls.getAccessFlags().isInterface()) {
return "interface";
}
return "class";
}
}
@@ -0,0 +1,107 @@
package jadx.core.codegen.json;
import java.io.File;
import java.io.FileWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import jadx.api.JadxArgs;
import jadx.core.codegen.json.mapping.JsonClsMapping;
import jadx.core.codegen.json.mapping.JsonFieldMapping;
import jadx.core.codegen.json.mapping.JsonMapping;
import jadx.core.codegen.json.mapping.JsonMthMapping;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.FileUtils;
public class JsonMappingGen {
private static final Logger LOG = LoggerFactory.getLogger(JsonMappingGen.class);
private static final Gson GSON = new GsonBuilder()
.setPrettyPrinting()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_DASHES)
.disableHtmlEscaping()
.create();
public static void dump(RootNode root) {
JsonMapping mapping = new JsonMapping();
fillMapping(mapping, root);
JadxArgs args = root.getArgs();
File outDirSrc = args.getOutDirSrc().getAbsoluteFile();
File mappingFile = new File(outDirSrc, "mapping.json");
FileUtils.makeDirsForFile(mappingFile);
try (Writer writer = new FileWriter(mappingFile)) {
GSON.toJson(mapping, writer);
LOG.info("Save mappings to {}", mappingFile.getAbsolutePath());
} catch (Exception e) {
throw new JadxRuntimeException("Failed to save mapping json", e);
}
}
private static void fillMapping(JsonMapping mapping, RootNode root) {
List<ClassNode> classes = root.getClasses(true);
mapping.setClasses(new ArrayList<>(classes.size()));
for (ClassNode cls : classes) {
ClassInfo classInfo = cls.getClassInfo();
JsonClsMapping jsonCls = new JsonClsMapping();
jsonCls.setName(classInfo.getRawName());
jsonCls.setAlias(classInfo.getAliasFullName());
jsonCls.setInner(classInfo.isInner());
jsonCls.setJson(cls.getTopParentClass().getClassInfo().getAliasFullPath() + ".json");
if (classInfo.isInner()) {
jsonCls.setTopClass(cls.getTopParentClass().getClassInfo().getFullName());
}
addFields(cls, jsonCls);
addMethods(cls, jsonCls);
mapping.getClasses().add(jsonCls);
}
}
private static void addMethods(ClassNode cls, JsonClsMapping jsonCls) {
List<MethodNode> methods = cls.getMethods();
if (methods.isEmpty()) {
return;
}
jsonCls.setMethods(new ArrayList<>(methods.size()));
for (MethodNode method : methods) {
JsonMthMapping jsonMethod = new JsonMthMapping();
MethodInfo methodInfo = method.getMethodInfo();
jsonMethod.setSignature(methodInfo.getShortId());
jsonMethod.setName(methodInfo.getName());
jsonMethod.setAlias(methodInfo.getAlias());
jsonMethod.setOffset("0x" + Long.toHexString(method.getMethodCodeOffset()));
jsonCls.getMethods().add(jsonMethod);
}
}
private static void addFields(ClassNode cls, JsonClsMapping jsonCls) {
List<FieldNode> fields = cls.getFields();
if (fields.isEmpty()) {
return;
}
jsonCls.setFields(new ArrayList<>(fields.size()));
for (FieldNode field : fields) {
JsonFieldMapping jsonField = new JsonFieldMapping();
jsonField.setName(field.getName());
jsonField.setAlias(field.getAlias());
jsonCls.getFields().add(jsonField);
}
}
private JsonMappingGen() {
}
}
@@ -0,0 +1,94 @@
package jadx.core.codegen.json.cls;
import java.util.List;
import com.google.gson.annotations.SerializedName;
public class JsonClass extends JsonNode {
@SerializedName("package")
private String pkg;
private String type; // class, interface, enum
@SerializedName("extends")
private String superClass;
@SerializedName("implements")
private List<String> interfaces;
private String dex;
private List<JsonField> fields;
private List<JsonMethod> methods;
private List<JsonClass> innerClasses;
private List<String> imports;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getSuperClass() {
return superClass;
}
public void setSuperClass(String superClass) {
this.superClass = superClass;
}
public List<String> getInterfaces() {
return interfaces;
}
public void setInterfaces(List<String> interfaces) {
this.interfaces = interfaces;
}
public List<JsonField> getFields() {
return fields;
}
public void setFields(List<JsonField> fields) {
this.fields = fields;
}
public List<JsonMethod> getMethods() {
return methods;
}
public void setMethods(List<JsonMethod> methods) {
this.methods = methods;
}
public List<JsonClass> getInnerClasses() {
return innerClasses;
}
public void setInnerClasses(List<JsonClass> innerClasses) {
this.innerClasses = innerClasses;
}
public String getPkg() {
return pkg;
}
public void setPkg(String pkg) {
this.pkg = pkg;
}
public String getDex() {
return dex;
}
public void setDex(String dex) {
this.dex = dex;
}
public List<String> getImports() {
return imports;
}
public void setImports(List<String> imports) {
this.imports = imports;
}
}
@@ -0,0 +1,33 @@
package jadx.core.codegen.json.cls;
import org.jetbrains.annotations.Nullable;
public class JsonCodeLine {
private String code;
private String offset;
private Integer sourceLine;
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getOffset() {
return offset;
}
public void setOffset(String offset) {
this.offset = offset;
}
public Integer getSourceLine() {
return sourceLine;
}
public void setSourceLine(@Nullable Integer sourceLine) {
this.sourceLine = sourceLine;
}
}
@@ -0,0 +1,5 @@
package jadx.core.codegen.json.cls;
public class JsonField extends JsonNode {
String type;
}
@@ -0,0 +1,51 @@
package jadx.core.codegen.json.cls;
import java.util.List;
public class JsonMethod extends JsonNode {
private String signature;
private String returnType;
private List<String> arguments;
private List<JsonCodeLine> lines;
private String offset;
public String getSignature() {
return signature;
}
public void setSignature(String signature) {
this.signature = signature;
}
public String getReturnType() {
return returnType;
}
public void setReturnType(String returnType) {
this.returnType = returnType;
}
public List<String> getArguments() {
return arguments;
}
public void setArguments(List<String> arguments) {
this.arguments = arguments;
}
public List<JsonCodeLine> getLines() {
return lines;
}
public void setLines(List<JsonCodeLine> lines) {
this.lines = lines;
}
public String getOffset() {
return offset;
}
public void setOffset(String offset) {
this.offset = offset;
}
}
@@ -0,0 +1,40 @@
package jadx.core.codegen.json.cls;
public class JsonNode {
private String name;
private String alias;
private String declaration;
private int accessFlags;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAlias() {
return alias;
}
public void setAlias(String alias) {
this.alias = alias;
}
public String getDeclaration() {
return declaration;
}
public void setDeclaration(String declaration) {
this.declaration = declaration;
}
public int getAccessFlags() {
return accessFlags;
}
public void setAccessFlags(int accessFlags) {
this.accessFlags = accessFlags;
}
}
@@ -0,0 +1,71 @@
package jadx.core.codegen.json.mapping;
import java.util.List;
public class JsonClsMapping {
private String name;
private String alias;
private String json;
private boolean inner;
private String topClass;
private List<JsonFieldMapping> fields;
private List<JsonMthMapping> methods;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAlias() {
return alias;
}
public void setAlias(String alias) {
this.alias = alias;
}
public String getJson() {
return json;
}
public void setJson(String json) {
this.json = json;
}
public boolean isInner() {
return inner;
}
public void setInner(boolean inner) {
this.inner = inner;
}
public String getTopClass() {
return topClass;
}
public void setTopClass(String topClass) {
this.topClass = topClass;
}
public List<JsonFieldMapping> getFields() {
return fields;
}
public void setFields(List<JsonFieldMapping> fields) {
this.fields = fields;
}
public List<JsonMthMapping> getMethods() {
return methods;
}
public void setMethods(List<JsonMthMapping> methods) {
this.methods = methods;
}
}
@@ -0,0 +1,22 @@
package jadx.core.codegen.json.mapping;
public class JsonFieldMapping {
private String name;
private String alias;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAlias() {
return alias;
}
public void setAlias(String alias) {
this.alias = alias;
}
}
@@ -0,0 +1,15 @@
package jadx.core.codegen.json.mapping;
import java.util.List;
public class JsonMapping {
private List<JsonClsMapping> classes;
public List<JsonClsMapping> getClasses() {
return classes;
}
public void setClasses(List<JsonClsMapping> classes) {
this.classes = classes;
}
}
@@ -0,0 +1,40 @@
package jadx.core.codegen.json.mapping;
public class JsonMthMapping {
private String signature;
private String name;
private String alias;
private String offset;
public String getSignature() {
return signature;
}
public void setSignature(String signature) {
this.signature = signature;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAlias() {
return alias;
}
public void setAlias(String alias) {
this.alias = alias;
}
public String getOffset() {
return offset;
}
public void setOffset(String offset) {
this.offset = offset;
}
}
@@ -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);
}
}
@@ -112,18 +115,20 @@ class DeobfPresets {
for (DeobfClsInfo deobfClsInfo : deobfuscator.getClsMap().values()) {
if (deobfClsInfo.getAlias() != null) {
list.add(String.format("c %s = %s",
deobfClsInfo.getCls().getClassInfo().getFullName(), deobfClsInfo.getAlias()));
deobfClsInfo.getCls().getClassInfo().makeRawFullName(), deobfClsInfo.getAlias()));
}
}
for (FieldInfo fld : deobfuscator.getFldMap().keySet()) {
list.add(String.format("f %s = %s", fld.getFullId(), fld.getAlias()));
list.add(String.format("f %s = %s", fld.getRawFullId(), fld.getAlias()));
}
for (MethodInfo mth : deobfuscator.getMthMap().keySet()) {
list.add(String.format("m %s = %s", mth.getFullId(), mth.getAlias()));
list.add(String.format("m %s = %s", mth.getRawFullId(), mth.getAlias()));
}
Collections.sort(list);
FileUtils.writeLines(deobfMapFile, MAP_FILE_CHARSET, list);
list.clear();
Files.write(deobfMapFile, list, MAP_FILE_CHARSET);
if (LOG.isDebugEnabled()) {
LOG.debug("Deobfuscation map file saved as: {}", deobfMapFile);
}
}
private static void dfsPackageName(List<String> list, String prefix, PackageNode node) {
@@ -136,15 +141,15 @@ class DeobfPresets {
}
public String getForCls(ClassInfo cls) {
return clsPresetMap.get(cls.getFullName());
return clsPresetMap.get(cls.makeRawFullName());
}
public String getForFld(FieldInfo fld) {
return fldPresetMap.get(fld.getFullId());
return fldPresetMap.get(fld.getRawFullId());
}
public String getForMth(MethodInfo mth) {
return mthPresetMap.get(mth.getFullId());
return mthPresetMap.get(mth.getRawFullId());
}
public void clear() {
@@ -1,22 +1,23 @@
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;
import java.util.Set;
import java.util.TreeSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.SourceFileAttr;
import jadx.core.dex.info.ClassInfo;
@@ -24,9 +25,10 @@ import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.kotlin.KotlinMetadataUtils;
public class Deobfuscator {
private static final Logger LOG = LoggerFactory.getLogger(Deobfuscator.class);
@@ -37,11 +39,10 @@ public class Deobfuscator {
public static final String INNER_CLASS_SEPARATOR = "$";
private final JadxArgs args;
@NotNull
private final List<DexNode> dexNodes;
private final RootNode root;
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<>();
@@ -55,19 +56,21 @@ public class Deobfuscator {
private final int maxLength;
private final int minLength;
private final boolean useSourceNameAsAlias;
private final boolean parseKotlinMetadata;
private int pkgIndex = 0;
private int clsIndex = 0;
private int fldIndex = 0;
private int mthIndex = 0;
public Deobfuscator(JadxArgs args, @NotNull List<DexNode> dexNodes, File deobfMapFile) {
public Deobfuscator(JadxArgs args, RootNode root, Path deobfMapFile) {
this.args = args;
this.dexNodes = dexNodes;
this.root = root;
this.minLength = args.getDeobfuscationMinLength();
this.maxLength = args.getDeobfuscationMaxLength();
this.useSourceNameAsAlias = args.isUseSourceNameAsClassAlias();
this.parseKotlinMetadata = args.isParseKotlinMetadata();
this.deobfPresets = new DeobfPresets(this, deobfMapFile);
}
@@ -78,8 +81,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() {
@@ -90,15 +105,11 @@ public class Deobfuscator {
}
private void preProcess() {
for (DexNode dexNode : dexNodes) {
for (ClassNode cls : dexNode.getClasses()) {
Collections.addAll(reservedClsNames, cls.getPackage().split("\\."));
}
for (ClassNode cls : root.getClasses()) {
Collections.addAll(reservedClsNames, cls.getPackage().split("\\."));
}
for (DexNode dexNode : dexNodes) {
for (ClassNode cls : dexNode.getClasses()) {
preProcessClass(cls);
}
for (ClassNode cls : root.getClasses()) {
preProcessClass(cls);
}
}
@@ -107,10 +118,8 @@ public class Deobfuscator {
if (DEBUG) {
dumpAlias();
}
for (DexNode dexNode : dexNodes) {
for (ClassNode cls : dexNode.getClasses()) {
processClass(cls);
}
for (ClassNode cls : root.getClasses()) {
processClass(cls);
}
postProcess();
}
@@ -128,8 +137,8 @@ public class Deobfuscator {
}
for (MethodInfo mth : o.getMethods()) {
if (aliasToUse == null) {
if (mth.isRenamed() && !mth.isAliasFromPreset()) {
mth.setAlias(String.format("mo%d%s", id, makeName(mth.getName())));
if (mth.hasAlias() && !mth.isAliasFromPreset()) {
mth.setAlias(String.format("mo%d%s", id, prepareNamePart(mth.getName())));
}
aliasToUse = mth.getAlias();
}
@@ -140,16 +149,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);
@@ -203,14 +202,14 @@ public class Deobfuscator {
if (added) {
ArgType superClass = cls.getSuperClass();
if (superClass != null) {
ClassNode superNode = cls.dex().resolveClass(superClass);
ClassNode superNode = cls.root().resolveClass(superClass);
if (superNode != null) {
collectClassHierarchy(superNode, collected);
}
}
for (ArgType argType : cls.getInterfaces()) {
ClassNode interfaceNode = cls.dex().resolveClass(argType);
ClassNode interfaceNode = cls.root().resolveClass(argType);
if (interfaceNode != null) {
collectClassHierarchy(interfaceNode, collected);
}
@@ -219,12 +218,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 +250,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) {
@@ -243,7 +258,11 @@ public class Deobfuscator {
}
}
public void renameMethod(MethodNode mth) {
public void forceRenameField(FieldNode field) {
field.getFieldInfo().setAlias(makeFieldAlias(field));
}
private void renameMethod(MethodNode mth) {
String alias = getMethodAlias(mth);
if (alias != null) {
mth.getMethodInfo().setAlias(alias);
@@ -253,6 +272,13 @@ public class Deobfuscator {
}
}
public void forceRenameMethod(MethodNode mth) {
mth.getMethodInfo().setAlias(makeMethodAlias(mth));
if (mth.isVirtual()) {
resolveOverriding(mth);
}
}
public void addPackagePreset(String origPkgName, String pkgAlias) {
PackageNode pkg = getPackageNode(origPkgName, true);
pkg.setAlias(pkgAlias);
@@ -263,7 +289,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)) {
@@ -314,7 +341,7 @@ public class Deobfuscator {
ClassInfo classInfo = cls.getClassInfo();
String pkgFullName = classInfo.getPackage();
PackageNode pkg = getPackageNode(pkgFullName, true);
doPkg(pkg, pkgFullName);
processPackageFull(pkg, pkgFullName);
String alias = deobfPresets.getForCls(classInfo);
if (alias != null) {
@@ -322,9 +349,8 @@ public class Deobfuscator {
} else {
if (!clsMap.containsKey(classInfo)) {
String clsShortName = classInfo.getShortName();
if (shouldRename(clsShortName) || reservedClsNames.contains(clsShortName)) {
makeClsAlias(cls);
}
boolean badName = shouldRename(clsShortName) || reservedClsNames.contains(clsShortName);
makeClsAlias(cls, badName);
}
}
for (ClassNode innerCls : cls.getInnerClasses()) {
@@ -337,26 +363,115 @@ public class Deobfuscator {
if (deobfClsInfo != null) {
return deobfClsInfo.getAlias();
}
return makeClsAlias(cls);
return makeClsAlias(cls, true);
}
private String makeClsAlias(ClassNode cls) {
public String getPkgAlias(ClassNode cls) {
ClassInfo classInfo = cls.getClassInfo();
String alias = null;
PackageNode pkg;
DeobfClsInfo deobfClsInfo = clsMap.get(classInfo);
if (deobfClsInfo != null) {
pkg = deobfClsInfo.getPkg();
} else {
String fullPkgName = classInfo.getPackage();
pkg = getPackageNode(fullPkgName, true);
processPackageFull(pkg, fullPkgName);
}
if (pkg.hasAnyAlias()) {
return pkg.getFullAlias();
} else {
return pkg.getFullName();
}
}
if (this.useSourceNameAsAlias) {
private String makeClsAlias(ClassNode cls, boolean badName) {
String alias = null;
String pkgName = null;
if (this.parseKotlinMetadata) {
ClassInfo kotlinCls = KotlinMetadataUtils.getClassName(cls);
if (kotlinCls != null) {
alias = prepareNameFull(kotlinCls.getShortName(), "C");
pkgName = kotlinCls.getPackage();
}
}
if (alias == null && this.useSourceNameAsAlias) {
alias = getAliasFromSourceFile(cls);
}
ClassInfo classInfo = cls.getClassInfo();
if (alias == null) {
String clsName = classInfo.getShortName();
alias = String.format("C%04d%s", clsIndex++, makeName(clsName));
if (badName) {
String clsName = classInfo.getShortName();
String prefix = makeClsPrefix(cls);
alias = String.format("%sC%04d%s", prefix, clsIndex++, prepareNamePart(clsName));
} else {
// rename not needed
return classInfo.getShortName();
}
}
PackageNode pkg = getPackageNode(classInfo.getPackage(), true);
if (pkgName == null) {
pkgName = classInfo.getPackage();
}
PackageNode pkg = getPackageNode(pkgName, true);
clsMap.put(classInfo, new DeobfClsInfo(this, cls, pkg, alias));
return alias;
}
/**
* Generate a prefix for a class name that bases on certain class properties, certain
* extended superclasses or implemented interfaces.
*/
private String makeClsPrefix(ClassNode cls) {
if (cls.isEnum()) {
return "Enum";
}
String result = "";
if (cls.getAccessFlags().isAbstract()) {
result += "Abstract";
}
// Process current class and all super classes
ClassNode currentCls = cls;
outerLoop: while (currentCls != null) {
if (currentCls.getSuperClass() != null) {
String superClsName = currentCls.getSuperClass().getObject();
if (superClsName.startsWith("android.app.")) {
// e.g. Activity or Fragment
result += superClsName.substring(12);
break;
} else if (superClsName.startsWith("android.os.")) {
// e.g. AsyncTask
result += superClsName.substring(11);
break;
}
}
for (ArgType intf : cls.getInterfaces()) {
String intfClsName = intf.getObject();
if (intfClsName.equals("java.lang.Runnable")) {
result += "Runnable";
break outerLoop;
} else if (intfClsName.startsWith("java.util.concurrent.")) {
// e.g. Callable
result += intfClsName.substring(21);
break outerLoop;
} else if (intfClsName.startsWith("android.view.")) {
// e.g. View.OnClickListener
result += intfClsName.substring(13);
break outerLoop;
} else if (intfClsName.startsWith("android.content.")) {
// e.g. DialogInterface.OnClickListener
result += intfClsName.substring(16);
break outerLoop;
}
}
if (currentCls.getSuperClass() == null) {
break;
}
currentCls = cls.root().resolveClass(currentCls.getSuperClass());
}
return result;
}
@Nullable
private String getAliasFromSourceFile(ClassNode cls) {
SourceFileAttr sourceFileAttr = cls.get(AType.SOURCE_FILE);
@@ -372,7 +487,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()) {
@@ -380,7 +495,7 @@ public class Deobfuscator {
return null;
}
}
ClassNode otherCls = cls.dex().root().searchClassByName(cls.getPackage() + "." + name);
ClassNode otherCls = cls.root().resolveClass(cls.getPackage() + '.' + name);
if (otherCls != null) {
return null;
}
@@ -409,6 +524,9 @@ public class Deobfuscator {
@Nullable
private String getMethodAlias(MethodNode mth) {
MethodInfo methodInfo = mth.getMethodInfo();
if (methodInfo.isClassInit() || methodInfo.isConstructor()) {
return null;
}
String alias = mthMap.get(methodInfo);
if (alias != null) {
return alias;
@@ -426,18 +544,18 @@ public class Deobfuscator {
}
public String makeFieldAlias(FieldNode field) {
String alias = String.format("f%d%s", fldIndex++, makeName(field.getName()));
String alias = String.format("f%d%s", fldIndex++, prepareNamePart(field.getName()));
fldMap.put(field.getFieldInfo(), alias);
return alias;
}
public String makeMethodAlias(MethodNode mth) {
String alias = String.format("m%d%s", mthIndex++, makeName(mth.getName()));
String alias = String.format("m%d%s", mthIndex++, prepareNamePart(mth.getName()));
mthMap.put(mth.getMethodInfo(), alias);
return alias;
}
private void doPkg(PackageNode pkg, String fullName) {
private void processPackageFull(PackageNode pkg, String fullName) {
if (pkgSet.contains(fullName)) {
return;
}
@@ -447,47 +565,50 @@ public class Deobfuscator {
PackageNode parentPkg = pkg.getParentPackage();
while (!parentPkg.getName().isEmpty()) {
if (!parentPkg.hasAlias()) {
doPkg(parentPkg, parentPkg.getFullName());
processPackageFull(parentPkg, parentPkg.getFullName());
}
parentPkg = parentPkg.getParentPackage();
}
String pkgName = pkg.getName();
if (!pkg.hasAlias() && shouldRename(pkgName)) {
String pkgAlias = String.format("p%03d%s", pkgIndex++, makeName(pkgName));
pkg.setAlias(pkgAlias);
if (!pkg.hasAlias()) {
String pkgName = pkg.getName();
if ((args.isDeobfuscationOn() && shouldRename(pkgName))
|| (args.isRenameValid() && !NameMapper.isValidIdentifier(pkgName))
|| (args.isRenamePrintable() && !NameMapper.isAllCharsPrintable(pkgName))) {
String pkgAlias = String.format("p%03d%s", pkgIndex++, prepareNamePart(pkg.getName()));
pkg.setAlias(pkgAlias);
}
}
}
private boolean shouldRename(String s) {
return s.length() > maxLength
|| s.length() < minLength
|| NameMapper.isReserved(s)
|| !NameMapper.isAllCharsPrintable(s);
int len = s.length();
return len < minLength || len > maxLength;
}
private String makeName(String name) {
private String prepareNamePart(String name) {
if (name.length() > maxLength) {
return "x" + Integer.toHexString(name.hashCode());
return 'x' + Integer.toHexString(name.hashCode());
}
if (NameMapper.isReserved(name)) {
return name;
}
if (!NameMapper.isAllCharsPrintable(name)) {
return removeInvalidChars(name);
}
return name;
return NameMapper.removeInvalidCharsMiddle(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);
}
private String prepareNameFull(String name, String prefix) {
if (name.length() > maxLength) {
return makeHashName(name, prefix);
}
return sb.toString();
String result = NameMapper.removeInvalidChars(name, prefix);
if (result.isEmpty()) {
return makeHashName(name, prefix);
}
if (NameMapper.isReserved(result)) {
return prefix + result;
}
return result;
}
private static String makeHashName(String name, String invalidPrefix) {
return invalidPrefix + 'x' + Integer.toHexString(name.hashCode());
}
private void dumpClassAlias(ClassNode cls) {
@@ -503,10 +624,8 @@ public class Deobfuscator {
}
private void dumpAlias() {
for (DexNode dexNode : dexNodes) {
for (ClassNode cls : dexNode.getClasses()) {
dumpClassAlias(cls);
}
for (ClassNode cls : root.getClasses()) {
dumpClassAlias(cls);
}
}
@@ -527,10 +646,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();
@@ -553,4 +669,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);
@@ -79,14 +77,26 @@ public class NameMapper {
public static boolean isValidIdentifier(String str) {
return notEmpty(str)
&& VALID_JAVA_IDENTIFIER.matcher(str).matches()
&& isAllCharsPrintable(str);
&& !isReserved(str)
&& VALID_JAVA_IDENTIFIER.matcher(str).matches();
}
public static boolean isValidFullIdentifier(String str) {
return notEmpty(str)
&& VALID_JAVA_FULL_IDENTIFIER.matcher(str).matches()
&& isAllCharsPrintable(str);
&& !isReserved(str)
&& VALID_JAVA_FULL_IDENTIFIER.matcher(str).matches();
}
public static boolean isValidAndPrintable(String str) {
return isValidIdentifier(str) && isAllCharsPrintable(str);
}
public static boolean isValidIdentifierStart(int codePoint) {
return Character.isJavaIdentifierStart(codePoint);
}
public static boolean isValidIdentifierPart(int codePoint) {
return Character.isJavaIdentifierPart(codePoint);
}
public static boolean isPrintableChar(int c) {
@@ -103,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() {
}
}
@@ -6,7 +6,7 @@ import jadx.core.dex.info.MethodInfo;
class OverridedMethodsNode {
private Set<MethodInfo> methods;
private final Set<MethodInfo> methods;
public OverridedMethodsNode(Set<MethodInfo> methodsSet) {
methods = methodsSet;
@@ -31,14 +31,17 @@ public class PackageNode {
public String getFullName() {
if (cachedPackageFullName == null) {
Deque<PackageNode> pp = getParentPackages();
StringBuilder result = new StringBuilder();
result.append(pp.pop().getName());
while (!pp.isEmpty()) {
result.append(SEPARATOR_CHAR);
if (pp.isEmpty()) {
cachedPackageFullName = "";
} else {
StringBuilder result = new StringBuilder();
result.append(pp.pop().getName());
while (!pp.isEmpty()) {
result.append(SEPARATOR_CHAR);
result.append(pp.pop().getName());
}
cachedPackageFullName = result.toString();
}
cachedPackageFullName = result.toString();
}
return cachedPackageFullName;
}
@@ -58,6 +61,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 +140,9 @@ public class PackageNode {
}
return pp;
}
@Override
public String toString() {
return packageAlias;
}
}
@@ -1,6 +1,7 @@
package jadx.core.dex.attributes;
public enum AFlag {
MTH_ENTER_BLOCK,
TRY_ENTER,
TRY_LEAVE,
@@ -8,25 +9,49 @@ 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_INLINE_CONST,
DONT_GENERATE, // process as usual, but don't output to generated code
COMMENT_OUT, // process as usual, but comment insn in generated code
REMOVE, // can be completely removed
HIDDEN, // instruction used inside other instruction but not listed in args
DONT_RENAME, // do not rename during deobfuscation
ADDED_TO_REGION,
FINALLY_INSNS,
SKIP_FIRST_ARG,
SKIP_ARG, // skip argument in invoke call
ANONYMOUS_CONSTRUCTOR,
ANONYMOUS_CLASS,
THIS,
SUPER,
/**
* RegisterArg attribute for method arguments
*/
METHOD_ARGUMENT,
/**
* Type of RegisterArg or SSAVar can't be changed
*/
IMMUTABLE_TYPE,
/**
* Force inline instruction with inline assign
*/
FORCE_ASSIGN_INLINE,
CUSTOM_DECLARE, // variable for this register don't need declaration
DECLARE_VAR,
ELSE_IF_CHAIN,
@@ -35,5 +60,23 @@ public enum AFlag {
FALL_THROUGH,
VARARG_CALL,
/**
* Use constants with explicit type: cast '(byte) 1' or type letter '7L'
*/
EXPLICIT_PRIMITIVE_TYPE,
EXPLICIT_CAST,
SOFT_CAST, // synthetic cast to help type inference (allow unchecked casts for generics)
INCONSISTENT_CODE, // warning about incorrect decompilation
REQUEST_IF_REGION_OPTIMIZE, // run if region visitor again
// Class processing flags
RESTART_CODEGEN, // codegen must be executed again
RELOAD_AT_CODEGEN_STAGE, // class can't be analyzed at 'process' stage => unload before 'codegen' stage
CLASS_DEEP_RELOAD, // perform deep class unload (reload) before process
DONT_UNLOAD_CLASS, // don't unload class after code generation (only for tests and debug!)
}
@@ -1,5 +1,9 @@
package jadx.core.dex.attributes;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import jadx.core.dex.attributes.annotations.AnnotationsList;
import jadx.core.dex.attributes.annotations.MethodParameters;
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
@@ -8,16 +12,22 @@ import jadx.core.dex.attributes.nodes.EnumClassAttr;
import jadx.core.dex.attributes.nodes.EnumMapAttr;
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
import jadx.core.dex.attributes.nodes.ForceReturnAttr;
import jadx.core.dex.attributes.nodes.GenericInfoAttr;
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.MethodOverrideAttr;
import jadx.core.dex.attributes.nodes.MethodTypeVarsAttr;
import jadx.core.dex.attributes.nodes.PhiListAttr;
import jadx.core.dex.attributes.nodes.RegDebugInfoAttr;
import jadx.core.dex.attributes.nodes.RenameReasonAttr;
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
import jadx.core.dex.attributes.nodes.SourceFileAttr;
import jadx.core.dex.nodes.parser.FieldInitAttr;
import jadx.core.dex.nodes.IMethodDetails;
import jadx.core.dex.trycatch.CatchAttr;
import jadx.core.dex.trycatch.ExcHandlerAttr;
import jadx.core.dex.trycatch.SplitterBlockAttr;
@@ -28,29 +38,61 @@ import jadx.core.dex.trycatch.SplitterBlockAttr;
*
* @param <T> attribute class implementation
*/
@SuppressWarnings("InstantiationOfUtilityClass")
public class AType<T extends IAttribute> {
public static final AType<AttrList<JumpInfo>> JUMP = new AType<>();
// class, method, field
public static final AType<AnnotationsList> ANNOTATION_LIST = new AType<>();
public static final AType<RenameReasonAttr> RENAME_REASON = new AType<>();
// class, method
public static final AType<AttrList<JadxError>> JADX_ERROR = new AType<>(); // code failed to decompile completely
public static final AType<AttrList<String>> JADX_WARN = new AType<>(); // mark code as inconsistent (code can be viewed)
public static final AType<AttrList<String>> COMMENTS = new AType<>(); // any additional info about decompilation
// class
public static final AType<SourceFileAttr> SOURCE_FILE = new AType<>();
public static final AType<EnumClassAttr> ENUM_CLASS = new AType<>();
public static final AType<EnumMapAttr> ENUM_MAP = new AType<>();
// field
public static final AType<FieldInitAttr> FIELD_INIT = new AType<>();
public static final AType<FieldReplaceAttr> FIELD_REPLACE = new AType<>();
// method
public static final AType<LocalVarsDebugInfoAttr> LOCAL_VARS_DEBUG_INFO = new AType<>();
public static final AType<MethodInlineAttr> METHOD_INLINE = new AType<>();
public static final AType<MethodParameters> ANNOTATION_MTH_PARAMETERS = new AType<>();
public static final AType<SkipMethodArgsAttr> SKIP_MTH_ARGS = new AType<>();
public static final AType<MethodOverrideAttr> METHOD_OVERRIDE = new AType<>();
public static final AType<MethodTypeVarsAttr> METHOD_TYPE_VARS = new AType<>();
// region
public static final AType<DeclareVariablesAttr> DECLARE_VARIABLES = new AType<>();
// block
public static final AType<PhiListAttr> PHI_LIST = new AType<>();
public static final AType<IgnoreEdgeAttr> IGNORE_EDGE = new AType<>();
public static final AType<ForceReturnAttr> FORCE_RETURN = new AType<>();
public static final AType<CatchAttr> CATCH_BLOCK = new AType<>();
public static final AType<SplitterBlockAttr> SPLITTER_BLOCK = new AType<>();
public static final AType<AttrList<LoopInfo>> LOOP = new AType<>();
public static final AType<AttrList<EdgeInsnAttr>> EDGE_INSN = new AType<>();
public static final AType<AttrList<JadxError>> JADX_ERROR = new AType<>();
public static final AType<AttrList<JadxWarn>> JADX_WARN = new AType<>();
// block or insn
public static final AType<ExcHandlerAttr> EXC_HANDLER = new AType<>();
public static final AType<CatchAttr> CATCH_BLOCK = new AType<>();
public static final AType<SplitterBlockAttr> SPLITTER_BLOCK = new AType<>();
public static final AType<ForceReturnAttr> FORCE_RETURN = new AType<>();
public static final AType<FieldInitAttr> FIELD_INIT = new AType<>();
public static final AType<FieldReplaceAttr> FIELD_REPLACE = new AType<>();
public static final AType<MethodInlineAttr> METHOD_INLINE = new AType<>();
public static final AType<EnumClassAttr> ENUM_CLASS = new AType<>();
public static final AType<EnumMapAttr> ENUM_MAP = new AType<>();
public static final AType<AnnotationsList> ANNOTATION_LIST = new AType<>();
public static final AType<MethodParameters> ANNOTATION_MTH_PARAMETERS = new AType<>();
public static final AType<PhiListAttr> PHI_LIST = new AType<>();
public static final AType<SourceFileAttr> SOURCE_FILE = new AType<>();
public static final AType<DeclareVariablesAttr> DECLARE_VARIABLES = new AType<>();
// instruction
public static final AType<LoopLabelAttr> LOOP_LABEL = new AType<>();
public static final AType<IgnoreEdgeAttr> IGNORE_EDGE = new AType<>();
public static final AType<AttrList<JumpInfo>> JUMP = new AType<>();
public static final AType<IMethodDetails> METHOD_DETAILS = new AType<>();
public static final AType<GenericInfoAttr> GENERIC_INFO = new AType<>();
// register
public static final AType<RegDebugInfoAttr> REG_DEBUG_INFO = new AType<>();
public static final Set<AType<?>> SKIP_ON_UNLOAD = new HashSet<>(Arrays.asList(
FIELD_REPLACE,
METHOD_INLINE,
SKIP_MTH_ARGS));
}
@@ -3,6 +3,7 @@ package jadx.core.dex.attributes;
import java.util.ArrayList;
import java.util.List;
import jadx.core.codegen.CodeWriter;
import jadx.core.utils.Utils;
public class AttrList<T> implements IAttribute {
@@ -25,6 +26,6 @@ public class AttrList<T> implements IAttribute {
@Override
public String toString() {
return Utils.listToString(list);
return Utils.listToString(list, CodeWriter.NL);
}
}
@@ -2,7 +2,7 @@ package jadx.core.dex.attributes;
import java.util.List;
import jadx.core.dex.attributes.annotations.Annotation;
import jadx.api.plugins.input.data.annotations.IAnnotation;
public abstract class AttrNode implements IAttributeNode {
@@ -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);
@@ -58,7 +64,7 @@ public abstract class AttrNode implements IAttributeNode {
}
@Override
public Annotation getAnnotation(String cls) {
public IAnnotation getAnnotation(String cls) {
return storage.getAnnotation(cls);
}
@@ -70,21 +76,36 @@ public abstract class AttrNode implements IAttributeNode {
@Override
public void remove(AFlag flag) {
storage.remove(flag);
unloadIfEmpty();
}
@Override
public <T extends IAttribute> void remove(AType<T> type) {
storage.remove(type);
unloadIfEmpty();
}
@Override
public void removeAttr(IAttribute attr) {
storage.remove(attr);
unloadIfEmpty();
}
@Override
public void clearAttributes() {
storage.clear();
unloadIfEmpty();
}
/**
* Remove all attribute with exceptions from {@link AType#SKIP_ON_UNLOAD}
*/
public void unloadAttributes() {
if (storage == EMPTY_ATTR_STORAGE) {
return;
}
storage.unloadAttributes();
unloadIfEmpty();
}
@Override
@@ -97,6 +118,7 @@ public abstract class AttrNode implements IAttributeNode {
return storage.toString();
}
@Override
public boolean isAttrStorageEmpty() {
return storage.isEmpty();
}
@@ -8,9 +8,10 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import jadx.core.dex.attributes.annotations.Annotation;
import jadx.api.plugins.input.data.annotations.IAnnotation;
import jadx.core.dex.attributes.annotations.AnnotationsList;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
/**
* Storage for different attribute types:
@@ -19,12 +20,19 @@ import jadx.core.utils.Utils;
*/
public class AttributeStorage {
static {
int flagsCount = AFlag.values().length;
if (flagsCount >= 64) {
throw new JadxRuntimeException("Try to reduce flags count to 64 for use one long in EnumSet, now " + flagsCount);
}
}
private final Set<AFlag> flags;
private final Map<AType<?>, IAttribute> attributes;
private Map<AType<?>, IAttribute> attributes;
public AttributeStorage() {
flags = EnumSet.noneOf(AFlag.class);
attributes = new IdentityHashMap<>();
attributes = Collections.emptyMap();
}
public void add(AFlag flag) {
@@ -32,7 +40,7 @@ public class AttributeStorage {
}
public void add(IAttribute attr) {
attributes.put(attr.getType(), attr);
writeAttributes().put(attr.getType(), attr);
}
public <T> void add(AType<AttrList<T>> type, T obj) {
@@ -46,7 +54,7 @@ public class AttributeStorage {
public void addAll(AttributeStorage otherList) {
flags.addAll(otherList.flags);
attributes.putAll(otherList.attributes);
writeAttributes().putAll(otherList.attributes);
}
public boolean contains(AFlag flag) {
@@ -62,7 +70,7 @@ public class AttributeStorage {
return (T) attributes.get(type);
}
public Annotation getAnnotation(String cls) {
public IAnnotation getAnnotation(String cls) {
AnnotationsList aList = get(AType.ANNOTATION_LIST);
return aList == null ? null : aList.get(cls);
}
@@ -80,20 +88,41 @@ public class AttributeStorage {
}
public <T extends IAttribute> void remove(AType<T> type) {
attributes.remove(type);
}
public void remove(IAttribute attr) {
AType<? extends IAttribute> type = attr.getType();
IAttribute a = attributes.get(type);
if (a == attr) {
if (!attributes.isEmpty()) {
attributes.remove(type);
}
}
public void remove(IAttribute attr) {
if (!attributes.isEmpty()) {
AType<? extends IAttribute> type = attr.getType();
IAttribute a = attributes.get(type);
if (a == attr) {
attributes.remove(type);
}
}
}
private Map<AType<?>, IAttribute> writeAttributes() {
if (attributes.isEmpty()) {
attributes = new IdentityHashMap<>(5);
}
return attributes;
}
public void clear() {
flags.clear();
attributes.clear();
if (!attributes.isEmpty()) {
attributes.clear();
}
}
public synchronized void unloadAttributes() {
if (attributes.isEmpty()) {
return;
}
Set<AType<?>> skipOnUnload = AType.SKIP_ON_UNLOAD;
attributes.keySet().removeIf(attrType -> !skipOnUnload.contains(attrType));
}
public List<String> getAttributeStrings() {
@@ -106,7 +135,7 @@ public class AttributeStorage {
list.add(a.toString());
}
for (IAttribute a : attributes.values()) {
list.add(a.toString());
list.add(a.toAttrString());
}
return list;
}
@@ -121,6 +150,7 @@ public class AttributeStorage {
if (list.isEmpty()) {
return "";
}
return "A:{" + Utils.listToString(list) + "}";
list.sort(String::compareTo);
return "A[" + Utils.listToString(list) + ']';
}
}
@@ -3,7 +3,7 @@ package jadx.core.dex.attributes;
import java.util.Collections;
import java.util.List;
import jadx.core.dex.attributes.annotations.Annotation;
import jadx.api.plugins.input.data.annotations.IAnnotation;
public final class EmptyAttrStorage extends AttributeStorage {
@@ -23,7 +23,7 @@ public final class EmptyAttrStorage extends AttributeStorage {
}
@Override
public Annotation getAnnotation(String cls) {
public IAnnotation getAnnotation(String cls) {
return null;
}
@@ -34,18 +34,22 @@ public final class EmptyAttrStorage extends AttributeStorage {
@Override
public void clear() {
// ignore
}
@Override
public void remove(AFlag flag) {
// ignore
}
@Override
public <T extends IAttribute> void remove(AType<T> type) {
// ignore
}
@Override
public void remove(IAttribute attr) {
// ignore
}
@Override
@@ -1,18 +1,16 @@
package jadx.core.dex.nodes.parser;
package jadx.core.dex.attributes;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
public class FieldInitAttr implements IAttribute {
public static final FieldInitAttr NULL_VALUE = constValue(null);
public static final FieldInitAttr NULL_VALUE = constValue(EncodedValue.NULL);
public enum InitType {
CONST,
INSN
}
private final Object value;
@@ -25,7 +23,7 @@ public class FieldInitAttr implements IAttribute {
this.insnMth = insnMth;
}
public static FieldInitAttr constValue(Object value) {
public static FieldInitAttr constValue(EncodedValue value) {
return new FieldInitAttr(InitType.CONST, value, null);
}
@@ -33,8 +31,8 @@ public class FieldInitAttr implements IAttribute {
return new FieldInitAttr(InitType.INSN, insn, mth);
}
public Object getValue() {
return value;
public EncodedValue getEncodedValue() {
return (EncodedValue) value;
}
public InsnNode getInsn() {

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