Compare commits

..

227 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
580 changed files with 22766 additions and 6951 deletions
@@ -13,10 +13,8 @@ assignees: ''
- search existing issues by exception message
**Describe error**
- provide full name of method or class with error
- provide full java stacktrace
**Note**: no need to copy method fallback code (commented pseudocode)
- attach or provide link to apk file (double check apk version)
- 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 :)
+2 -2
View File
@@ -1,5 +1,5 @@
:exclamation: Please review the [guidelines for contributing](../CONTRIBUTING.md#Pull-Request-Process)
: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 fix.
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
+3 -1
View File
@@ -5,6 +5,7 @@
# IntelliJ Idea files
.idea/
.run/
out/
*.iml
*.ipr
@@ -24,8 +25,9 @@ node_modules/
jadx-output/
*-tmp/
**/tmp/
*.jobf
*.dex
*.class
*.dump
*.log
+5 -18
View File
@@ -7,31 +7,18 @@ before_script:
stages:
- test
- check
java-8:
stage: test
image: openjdk:8
script: ./gradlew clean build
script: ./gradlew clean build dist
java-11:
stage: test
image: openjdk:11
script: ./gradlew clean build
script: ./gradlew clean build dist
java-12:
java-latest:
stage: test
image: gradle:jdk12 # latest gradle and jdk12
script: gradle clean build
check:
stage: check
image: openjdk:8
script:
- export JADX_LAST_TAG="$(git describe --abbrev=0 --tags)"
- export JADX_VERSION="${JADX_LAST_TAG:1}-dev-$(git rev-parse --short HEAD)"
- ./gradlew clean sonarqube -Dsonar.host.url="${SONAR_HOST}" -Dsonar.projectKey=jadx -Dsonar.organization="${SONAR_ORG}" -Dsonar.login="${SONAR_TOKEN}" -Dsonar.branch.name=dev || echo "Skip sonar build and upload"
- ./gradlew clean dist
artifacts:
paths:
- build/jadx*.zip
image: openjdk:latest
script: java -version && ./gradlew clean build dist
+3 -1
View File
@@ -1,4 +1,6 @@
branch: release
branches:
- release
verifyConditions:
- '@semantic-release/github'
+5
View File
@@ -28,6 +28,11 @@ script: ./gradlew clean build
jobs:
include:
- stage: checks
jdk: openjdk11
if: branch = master AND repo = env(MAIN_REPO) AND type = push
script: bash scripts/travis-checks.sh
- stage: deploy-unstable
jdk: openjdk8
if: branch = master AND repo = env(MAIN_REPO) AND type = push
+5 -6
View File
@@ -10,11 +10,10 @@ Please note we have a [code of conduct](CODE_OF_CONDUCT.md), please follow it in
- search existing issues by exception message
2. Describe error
- provide full name of method or class with error
- provide full java stacktrace
**Note**: no need to copy method fallback code (commented pseudocode)
- attach or provide link to apk file (double check apk version)
**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 :)
@@ -23,7 +22,7 @@ Please note we have a [code of conduct](CODE_OF_CONDUCT.md), please follow it in
1. Please don't submit any code style fixes, dependencies updates or other changes which are not fixing any issues.
1. Before open a PR please discuss the change you wish to make via issue. PR without corresponding issue will be rejected.
1. 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.
+4 -4
View File
@@ -1,6 +1,8 @@
<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)
@@ -9,7 +11,7 @@
**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
@@ -121,5 +123,3 @@ To support this project you can:
---------------------------------------
*Licensed under the Apache 2.0 License*
*Copyright 2019 by Skylot*
+33 -24
View File
@@ -1,7 +1,7 @@
plugins {
id 'org.sonarqube' version '2.8'
id 'com.github.ben-manes.versions' version '0.27.0'
id "com.diffplug.gradle.spotless" version "3.26.0"
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"
@@ -15,6 +15,9 @@ allprojects {
version = jadxVersion
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
tasks.withType(JavaCompile) {
if (!"$it".contains(':jadx-samples:')) {
options.compilerArgs << '-Xlint' << '-Xlint:unchecked' << '-Xlint:deprecation'
@@ -26,24 +29,25 @@ allprojects {
}
jar {
version = jadxVersion
manifest {
mainAttributes('jadx-version': jadxVersion)
}
}
dependencies {
compile 'org.slf4j:slf4j-api:1.7.29'
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 'org.hamcrest:hamcrest-library:2.2'
testCompile 'org.mockito:mockito-core:3.1.0'
testCompile 'org.assertj:assertj-core:3.14.0'
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'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.5.2'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.5.2'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0'
testCompile 'org.eclipse.jdt.core.compiler:ecj:4.6.1'
testImplementation 'org.eclipse.jdt.core.compiler:ecj:4.6.1'
testCompileOnly 'org.jetbrains:annotations:20.1.0'
}
test {
@@ -58,7 +62,7 @@ allprojects {
}
jacoco {
toolVersion = "0.8.2"
toolVersion = "0.8.6"
}
jacocoTestReport {
reports {
@@ -86,6 +90,7 @@ spotless {
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'
@@ -99,7 +104,7 @@ spotless {
}
format 'misc', {
target '**/*.gradle', '**/*.md', '**/*.xml', '**/.gitignore', '**/.properties'
targetExclude ".gradle/**", ".idea/**"
targetExclude ".gradle/**", ".idea/**", "*/build/**"
lineEndings(com.diffplug.spotless.LineEnding.UNIX)
encoding("UTF-8")
@@ -108,14 +113,16 @@ spotless {
}
}
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')
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')
}
}
}
}
@@ -126,11 +133,12 @@ task copyArtifacts(type: Sync, dependsOn: ['jadx-cli:installDist', 'jadx-gui:ins
['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"
destinationDirectory = buildDir
archiveFileName = "jadx-${jadxVersion}.zip"
from copyArtifacts.destinationDir
}
@@ -140,6 +148,7 @@ task copyExe(type: Copy, dependsOn: 'jadx-gui:createExe') {
destinationDir buildDir
from tasks.getByPath('jadx-gui:createExe').outputs
include '*.exe'
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
task dist(dependsOn: [pack, copyExe]) {
Binary file not shown.
+1 -1
View File
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored
+2
View File
@@ -82,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
@@ -129,6 +130,7 @@ fi
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
Vendored
+7 -18
View File
@@ -29,6 +29,9 @@ 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="-Xmx64m" "-Xms64m"
@@ -37,7 +40,7 @@ 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.
@@ -51,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%
@@ -61,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
+8 -3
View File
@@ -3,9 +3,14 @@ plugins {
}
dependencies {
compile(project(':jadx-core'))
compile 'com.beust:jcommander:1.78'
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 {
+25 -20
View File
@@ -7,6 +7,7 @@ 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);
@@ -14,35 +15,39 @@ 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) {
JadxArgs args = inputArgs.toJadxArgs();
args.setCodeCache(new NoOpCodeCache());
JadxDecompiler jadx = new JadxDecompiler(args);
try {
jadx.load();
} catch (JadxArgsValidateException e) {
LOG.error("Incorrect arguments: {}", e.getMessage());
return 1;
public static int execute(String[] args) {
JadxCLIArgs jadxArgs = new JadxCLIArgs();
if (jadxArgs.processArgs(args)) {
return processAndSave(jadxArgs.toJadxArgs());
}
jadx.save();
int errorsCount = jadx.getErrorsCount();
if (errorsCount != 0) {
jadx.printErrorsReport();
LOG.error("finished with errors, count: {}", errorsCount);
} else {
LOG.info("done");
return 0;
}
private static int processAndSave(JadxArgs jadxArgs) {
jadxArgs.setCodeCache(new NoOpCodeCache());
try (JadxDecompiler jadx = new JadxDecompiler(jadxArgs)) {
jadx.load();
jadx.save();
int errorsCount = jadx.getErrorsCount();
if (errorsCount != 0) {
jadx.printErrorsReport();
LOG.error("finished with errors, count: {}", errorsCount);
} else {
LOG.info("done");
}
}
return 0;
}
@@ -1,12 +1,12 @@
package jadx.cli;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.beust.jcommander.IStringConverter;
import com.beust.jcommander.Parameter;
@@ -19,7 +19,7 @@ import jadx.core.utils.files.FileUtils;
public class JadxCLIArgs {
@Parameter(description = "<input file> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc)")
@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")
@@ -85,6 +85,9 @@ public class JadxCLIArgs {
@Parameter(names = { "--deobf-use-sourcename" }, description = "use source file name as class name alias")
protected boolean deobfuscationUseSourceNameAsAlias = false;
@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,"
@@ -194,6 +197,7 @@ public class JadxCLIArgs {
args.setDeobfuscationMinLength(deobfuscationMinLength);
args.setDeobfuscationMaxLength(deobfuscationMaxLength);
args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias);
args.setParseKotlinMetadata(deobfuscationParseKotlinMetadata);
args.setEscapeUnicode(escapeUnicode);
args.setRespectBytecodeAccModifiers(respectBytecodeAccessModifiers);
args.setExportAsGradleProject(exportAsGradleProject);
@@ -275,6 +279,10 @@ public class JadxCLIArgs {
return deobfuscationUseSourceNameAsAlias;
}
public boolean isDeobfuscationParseKotlinMetadata() {
return deobfuscationParseKotlinMetadata;
}
public boolean isEscapeUnicode() {
return escapeUnicode;
}
@@ -345,7 +353,7 @@ public class JadxCLIArgs {
}
public static String enumValuesString(Enum<?>[] values) {
return Arrays.stream(values)
return Stream.of(values)
.map(v -> '\'' + v.name().toLowerCase(Locale.ROOT) + '\'')
.collect(Collectors.joining(", "));
}
@@ -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");
}
}
@@ -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.
+21 -17
View File
@@ -1,18 +1,22 @@
dependencies {
runtime files('clsp-data/android-29-clst.jar')
runtime files('clsp-data/android-29-res.jar')
compile files('lib/dx-1.16.jar') // TODO: dx don't support java version > 9 (53)
compile 'org.ow2.asm:asm:7.2'
compile 'org.jetbrains:annotations:18.0.0'
compile 'com.google.code.gson:gson:2.8.6'
compile 'org.smali:baksmali:2.3.4'
compile('org.smali:smali:2.3.4') {
exclude group: 'com.google.guava'
}
compile 'com.google.guava:guava:28.1-jre'
testCompile 'org.apache.commons:commons-lang3:3.9'
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.
@@ -6,6 +6,8 @@ public interface ICodeCache {
void add(String clsFullName, ICodeInfo codeInfo);
void remove(String clsFullName);
@Nullable
ICodeInfo get(String clsFullName);
}
@@ -2,7 +2,12 @@ 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();
@@ -49,6 +49,7 @@ public class JadxArgs {
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;
@@ -230,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;
}
@@ -318,6 +327,14 @@ public class JadxArgs {
}
}
public void setRenameFlags(Set<RenameEnum> renameFlags) {
this.renameFlags = renameFlags;
}
public Set<RenameEnum> getRenameFlags() {
return renameFlags;
}
public OutputFormatEnum getOutputFormat() {
return outputFormat;
}
@@ -355,6 +372,7 @@ public class JadxArgs {
+ ", deobfuscationOn=" + deobfuscationOn
+ ", deobfuscationForceSave=" + deobfuscationForceSave
+ ", useSourceNameAsClassAlias=" + useSourceNameAsClassAlias
+ ", parseKotlinMetadata=" + parseKotlinMetadata
+ ", deobfuscationMinLength=" + deobfuscationMinLength
+ ", deobfuscationMaxLength=" + deobfuscationMaxLength
+ ", escapeUnicode=" + escapeUnicode
@@ -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);
@@ -1,22 +1,32 @@
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.dex.attributes.AFlag;
import jadx.core.dex.attributes.nodes.LineAttrNode;
@@ -26,8 +36,8 @@ import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.SaveCode;
import jadx.core.export.ExportGradleProject;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.InputFile;
import jadx.core.xmlgen.BinaryXMLParser;
import jadx.core.xmlgen.ResourcesSaver;
@@ -39,10 +49,10 @@ import jadx.core.xmlgen.ResourcesSaver;
* 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();
* try (JadxDecompiler jadx = new JadxDecompiler(args)) {
* jadx.load();
* jadx.save();
* }
* </code>
* </pre>
* <p>
@@ -56,11 +66,12 @@ import jadx.core.xmlgen.ResourcesSaver;
* </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 List<InputFile> inputFiles;
private final JadxArgs args;
private final JadxPluginManager pluginManager = new JadxPluginManager();
private final List<ILoadResult> loadedInputs = new ArrayList<>();
private RootNode root;
private List<JavaClass> classes;
@@ -68,9 +79,9 @@ public final class JadxDecompiler {
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());
@@ -84,16 +95,27 @@ public final class JadxDecompiler {
reset();
JadxArgsValidator.validate(args);
LOG.info("loading ...");
inputFiles = loadFiles(args.getInputFiles());
loadInputFiles();
root = new RootNode(args);
root.load(inputFiles);
root.loadClasses(loadedInputs);
root.initClassPath();
root.loadResources(getResources());
root.runPreDecompileStage();
root.initPasses();
}
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);
}
}
}
private void reset() {
root = null;
classes = null;
@@ -103,27 +125,34 @@ public final class JadxDecompiler {
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 List<InputFile> loadFiles(List<File> files) {
if (files.isEmpty()) {
throw new JadxRuntimeException("Empty file list");
}
List<InputFile> filesList = new ArrayList<>();
for (File file : files) {
try {
InputFile.addFilesFrom(file, filesList, args.isSkipSources());
} catch (Exception e) {
throw new JadxRuntimeException("Error load file: " + file, e);
}
}
return filesList;
}
public void save() {
save(!args.isSkipSources(), !args.isSkipResources());
}
@@ -182,13 +211,19 @@ public final class JadxDecompiler {
}
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) {
final Predicate<String> classFilter = args.getClassFilter();
Predicate<String> classFilter = args.getClassFilter();
for (JavaClass cls : getClasses()) {
if (cls.getClassNode().contains(AFlag.DONT_GENERATE)) {
continue;
@@ -232,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;
}
@@ -281,7 +316,10 @@ public final class JadxDecompiler {
root.getErrorsCounter().printReport();
}
RootNode getRoot() {
/**
* Internal API. Not Stable!
*/
public RootNode getRoot() {
return root;
}
@@ -397,6 +435,13 @@ public final class JadxDecompiler {
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();
@@ -57,6 +57,11 @@ public final class JavaClass implements JavaNode {
cls.decompile();
}
public synchronized void reload() {
listsLoaded = false;
cls.reloadCode();
}
public synchronized String getSmali() {
return cls.getSmali();
}
@@ -66,11 +71,14 @@ public final class JavaClass implements JavaNode {
listsLoaded = false;
}
/**
* Internal API. Not Stable!
*/
public ClassNode getClassNode() {
return cls;
}
private void loadLists() {
private synchronized void loadLists() {
if (listsLoaded) {
return;
}
@@ -116,7 +124,7 @@ public final class JavaClass implements JavaNode {
}
}
private JadxDecompiler getRootDecompiler() {
protected JadxDecompiler getRootDecompiler() {
if (parent != null) {
return parent.getRootDecompiler();
}
@@ -148,6 +156,11 @@ public final class JavaClass implements JavaNode {
return resultMap;
}
@Override
public List<JavaNode> getUseIn() {
return getRootDecompiler().convertNodes(cls.getUseIn());
}
@Nullable
@Deprecated
public JavaNode getJavaNodeAtPosition(int line, int offset) {
@@ -174,6 +187,10 @@ public final class JavaClass implements JavaNode {
return cls.getFullName();
}
public String getRawName() {
return cls.getRawName();
}
public String getPackage() {
return cls.getPackage();
}
@@ -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;
@@ -39,14 +41,23 @@ public final class JavaField implements JavaNode {
}
public ArgType getType() {
return ArgType.tryToResolveClassAlias(field.dex(), field.getType());
return ArgType.tryToResolveClassAlias(field.root(), field.getType());
}
@Override
public int getDecompiledLine() {
return field.getDecompiledLine();
}
FieldNode getFieldNode() {
@Override
public List<JavaNode> getUseIn() {
return getDeclaringClass().getRootDecompiler().convertNodes(field.getUseIn());
}
/**
* Internal API. Not Stable!
*/
public FieldNode getFieldNode() {
return field;
}
@@ -47,19 +47,18 @@ public final class JavaMethod implements JavaNode {
return Collections.emptyList();
}
List<ArgType> arguments = mth.getArgTypes();
if (arguments == null) {
arguments = infoArgTypes;
}
return Utils.collectionMap(arguments,
type -> ArgType.tryToResolveClassAlias(mth.dex(), type));
type -> ArgType.tryToResolveClassAlias(mth.root(), type));
}
public ArgType getReturnType() {
ArgType retType = mth.getReturnType();
if (retType == null) {
retType = mth.getMethodInfo().getReturnType();
}
return ArgType.tryToResolveClassAlias(mth.dex(), retType);
return ArgType.tryToResolveClassAlias(mth.root(), retType);
}
@Override
public List<JavaNode> getUseIn() {
return getDeclaringClass().getRootDecompiler().convertNodes(mth.getUseIn());
}
public boolean isConstructor() {
@@ -70,11 +69,15 @@ public final class JavaMethod implements JavaNode {
return mth.getMethodInfo().isClassInit();
}
@Override
public int getDecompiledLine() {
return mth.getDecompiledLine();
}
MethodNode getMethodNode() {
/**
* Internal API. Not Stable!
*/
public MethodNode getMethodNode() {
return mth;
}
@@ -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,7 @@ 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)) {
@@ -48,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;
}
@@ -64,6 +70,15 @@ 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;
}
@@ -1,18 +1,17 @@
package jadx.api;
import jadx.core.codegen.CodeWriter;
import jadx.core.xmlgen.ResContainer;
public class ResourceFileContent extends ResourceFile {
private final CodeWriter content;
private final ICodeInfo content;
public 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.textResource(getName(), content);
return ResContainer.textResource(getDeobfName(), content);
}
}
@@ -1,14 +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", ".otf"),
IMG(".png", ".gif", ".jpg"),
MEDIA(".mp3", ".wav"),
LIB(".so"),
MANIFEST,
UNKNOWN;
private final String[] exts;
@@ -21,14 +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.toLowerCase().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;
}
}
@@ -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,12 +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;
@@ -38,10 +38,11 @@ public final class ResourcesLoader {
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;
}
@@ -54,7 +55,7 @@ public final class ResourcesLoader {
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);
}
@@ -67,13 +68,13 @@ 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);
}
}
@@ -85,7 +86,7 @@ public final class ResourcesLoader {
CodeWriter cw = new CodeWriter();
cw.add("Error decode ").add(rf.getType().toString().toLowerCase());
Utils.appendStackTrace(cw, e.getCause());
return ResContainer.textResource(rf.getName(), cw);
return ResContainer.textResource(rf.getDeobfName(), cw.finish());
}
}
@@ -94,8 +95,8 @@ public final class ResourcesLoader {
switch (rf.getType()) {
case MANIFEST:
case XML:
CodeWriter content = jadxRef.getXmlParser().parse(inputStream);
return ResContainer.textResource(rf.getName(), content);
ICodeInfo content = jadxRef.getXmlParser().parse(inputStream);
return ResContainer.textResource(rf.getDeobfName(), content);
case ARSC:
return new ResTableParser(jadxRef.getRoot()).decodeFiles(inputStream);
@@ -109,12 +110,12 @@ public final class ResourcesLoader {
}
private static ResContainer decodeImage(ResourceFile rf, InputStream inputStream) {
String name = rf.getName();
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.getName(), os.toByteArray());
return ResContainer.decodedData(rf.getDeobfName(), os.toByteArray());
} catch (Exception e) {
LOG.error("Failed to decode 9-patch png image, path: {}", name, e);
}
@@ -126,16 +127,9 @@ public final class ResourcesLoader {
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);
}
}
@@ -162,9 +156,9 @@ public final class ResourcesLoader {
}
}
public static CodeWriter loadToCodeWriter(InputStream is) throws IOException {
public static ICodeInfo loadToCodeWriter(InputStream is) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream(READ_BUFFER_SIZE);
copyStream(is, baos);
return new CodeWriter(baos.toString("UTF-8"));
return new SimpleCodeInfo(baos.toString("UTF-8"));
}
}
@@ -17,8 +17,18 @@ public class InMemoryCodeCache implements ICodeCache {
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";
}
}
@@ -12,8 +12,18 @@ public class NoOpCodeCache implements ICodeCache {
// 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";
+86 -53
View File
@@ -11,32 +11,41 @@ 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.ConstInlineVisitor;
import jadx.core.dex.visitors.ConstructorVisitor;
import jadx.core.dex.visitors.DeboxingVisitor;
import jadx.core.dex.visitors.DependencyCollector;
import jadx.core.dex.visitors.DotGraphVisitor;
import jadx.core.dex.visitors.EnumVisitor;
import jadx.core.dex.visitors.ExtractFieldInit;
import jadx.core.dex.visitors.FallbackModeVisitor;
import jadx.core.dex.visitors.FixAccessModifiers;
import jadx.core.dex.visitors.GenericTypesVisitor;
import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.dex.visitors.InitCodeVariables;
import jadx.core.dex.visitors.InlineMethods;
import jadx.core.dex.visitors.MarkFinallyVisitor;
import jadx.core.dex.visitors.MethodInlineVisitor;
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.BlockFinish;
import jadx.core.dex.visitors.blocksmaker.BlockProcessor;
import jadx.core.dex.visitors.blocksmaker.BlockSplitter;
import jadx.core.dex.visitors.debuginfo.DebugInfoApplyVisitor;
import jadx.core.dex.visitors.debuginfo.DebugInfoParseVisitor;
import jadx.core.dex.visitors.debuginfo.DebugInfoAttachVisitor;
import jadx.core.dex.visitors.regions.CheckRegions;
import jadx.core.dex.visitors.regions.CleanRegions;
import jadx.core.dex.visitors.regions.IfRegionVisitor;
@@ -47,6 +56,7 @@ import jadx.core.dex.visitors.regions.variables.ProcessVariables;
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
import jadx.core.dex.visitors.ssa.SSATransform;
import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor;
import jadx.core.dex.visitors.usage.UsageInfoVisitor;
public class Jadx {
private static final Logger LOG = LoggerFactory.getLogger(Jadx.class);
@@ -60,67 +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 {
if (args.isDebugInfo()) {
passes.add(new DebugInfoParseVisitor());
}
return getFallbackPassesList();
}
passes.add(new BlockSplitter());
if (args.isRawCFGOutput()) {
passes.add(DotGraphVisitor.dumpRaw());
}
List<IDexTreeVisitor> passes = new ArrayList<>();
if (args.isDebugInfo()) {
passes.add(new DebugInfoAttachVisitor());
}
passes.add(new AttachTryCatchVisitor());
passes.add(new ProcessInstructionsVisitor());
passes.add(new BlockProcessor());
passes.add(new BlockExceptionHandler());
passes.add(new BlockFinish());
passes.add(new BlockSplitter());
if (args.isRawCFGOutput()) {
passes.add(DotGraphVisitor.dumpRaw());
}
passes.add(new BlockProcessor());
passes.add(new BlockExceptionHandler());
passes.add(new BlockFinish());
passes.add(new SSATransform());
passes.add(new ConstructorVisitor());
passes.add(new InitCodeVariables());
passes.add(new MarkFinallyVisitor());
passes.add(new ConstInlineVisitor());
passes.add(new TypeInferenceVisitor());
if (args.isDebugInfo()) {
passes.add(new DebugInfoApplyVisitor());
}
passes.add(new AttachMethodDetails());
passes.add(new OverrideMethodVisitor());
passes.add(new DeboxingVisitor());
passes.add(new ModVisitor());
passes.add(new CodeShrinkVisitor());
passes.add(new ReSugarCode());
if (args.isCfgOutput()) {
passes.add(DotGraphVisitor.dump());
}
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 RegionMakerVisitor());
passes.add(new IfRegionVisitor());
passes.add(new ReturnVisitor());
passes.add(new CleanRegions());
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());
}
passes.add(new CodeShrinkVisitor());
passes.add(new SimplifyVisitor());
passes.add(new CheckRegions());
passes.add(new RegionMakerVisitor());
passes.add(new IfRegionVisitor());
passes.add(new ReturnVisitor());
passes.add(new CleanRegions());
passes.add(new ExtractFieldInit());
passes.add(new FixAccessModifiers());
passes.add(new ProcessAnonymous());
passes.add(new ClassModifier());
passes.add(new MethodInlineVisitor());
passes.add(new EnumVisitor());
passes.add(new LoopRegionVisitor());
passes.add(new CodeShrinkVisitor());
passes.add(new MethodInvokeVisitor());
passes.add(new SimplifyVisitor());
passes.add(new CheckRegions());
passes.add(new ProcessVariables());
passes.add(new PrepareForCodeGen());
if (args.isCfgOutput()) {
passes.add(DotGraphVisitor.dumpRegions());
}
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 DependencyCollector());
passes.add(new RenameVisitor());
passes.add(new MarkMethodsForInline());
passes.add(new ProcessVariables());
passes.add(new PrepareForCodeGen());
if (args.isCfgOutput()) {
passes.add(DotGraphVisitor.dumpRegions());
}
return passes;
}
@@ -1,15 +1,18 @@
package jadx.core;
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.PROCESS_COMPLETE;
@@ -20,18 +23,33 @@ public final class ProcessClass {
private ProcessClass() {
}
public static void process(ClassNode cls) {
ClassNode topParentClass = cls.getTopParentClass();
if (topParentClass != cls) {
process(topParentClass);
return;
}
if (cls.getState().isProcessed()) {
@Nullable
private static ICodeInfo process(ClassNode cls, boolean codegen) {
if (!codegen && cls.getState() == PROCESS_COMPLETE) {
// nothing to do
return;
return null;
}
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();
}
@@ -42,9 +60,18 @@ public final class ProcessClass {
}
cls.setState(PROCESS_COMPLETE);
}
if (codegen) {
ICodeInfo code = CodeGen.generate(cls);
if (!cls.contains(AFlag.DONT_UNLOAD_CLASS)) {
cls.unload();
cls.setState(GENERATED_AND_UNLOADED);
}
return code;
}
} catch (Throwable e) {
ErrorsCounter.classError(cls, e.getClass().getSimpleName(), e);
cls.addError("Class process error: " + e.getClass().getSimpleName(), e);
}
return null;
}
}
@@ -55,11 +82,13 @@ public final class ProcessClass {
return generateCode(topParentClass);
}
try {
process(cls);
cls.getDependencies().forEach(ProcessClass::process);
ICodeInfo code = CodeGen.generate(cls);
cls.unload();
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);
+282 -253
View File
@@ -1,5 +1,6 @@
package jadx.core.clsp;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
@@ -16,23 +17,29 @@ 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.GenericInfo;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.FileUtils;
import jadx.core.utils.files.ZipSecurity;
import static jadx.core.utils.Utils.notEmpty;
/**
* Classes list for import into classpath graph
@@ -45,130 +52,139 @@ 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 = 2;
private static final int VERSION = 3;
private static final String STRING_CHARSET = "US-ASCII";
private static final NClass[] EMPTY_NCLASS_ARRAY = new NClass[0];
private static final ArgType[] EMPTY_ARGTYPE_ARRAY = new ArgType[0];
private enum TypeEnum {
WILDCARD, GENERIC, GENERIC_TYPE, OBJECT, ARRAY, PRIMITIVE
private final RootNode root;
public ClsSet(RootNode root) {
this.root = root;
}
private NClass[] classes;
private enum TypeEnum {
WILDCARD,
GENERIC,
GENERIC_TYPE_VARIABLE,
OUTER_GENERIC,
OBJECT,
ARRAY,
PRIMITIVE
}
private ClspClass[] classes;
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 loadFrom(RootNode root) {
List<ClassNode> list = root.getClasses(true);
Map<String, NClass> names = new HashMap<>(list.size());
Map<String, ClspClass> names = new HashMap<>(list.size());
int k = 0;
for (ClassNode cls : list) {
String clsRawName = cls.getRawName();
if (cls.getAccessFlags().isPublic()) {
cls.load();
NClass nClass = new NClass(clsRawName, k);
if (names.put(clsRawName, nClass) != null) {
throw new JadxRuntimeException("Duplicate class: " + clsRawName);
}
k++;
nClass.setGenerics(cls.getGenerics());
nClass.setMethods(getMethodsDetails(cls));
} else {
names.put(clsRawName, null);
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 NClass[k];
classes = new ClspClass[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++;
ClspClass nClass = getCls(cls, names);
if (nClass == null) {
throw new JadxRuntimeException("Missing class: " + cls);
}
nClass.setParents(makeParentsArray(cls));
classes[k] = nClass;
k++;
}
}
private List<NMethod> getMethodsDetails(ClassNode cls) {
List<NMethod> methods = new ArrayList<>();
for (MethodNode m : cls.getMethods()) {
AccessInfo accessFlags = m.getAccessFlags();
if (accessFlags.isPublic() || accessFlags.isProtected()) {
processMethodDetails(methods, m, accessFlags);
}
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(List<NMethod> methods, MethodNode mth, AccessInfo accessFlags) {
List<ArgType> args = mth.getArgTypes();
boolean genericArg = false;
ArgType[] genericArgs;
if (args.isEmpty()) {
genericArgs = null;
} else {
int argsCount = args.size();
genericArgs = new ArgType[argsCount];
for (int i = 0; i < argsCount; i++) {
ArgType argType = args.get(i);
if (argType.isGeneric() || argType.isGenericType()) {
genericArgs[i] = argType;
genericArg = true;
}
}
}
ArgType retType = mth.getReturnType();
if (!retType.isGeneric() && !retType.isGenericType()) {
retType = null;
private void processMethodDetails(MethodNode mth, List<ClspMethod> methods) {
AccessInfo accessFlags = mth.getAccessFlags();
if (accessFlags.isPrivate()) {
return;
}
ArgType genericRetType = mth.getReturnType();
boolean varArgs = accessFlags.isVarArgs();
if (genericArg || retType != null || varArgs) {
methods.add(new NMethod(mth.getMethodInfo().getShortId(), genericArgs, retType, varArgs));
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 NClass[] makeParentsArray(ClassNode cls, Map<String, NClass> names) {
List<NClass> parents = new ArrayList<>(1 + cls.getInterfaces().size());
public static ArgType[] makeParentsArray(ClassNode cls) {
ArgType superClass = cls.getSuperClass();
if (superClass != null) {
NClass c = getCls(superClass.getObject(), names);
if (c != null) {
parents.add(c);
}
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()) {
NClass c = getCls(iface.getObject(), names);
if (c != null) {
parents.add(c);
}
parents[k] = iface;
k++;
}
int size = parents.size();
if (size == 0) {
return EMPTY_NCLASS_ARRAY;
}
return parents.toArray(new NClass[size]);
return parents;
}
private static NClass getCls(String fullName, Map<String, NClass> names) {
NClass cls = names.get(fullName);
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;
}
void save(Path path) throws IOException {
public void save(Path path) throws IOException {
FileUtils.makeDirsForFile(path);
String outputName = path.getFileName().toString();
if (outputName.endsWith(CLST_EXTENSION)) {
@@ -182,97 +198,106 @@ public class ClsSet {
try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(path));
ZipInputStream in = new ZipInputStream(Files.newInputStream(temp))) {
String clst = CLST_PKG_PATH + '/' + CLST_FILENAME;
out.putNextEntry(new ZipEntry(clst));
save(out);
boolean clstReplaced = false;
ZipEntry entry = in.getNextEntry();
while (entry != null) {
if (!entry.getName().equals(clst)) {
out.putNextEntry(new ZipEntry(entry.getName()));
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();
}
if (!clstReplaced) {
out.putNextEntry(new ZipEntry(clst));
save(out);
}
}
} else {
throw new JadxRuntimeException("Unknown file format: " + outputName);
}
}
public void save(OutputStream output) throws IOException {
private void save(OutputStream output) throws IOException {
DataOutputStream out = new DataOutputStream(output);
out.writeBytes(JADX_CLS_SET_HEADER);
out.writeByte(VERSION);
LOG.info("Classes count: {}", classes.length);
Map<String, NClass> names = new HashMap<>(classes.length);
Map<String, ClspClass> names = new HashMap<>(classes.length);
out.writeInt(classes.length);
for (NClass cls : classes) {
writeString(out, cls.getName());
names.put(cls.getName(), cls);
for (ClspClass cls : classes) {
String clsName = cls.getName();
writeString(out, clsName);
names.put(clsName, cls);
}
for (NClass cls : classes) {
NClass[] parents = cls.getParents();
out.writeByte(parents.length);
for (NClass parent : parents) {
out.writeInt(parent.getId());
}
writeGenerics(out, cls, names);
List<NMethod> methods = cls.getMethodsList();
out.writeByte(methods.size());
for (NMethod method : methods) {
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 writeGenerics(DataOutputStream out, NClass cls, Map<String, NClass> names) throws IOException {
List<GenericInfo> genericsList = cls.getGenerics();
out.writeByte(genericsList.size());
for (GenericInfo genericInfo : genericsList) {
writeArgType(out, genericInfo.getGenericType(), names);
List<ArgType> extendsList = genericInfo.getExtendsList();
out.writeByte(extendsList.size());
for (ArgType type : extendsList) {
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);
}
}
}
private static void writeMethod(DataOutputStream out, NMethod method, Map<String, NClass> names) throws IOException {
writeLongString(out, method.getShortId());
ArgType[] argTypes = method.getGenericArgs();
if (argTypes == null) {
out.writeByte(0);
} else {
int argCount = 0;
for (ArgType arg : argTypes) {
if (arg != null) {
argCount++;
}
}
out.writeByte(argCount);
// last argument first
for (int i = argTypes.length - 1; i >= 0; i--) {
ArgType argType = argTypes[i];
if (argType != null) {
out.writeByte(i);
writeArgType(out, argType, names);
}
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);
}
}
if (method.getReturnType() == null) {
out.writeBoolean(false);
} else {
out.writeBoolean(true);
writeArgType(out, method.getReturnType(), names);
}
out.writeBoolean(method.isVarArgs());
}
private static void writeArgType(DataOutputStream out, ArgType argType, Map<String, NClass> names) throws IOException {
if (argType.getWildcardType() != null) {
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());
@@ -281,28 +306,18 @@ public class ClsSet {
}
} else if (argType.isGeneric()) {
out.writeByte(TypeEnum.GENERIC.ordinal());
out.writeInt(names.get(argType.getObject()).getId());
ArgType[] types = argType.getGenericTypes();
if (types == null) {
out.writeByte(0);
} else {
out.writeByte(types.length);
for (ArgType type : types) {
writeArgType(out, type, names);
}
}
out.writeInt(getCls(argType, names).getId());
writeArgTypesList(out, argType.getGenericTypes(), names);
} else if (argType.isGenericType()) {
out.writeByte(TypeEnum.GENERIC_TYPE.ordinal());
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(names.get(argType.getObject()).getId());
out.writeInt(getCls(argType, names).getId());
} else if (argType.isArray()) {
out.writeByte(TypeEnum.ARRAY.ordinal());
writeArgType(out, argType.getArrayElement(), names);
} else if (argType.isPrimitive()) {
out.writeByte(TypeEnum.PRIMITIVE.ordinal());
out.writeByte(argType.getPrimitiveType().getShortName().charAt(0));
} else {
throw new JadxRuntimeException("Cannot save type: " + argType);
}
@@ -310,27 +325,27 @@ public class ClsSet {
private void load(File input) throws IOException, DecodeException {
String name = input.getName();
try (InputStream inputStream = new FileInputStream(input)) {
if (name.endsWith(CLST_EXTENSION)) {
if (name.endsWith(CLST_EXTENSION)) {
try (InputStream inputStream = new FileInputStream(input)) {
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();
}
} 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);
}
});
} else {
throw new JadxRuntimeException("Unknown file format: " + name);
}
}
private void load(InputStream input) throws IOException, DecodeException {
try (DataInputStream in = new DataInputStream(input)) {
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();
@@ -339,103 +354,113 @@ 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()];
}
NClass nClass = classes[i];
nClass.setParents(parents);
nClass.setGenerics(readGenerics(in));
nClass.setMethods(readClsMethods(in));
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 List<GenericInfo> readGenerics(DataInputStream in) throws IOException {
int count = in.readByte();
if (count == 0) {
return Collections.emptyList();
}
List<GenericInfo> list = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
ArgType genericType = readArgType(in);
List<ArgType> extendsList;
byte extCount = in.readByte();
if (extCount == 0) {
extendsList = Collections.emptyList();
} else {
extendsList = new ArrayList<>(extCount);
for (int j = 0; j < extCount; j++) {
extendsList.add(readArgType(in));
}
}
list.add(new GenericInfo(genericType, extendsList));
}
return list;
}
private List<NMethod> readClsMethods(DataInputStream in) throws IOException {
int mCount = in.readByte();
List<NMethod> methods = new ArrayList<>(mCount);
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));
methods.add(readMethod(in, clsInfo));
}
return methods;
}
private NMethod readMethod(DataInputStream in) throws IOException {
String shortId = readLongString(in);
int argCount = in.readByte();
ArgType[] argTypes = null;
for (int i = 0; i < argCount; i++) {
int index = in.readByte();
ArgType argType = readArgType(in);
if (argTypes == null) {
argTypes = new ArgType[index + 1];
}
argTypes[index] = argType;
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 retType = in.readBoolean() ? readArgType(in) : null;
ArgType genericRetType = readArgType(in);
if (Objects.equals(genericRetType, retType)) {
genericRetType = retType;
}
List<ArgType> typeParameters = readArgTypesList(in);
boolean varArgs = in.readBoolean();
return new NMethod(shortId, argTypes, retType, varArgs);
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:
int bounds = in.readByte();
return bounds == 0
? ArgType.wildcard()
: ArgType.wildcard(readArgType(in), ArgType.WildcardBound.getByNum(bounds));
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:
String obj = classes[in.readInt()].getName();
int typeLength = in.readByte();
ArgType[] generics;
if (typeLength == 0) {
generics = null;
} else {
generics = new ArgType[typeLength];
for (int i = 0; i < typeLength; i++) {
generics[i] = readArgType(in);
}
}
return ArgType.generic(obj, generics);
ArgType clsType = classes[in.readInt()].getClsType();
return ArgType.generic(clsType, readArgTypesList(in));
case GENERIC_TYPE:
return ArgType.genericType(readString(in));
case GENERIC_TYPE_VARIABLE:
String typeVar = readString(in);
List<ArgType> extendTypes = readArgTypesList(in);
return ArgType.genericType(typeVar, extendTypes);
case OBJECT:
return ArgType.object(classes[in.readInt()].getName());
return classes[in.readInt()].getClsType();
case ARRAY:
return ArgType.array(readArgType(in));
@@ -451,23 +476,16 @@ public class ClsSet {
private static void writeString(DataOutputStream out, String name) throws IOException {
byte[] bytes = name.getBytes(STRING_CHARSET);
out.writeByte(bytes.length);
out.write(bytes);
}
private static void writeLongString(DataOutputStream out, String name) throws IOException {
byte[] bytes = name.getBytes(STRING_CHARSET);
out.writeShort(bytes.length);
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();
return readString(in, len);
}
private static String readLongString(DataInputStream in) throws IOException {
int len = in.readShort();
int len = readUnsignedByte(in);
return readString(in, len);
}
@@ -485,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,6 +10,7 @@ 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;
@@ -17,6 +18,8 @@ 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;
@@ -26,13 +29,18 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
public class ClspGraph {
private static final Logger LOG = LoggerFactory.getLogger(ClspGraph.class);
private final Map<String, Set<String>> ancestorCache = Collections.synchronizedMap(new WeakHashMap<>());
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();
ClsSet set = new ClsSet(root);
set.loadFromClstFile();
addClasspath(set);
}
@@ -50,14 +58,8 @@ 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);
}
}
@@ -65,31 +67,51 @@ public class ClspGraph {
return nameMap.containsKey(fullName);
}
public NClass getClsDetails(ArgType type) {
public ClspClass getClsDetails(ArgType type) {
return nameMap.get(type.getObject());
}
@Nullable
public NMethod getMethodDetails(MethodInfo methodInfo) {
NClass cls = nameMap.get(methodInfo.getDeclClass().getRawName());
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 NClass addClass(ClassNode cls) {
String rawName = cls.getRawName();
NClass nClass = new NClass(rawName, -1);
nameMap.put(rawName, nClass);
return nClass;
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);
}
@@ -107,7 +129,7 @@ public class ClspGraph {
if (clsName.equals(implClsName)) {
return clsName;
}
NClass cls = nameMap.get(implClsName);
ClspClass cls = nameMap.get(implClsName);
if (cls == null) {
missingClasses.add(clsName);
return null;
@@ -115,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;
}
public 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,75 +0,0 @@
package jadx.core.clsp;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
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);
}
Path output = Paths.get(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, false);
}
}
for (InputFile inputFile : inputFiles) {
LOG.info("Loaded: {}", inputFile.getFile());
}
RootNode root = new RootNode(new JadxArgs());
root.load(inputFiles);
ClsSet set = new ClsSet();
set.loadFrom(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, false);
} catch (Exception e) {
LOG.warn("Skip file: {}, load error: {}", file, e.getMessage());
}
}
}
}
}
@@ -1,96 +0,0 @@
package jadx.core.clsp;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import jadx.core.dex.nodes.GenericInfo;
/**
* Class node in classpath graph
*/
public class NClass {
private final String name;
private final int id;
private NClass[] parents;
private Map<String, NMethod> methodsMap = Collections.emptyMap();
private List<GenericInfo> generics = Collections.emptyList();
public NClass(String name, int id) {
this.name = name;
this.id = id;
}
public String getName() {
return name;
}
public int getId() {
return id;
}
public NClass[] getParents() {
return parents;
}
public void setParents(NClass[] parents) {
this.parents = parents;
}
public Map<String, NMethod> getMethodsMap() {
return methodsMap;
}
public List<NMethod> getMethodsList() {
List<NMethod> list = new ArrayList<>(methodsMap.size());
list.addAll(methodsMap.values());
Collections.sort(list);
return list;
}
public void setMethodsMap(Map<String, NMethod> methodsMap) {
this.methodsMap = Objects.requireNonNull(methodsMap);
}
public void setMethods(List<NMethod> methods) {
Map<String, NMethod> map = new HashMap<>(methods.size());
for (NMethod mth : methods) {
map.put(mth.getShortId(), mth);
}
setMethodsMap(map);
}
public List<GenericInfo> getGenerics() {
return generics;
}
public void setGenerics(List<GenericInfo> generics) {
this.generics = generics;
}
@Override
public int hashCode() {
return name.hashCode();
}
@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;
}
}
@@ -1,92 +0,0 @@
package jadx.core.clsp;
import java.util.Arrays;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import jadx.core.dex.instructions.args.ArgType;
/**
* Generic method node in classpath graph.
*/
public class NMethod implements Comparable<NMethod> {
private final String shortId;
/**
* Array contains only generic args, others set to 'null', size can be less than total args count
*/
@Nullable
private final ArgType[] genericArgs;
@Nullable
private final ArgType retType;
private final boolean varArgs;
public NMethod(String shortId, @Nullable ArgType[] genericArgs, @Nullable ArgType retType, boolean varArgs) {
this.shortId = shortId;
this.genericArgs = genericArgs;
this.retType = retType;
this.varArgs = varArgs;
}
public String getShortId() {
return shortId;
}
@Nullable
public ArgType[] getGenericArgs() {
return genericArgs;
}
@Nullable
public ArgType getGenericArg(int i) {
ArgType[] args = this.genericArgs;
if (args != null && i < args.length) {
return args[i];
}
return null;
}
@Nullable
public ArgType getReturnType() {
return retType;
}
public boolean isVarArgs() {
return varArgs;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof NMethod)) {
return false;
}
NMethod other = (NMethod) o;
return shortId.equals(other.shortId);
}
@Override
public int hashCode() {
return shortId.hashCode();
}
@Override
public int compareTo(@NotNull NMethod other) {
return this.shortId.compareTo(other.shortId);
}
@Override
public String toString() {
return "NMethod{'" + shortId + '\''
+ ", argTypes=" + Arrays.toString(genericArgs)
+ ", retType=" + retType
+ ", varArgs=" + varArgs
+ '}';
}
}
@@ -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 + '}';
}
}
@@ -7,10 +7,12 @@ 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;
@@ -18,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;
@@ -52,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(' ');
}
@@ -63,7 +66,7 @@ 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)) {
code.startLine();
@@ -72,20 +75,20 @@ public class AnnotationGen {
}
}
private void formatAnnotation(CodeWriter code, Annotation a) {
private void formatAnnotation(CodeWriter code, IAnnotation a) {
code.add('@');
ClassNode annCls = cls.dex().resolveClass(a.getType());
ClassNode annCls = cls.root().resolveClass(a.getAnnotationClass());
if (annCls != null) {
classGen.useClass(code, annCls);
} else {
classGen.useType(code, a.getType());
classGen.useClass(code, a.getAnnotationClass());
}
Map<String, Object> vl = a.getValues();
Map<String, EncodedValue> vl = a.getValues();
if (!vl.isEmpty()) {
code.add('(');
for (Iterator<Entry<String, Object>> it = vl.entrySet().iterator(); it.hasNext();) {
Entry<String, Object> e = it.next();
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
@@ -93,7 +96,7 @@ public class AnnotationGen {
code.add(paramName);
code.add(" = ");
}
encodeValue(code, e.getValue());
encodeValue(cls.root(), code, e.getValue());
if (it.hasNext()) {
code.add(", ");
}
@@ -113,13 +116,11 @@ public class AnnotationGen {
return paramName;
}
@SuppressWarnings("unchecked")
public void addThrows(MethodNode mth, CodeWriter code) {
Annotation an = mth.getAnnotation(Consts.DALVIK_THROWS);
if (an != null) {
Object exs = an.getDefaultValue();
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()) {
@@ -129,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, false));
} 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, false));
} else if (val instanceof Short) {
code.add(TypeGen.formatShort((Short) val, false));
} else if (val instanceof Byte) {
code.add(TypeGen.formatByte((Byte) val, false));
} 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,6 +1,7 @@
package jadx.core.codegen;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
@@ -8,31 +9,36 @@ import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Stream;
import com.android.dx.rop.code.AccessFlags;
import com.google.common.collect.Streams;
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.LineAttrNode;
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.GenericInfo;
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;
@@ -51,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());
}
@@ -73,7 +81,7 @@ public class ClassGen {
return cls;
}
public CodeWriter makeClass() throws CodegenException {
public ICodeInfo makeClass() throws CodegenException {
CodeWriter clsBody = new CodeWriter();
addClassCode(clsBody);
@@ -106,6 +114,9 @@ public class ClassGen {
if (cls.contains(AFlag.DONT_GENERATE)) {
return;
}
if (Consts.DEBUG_USAGE) {
addClassUsageInfo(code, cls);
}
CodeGenUtils.addComments(code, cls);
insertDecompilationProblems(code, cls);
addClassDeclaration(code);
@@ -115,17 +126,17 @@ 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.getClassInfo().isInner()) {
af = af.remove(AccessFlags.ACC_STATIC).remove(AccessFlags.ACC_PRIVATE);
af = af.remove(AccessFlags.STATIC).remove(AccessFlags.PRIVATE);
}
annotationGen.addForClass(clsCode);
@@ -146,7 +157,7 @@ public class ClassGen {
clsCode.attachDefinition(cls);
clsCode.add(cls.getClassInfo().getAliasShortName());
addGenericMap(clsCode, cls.getGenerics(), true);
addGenericTypeParameters(clsCode, cls.getGenericTypeParameters(), true);
clsCode.add(' ');
ArgType sup = cls.getSuperClass();
@@ -177,23 +188,22 @@ public class ClassGen {
}
}
public boolean addGenericMap(CodeWriter code, List<GenericInfo> generics, boolean classDeclaration) {
public boolean addGenericTypeParameters(CodeWriter code, List<ArgType> generics, boolean classDeclaration) {
if (generics == null || generics.isEmpty()) {
return false;
}
code.add('<');
int i = 0;
for (GenericInfo genericInfo : generics) {
for (ArgType genericInfo : generics) {
if (i != 0) {
code.add(", ");
}
ArgType type = genericInfo.getGenericType();
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.getExtendsList();
List<ArgType> list = genericInfo.getExtendTypes();
if (list != null && !list.isEmpty()) {
code.add(" extends ");
for (Iterator<ArgType> it = list.iterator(); it.hasNext();) {
@@ -220,9 +230,22 @@ 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);
addInnerClsAndMethods(clsCode);
clsCode.decIndent();
@@ -230,7 +253,8 @@ public class ClassGen {
}
private void addInnerClsAndMethods(CodeWriter clsCode) {
Streams.concat(cls.getInnerClasses().stream(), cls.getMethods().stream())
Stream.of(cls.getInnerClasses(), cls.getMethods())
.flatMap(Collection::stream)
.filter(node -> !node.contains(AFlag.DONT_GENERATE))
.sorted(Comparator.comparingInt(LineAttrNode::getSourceLine))
.forEach(node -> {
@@ -249,7 +273,7 @@ public class ClassGen {
inClGen.addClassCode(code);
imports.addAll(inClGen.getImports());
} catch (Exception e) {
ErrorsCounter.classError(innerCls, "Inner class code generation error", e);
innerCls.addError("Inner class code generation error", e);
}
}
@@ -274,7 +298,7 @@ public class ClassGen {
throw new JadxRuntimeException("Method generation error", e);
}
code.newLine().add("/*");
code.newLine().addMultiLine(ErrorsCounter.methodError(mth, "Method generation error", e));
code.newLine().addMultiLine(ErrorsCounter.error(mth, "Method generation error", e));
Utils.appendStackTrace(code, e);
code.newLine().add("*/");
code.setIndent(savedIndent);
@@ -293,7 +317,7 @@ public class ClassGen {
public void addMethodCode(CodeWriter code, MethodNode mth) throws CodegenException {
CodeGenUtils.addComments(code, mth);
if (mth.getAccessFlags().isAbstract() || mth.getAccessFlags().isNative()) {
if (mth.isNoCode()) {
MethodGen mthGen = new MethodGen(this, mth);
mthGen.addDefinition(code);
code.add(';');
@@ -353,6 +377,9 @@ public class ClassGen {
if (f.contains(AFlag.DONT_GENERATE)) {
return;
}
if (Consts.DEBUG_USAGE) {
addFieldUsageInfo(code, f);
}
CodeGenUtils.addComments(code, f);
annotationGen.addForField(code, f);
@@ -368,15 +395,16 @@ public class ClassGen {
FieldInitAttr fv = f.get(AType.FIELD_INIT);
if (fv != null) {
code.add(" = ");
if (fv.getValue() == null) {
code.add(TypeGen.literalToString(0, f.getType(), cls, fallback));
} else {
if (fv.getValueType() == InitType.CONST) {
annotationGen.encodeValue(code, fv.getValue());
} else if (fv.getValueType() == InitType.INSN) {
InsnGen insnGen = makeInsnGen(fv.getInsnMth());
addInsnBody(insnGen, code, fv.getInsn());
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(';');
@@ -401,12 +429,13 @@ public class ClassGen {
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(' ');
@@ -427,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);
@@ -436,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);
}
}
@@ -458,25 +497,30 @@ public class ClassGen {
}
}
public void useClass(CodeWriter code, String rawCls) {
useClass(code, ArgType.object(rawCls));
}
public void useClass(CodeWriter code, ArgType type) {
ArgType outerType = type.getOuterType();
if (outerType != null) {
useClass(code, outerType);
code.add('.');
useClass(code, type.getInnerType());
// import not needed, force use short name
useClassShortName(code, type.getObject());
return;
}
useClass(code, ClassInfo.fromType(cls.root(), type));
ArgType[] generics = type.getGenericTypes();
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) {
ArgType.WildcardBound bound = gt.getWildcardBound();
@@ -492,8 +536,17 @@ public class ClassGen {
}
}
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);
}
code.add(classInfo.getAliasShortName());
}
public void useClass(CodeWriter code, ClassInfo classInfo) {
ClassNode classNode = cls.dex().resolveClass(classInfo);
ClassNode classNode = cls.root().resolveClass(classInfo);
if (classNode != null) {
useClass(code, classNode);
} else {
@@ -533,12 +586,7 @@ 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
@@ -617,7 +665,7 @@ public class ClassGen {
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;
}
@@ -625,7 +673,7 @@ public class ClassGen {
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)
@@ -634,7 +682,7 @@ public class ClassGen {
}
}
}
return searchCollision(dex, useCls.getParentClass(), searchCls);
return searchCollision(root, useCls.getParentClass(), searchCls);
}
private void insertRenameInfo(CodeWriter code, ClassNode cls) {
@@ -644,6 +692,40 @@ public class ClassGen {
}
}
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());
}
}
public ClassGen getParentGen() {
return parentGen == null ? this : parentGen;
}
@@ -655,4 +737,12 @@ public class ClassGen {
public boolean isFallbackMode() {
return fallback;
}
public boolean isBodyGenStarted() {
return bodyGenStarted;
}
public void setBodyGenStarted(boolean bodyGenStarted) {
this.bodyGenStarted = bodyGenStarted;
}
}
@@ -4,6 +4,7 @@ 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;
@@ -13,7 +14,7 @@ public class CodeGen {
public static ICodeInfo generate(ClassNode cls) {
if (cls.contains(AFlag.DONT_GENERATE)) {
return CodeWriter.EMPTY;
return ICodeInfo.EMPTY;
}
JadxArgs args = cls.root().getArgs();
switch (args.getOutputFormat()) {
@@ -36,7 +37,7 @@ public class CodeGen {
private static ICodeInfo generateJson(ClassNode cls) {
JsonCodeGen codeGen = new JsonCodeGen(cls);
String clsJson = wrapCodeGen(cls, codeGen::process);
return new CodeWriter(clsJson);
return new SimpleCodeInfo(clsJson);
}
private static <R> R wrapCodeGen(ClassNode cls, Callable<R> codeGenFunc) {
@@ -1,7 +1,5 @@
package jadx.core.codegen;
import java.io.File;
import java.io.PrintWriter;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@@ -13,12 +11,12 @@ 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 implements ICodeInfo {
public class CodeWriter {
private static final Logger LOG = LoggerFactory.getLogger(CodeWriter.class);
public static final String NL = System.getProperty("line.separator");
@@ -35,8 +33,6 @@ public class CodeWriter implements ICodeInfo {
INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR,
};
public static final CodeWriter EMPTY = new CodeWriter().finish();
private StringBuilder buf;
@Nullable
private String code;
@@ -58,12 +54,6 @@ public class CodeWriter implements ICodeInfo {
}
}
// create filled instance (just string wrapper)
public CodeWriter(String code) {
this.buf = null;
this.code = code;
}
public CodeWriter startLine() {
addLine();
addLineIndent();
@@ -169,11 +159,7 @@ public class CodeWriter implements ICodeInfo {
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);
}
}
@@ -244,11 +230,6 @@ public class CodeWriter implements ICodeInfo {
return annotations.put(pos, obj);
}
@Override
public Map<CodePosition, Object> getAnnotations() {
return annotations;
}
public void attachSourceLine(int sourceLine) {
if (sourceLine == 0) {
return;
@@ -263,27 +244,12 @@ public class CodeWriter implements ICodeInfo {
lineMap.put(decompiledLine, sourceLine);
}
@Override
public Map<Integer, Integer> getLineMapping() {
return lineMap;
}
public CodeWriter finish() {
public ICodeInfo finish() {
removeFirstEmptyLine();
buf.trimToSize();
processDefinitionAnnotations();
code = buf.toString();
buf = null;
annotations.entrySet().removeIf(entry -> {
Object v = entry.getValue();
if (v instanceof DefinitionWrapper) {
LineAttrNode l = ((DefinitionWrapper) v).getNode();
l.setDecompiledLine(entry.getKey().getLine());
return true;
}
return false;
});
return this;
return new SimpleCodeInfo(code, lineMap, annotations);
}
private void removeFirstEmptyLine() {
@@ -293,46 +259,33 @@ public class CodeWriter implements ICodeInfo {
}
}
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();
}
@Override
public String getCodeStr() {
if (code == null) {
throw new NullPointerException("Code not set");
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();
}
}
@@ -16,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;
@@ -123,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));
@@ -1,6 +1,5 @@
package jadx.core.codegen;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.List;
@@ -10,23 +9,22 @@ import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.Consts;
import jadx.core.deobf.NameMapper;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
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.CallMthInterface;
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;
@@ -35,14 +33,13 @@ 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.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.NamedArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.instructions.mods.ConstructorInsn;
@@ -53,7 +50,6 @@ import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.RegionUtils;
import jadx.core.utils.TypeUtils;
import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.JadxRuntimeException;
@@ -152,7 +148,7 @@ public class InsnGen {
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) {
@@ -183,14 +179,14 @@ public class InsnGen {
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);
}
@@ -226,6 +222,9 @@ public class InsnGen {
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 {
if (flag == Flags.BODY_ONLY || flag == Flags.BODY_ONLY_NOWRAP) {
makeInsnBody(code, insn, flag == Flags.BODY_ONLY ? BODY_ONLY_FLAG : BODY_ONLY_NOWRAP_FLAGS);
@@ -261,7 +260,7 @@ public class InsnGen {
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:
@@ -392,7 +391,7 @@ public class InsnGen {
break;
case FILL_ARRAY:
FillArrayNode arrayNode = (FillArrayNode) insn;
FillArrayInsn arrayNode = (FillArrayInsn) insn;
if (fallback) {
String arrStr = arrayNode.dataToString();
addArg(code, insn.getArg(0));
@@ -467,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;
@@ -504,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(';');
@@ -536,6 +538,21 @@ public class InsnGen {
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:
throw new CodegenException(mth, "Unknown instruction: " + insn.getType());
}
@@ -545,11 +562,19 @@ public class InsnGen {
* In most cases must be combined with new array instructions.
* Use one by one array fill (can be replaced with System.arrayCopy)
*/
private void fillArray(CodeWriter code, FillArrayNode arrayNode) throws CodegenException {
private void fillArray(CodeWriter code, FillArrayInsn arrayNode) throws CodegenException {
code.add("// fill-array-data instruction");
code.startLine();
List<LiteralArg> args = arrayNode.getLiteralArgs(arrayNode.getElementType());
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) {
@@ -599,12 +624,12 @@ public class InsnGen {
code.add('}');
}
private void makeConstructor(ConstructorInsn insn, CodeWriter code)
throws CodegenException {
ClassNode cls = mth.dex().resolveClass(insn.getClassType());
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()) {
@@ -617,26 +642,24 @@ public class InsnGen {
} else {
code.add("new ");
useClass(code, insn.getClassType());
ArgType argType = insn.getResult().getSVar().getCodeVar().getType();
boolean genericCls = cls == null || !cls.getGenerics().isEmpty();
if (argType != null
&& argType.getGenericTypes() != null
&& genericCls) {
GenericInfoAttr genericInfoAttr = insn.get(AType.GENERIC_INFO);
if (genericInfoAttr != null) {
code.add('<');
if (insn.contains(AFlag.EXPLICIT_GENERICS)) {
if (genericInfoAttr.isExplicit()) {
boolean first = true;
for (ArgType type : argType.getGenericTypes()) {
for (ArgType type : genericInfoAttr.getGenericTypes()) {
if (!first) {
code.add(',');
} else {
first = false;
}
mgen.getClassGen().useType(code, type);
first = false;
}
}
code.add('>');
}
}
MethodNode callMth = mth.dex().resolveMethod(insn.getCallMth());
MethodNode callMth = mth.root().resolveMethod(insn.getCallMth());
generateMethodArguments(code, insn, 0, callMth);
}
@@ -670,20 +693,15 @@ public class InsnGen {
} 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 && inlineMethod(callMthNode, insn, code)) {
return;
}
int k = 0;
InvokeType type = insn.getInvokeType();
@@ -758,30 +776,31 @@ public class InsnGen {
return useCls.getParentClass().getClassInfo();
}
void generateMethodArguments(CodeWriter code, InsnNode insn, int startArgNum,
@Nullable MethodNode callMth) throws CodegenException {
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;
}
if (SkipMethodArgsAttr.isSkip(callMth, i - startArgNum)) {
int argOrigPos = i - startArgNum;
if (SkipMethodArgsAttr.isSkip(mthNode, argOrigPos)) {
continue;
}
if (!firstArg) {
code.add(", ");
} else {
firstArg = false;
}
boolean cast = addArgCast(code, insn, callMth, arg, i - startArgNum, overloaded);
if (!cast && i == argsCount - 1 && processVarArg(code, callMth, arg)) {
if (i == argsCount - 1 && processVarArg(code, insn, arg)) {
continue;
}
addArg(code, arg, false);
@@ -791,151 +810,31 @@ public class InsnGen {
code.add(')');
}
/**
* Add additional cast for method argument.
*/
private boolean addArgCast(CodeWriter code, InsnNode insn, @Nullable MethodNode callMth,
InsnArg arg, int origPos, boolean overloaded) {
ArgType castType = null;
if (callMth != null) {
List<ArgType> argTypes = callMth.getArgTypes();
ArgType origType = argTypes.get(origPos);
if (origType.isGenericType() && !callMth.getParentClass().equals(mth.getParentClass())) {
// cancel cast
return false;
}
if (insn instanceof CallMthInterface && origType.containsGenericType()) {
ArgType clsType;
CallMthInterface mthCall = (CallMthInterface) insn;
RegisterArg instanceArg = mthCall.getInstanceArg();
if (instanceArg != null) {
clsType = instanceArg.getType();
} else {
clsType = mthCall.getCallMth().getDeclClass().getType();
}
ArgType replacedType = TypeUtils.replaceClassGenerics(root, clsType, origType);
if (replacedType != null) {
castType = replacedType;
}
if (castType == null) {
ArgType invReplType = TypeUtils.replaceMethodGenerics(root, insn, origType);
if (invReplType != null) {
castType = invReplType;
}
}
}
if (castType == null) {
castType = origType;
}
} else {
castType = arg.getType();
}
// TODO: check castType for left type variables
if (isCastNeeded(arg, castType, overloaded)) {
code.add('(');
useType(code, castType);
code.add(") ");
return true;
}
return false;
}
private boolean isCastNeeded(InsnArg arg, ArgType origType, boolean overloaded) {
ArgType argType = arg.getType();
if (arg.isLiteral() && ((LiteralArg) arg).getLiteral() == 0
&& (argType.isObject() || argType.isArray())) {
return true;
}
if (argType.equals(origType)) {
return false;
}
return overloaded;
}
/**
* 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 (Consts.DEBUG) {
code.add("/* inline method: ").add(callMthNode.toString()).add("*/").startLine();
}
if (forceAssign(inl, insn, callMthNode)) {
ArgType varType = callMthNode.getReturnType();
useType(code, varType);
code.add(' ');
code.add(mgen.getNameGen().assignNamedArg(new NamedArg("unused", varType)));
code.add(" = ");
}
if (callMthNode.getMethodInfo().getArgumentsTypes().isEmpty()) {
makeInsn(inl, code, Flags.BODY_ONLY);
} else {
// remap args
InsnArg[] regs = new InsnArg[callMthNode.getRegsCount()];
int[] regNums = mia.getArgsRegNums();
for (int i = 0; i < regNums.length; i++) {
InsnArg arg = insn.getArg(i);
regs[regNums[i]] = 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;
}
private boolean forceAssign(InsnNode inlineInsn, InvokeNode parentInsn, MethodNode callMthNode) {
if (parentInsn.getResult() != null) {
return false;
}
if (parentInsn.contains(AFlag.WRAPPED)) {
return false;
}
return !callMthNode.getReturnType().equals(ArgType.VOID);
}
private void makeTernary(TernaryInsn insn, CodeWriter code, Set<Flags> state) throws CodegenException {
boolean wrap = state.contains(Flags.BODY_ONLY);
if (wrap) {
@@ -944,7 +843,7 @@ 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());
@@ -7,25 +7,29 @@ import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.android.dx.rop.code.AccessFlags;
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.dex.visitors.IDexTreeVisitor;
import jadx.core.utils.CodeGenUtils;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.Utils;
@@ -33,6 +37,10 @@ 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);
@@ -72,18 +80,22 @@ 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().hasAlias() && !ai.isConstructor()) {
@@ -98,8 +110,12 @@ public class MethodGen {
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.getGenerics(), false)) {
if (classGen.addGenericTypeParameters(code, mth.getTypeParameters(), false)) {
code.add(' ');
}
if (ai.isConstructor()) {
@@ -133,15 +149,32 @@ public class MethodGen {
// add default value if in annotation class
if (mth.getParentClass().getAccessFlags().isAnnotation()) {
Object def = annotationGen.getAnnotationDefaultValue(mth.getName());
EncodedValue def = annotationGen.getAnnotationDefaultValue(mth.getName());
if (def != null) {
code.add(" default ");
annotationGen.encodeValue(code, def);
annotationGen.encodeValue(mth.root(), code, def);
}
}
return true;
}
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;
@@ -152,7 +185,7 @@ public class MethodGen {
CodeVar var;
if (ssaVar == null) {
// null for abstract or interface methods
var = CodeVar.fromMthArg(mthArg);
var = CodeVar.fromMthArg(mthArg, classGen.isFallbackMode());
} else {
var = ssaVar.getCodeVar();
}
@@ -197,7 +230,7 @@ public class MethodGen {
public void addInstructions(CodeWriter code) throws CodegenException {
if (mth.root().getArgs().isFallbackMode()) {
addFallbackMethodCode(code);
addFallbackMethodCode(code, FALLBACK_MODE);
} else if (classGen.isFallbackMode()) {
dumpInstructions(code);
} else {
@@ -225,7 +258,7 @@ public class MethodGen {
public void dumpInstructions(CodeWriter code) {
code.startLine("/*");
addFallbackMethodCode(code);
addFallbackMethodCode(code, COMMENTED_DUMP);
code.startLine("*/");
code.startLine("throw new UnsupportedOperationException(\"Method not decompiled: ")
@@ -239,33 +272,44 @@ public class MethodGen {
.add("\");");
}
public void addFallbackMethodCode(CodeWriter code) {
if (mth.getInstructions() == null) {
// load original instructions
try {
mth.unload();
mth.load();
DepthTraversal.visit(new FallbackModeVisitor(), mth);
} catch (DecodeException e) {
LOG.error("Error reload instructions in fallback mode:", e);
code.startLine("// Can't load method instructions: " + e.getMessage());
return;
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;
@@ -273,7 +317,7 @@ public class MethodGen {
if (insn == null) {
continue;
}
if (addLabels && needLabel(insn, prevInsn)) {
if (option != BLOCK_DUMP && needLabel(insn, prevInsn)) {
code.decIndent();
code.startLine(getLabelName(insn.getOffset()) + ':');
code.incIndent();
@@ -282,7 +326,14 @@ public class MethodGen {
continue;
}
try {
code.startLine();
boolean escapeComment = isCommentEscapeNeeded(insn, option);
if (escapeComment) {
code.decIndent();
code.startLine("*/");
code.startLine("// ");
} else {
code.startLine();
}
if (attachInsns) {
code.attachLineAnnotation(insn);
}
@@ -294,18 +345,34 @@ public class MethodGen {
}
}
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 (CodegenException e) {
} 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;
@@ -72,11 +72,13 @@ public class NameGen {
}
public String assignArg(CodeVar var) {
String name = makeArgName(var);
if (fallback) {
return name;
return getFallbackName(var);
}
name = getUniqueVarName(name);
if (var.isThis()) {
return RegisterArg.THIS_ARG_NAME;
}
String name = getUniqueVarName(makeArgName(var));
var.setName(name);
return name;
}
@@ -118,24 +120,17 @@ public class NameGen {
}
private String makeArgName(CodeVar var) {
if (fallback) {
return getFallbackName(var);
}
if (var.isThis()) {
return RegisterArg.THIS_ARG_NAME;
}
String name = var.getName();
String varName = name != null ? name : guessName(var);
if (NameMapper.isReserved(varName)) {
varName = varName + 'R';
if (name == null) {
name = guessName(var);
}
if (!NameMapper.isValidAndPrintable(varName)) {
varName = getFallbackName(var);
if (!NameMapper.isValidAndPrintable(name)) {
name = getFallbackName(var);
}
if (Consts.DEBUG) {
varName += '_' + getFallbackName(var);
name += '_' + getFallbackName(var);
}
return varName;
return name;
}
private String getFallbackName(CodeVar var) {
@@ -10,11 +10,13 @@ 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.info.ClassInfo;
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.CodeVar;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.NamedArg;
@@ -25,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;
@@ -38,7 +40,6 @@ import jadx.core.dex.regions.loops.LoopRegion;
import jadx.core.dex.regions.loops.LoopType;
import jadx.core.dex.trycatch.ExceptionHandler;
import jadx.core.utils.BlockUtils;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.RegionUtils;
import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.JadxRuntimeException;
@@ -181,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(':');
@@ -262,7 +251,7 @@ public class RegionGen extends InsnGen {
}
private CodeWriter makeSwitch(SwitchRegion sw, CodeWriter code) throws CodegenException {
SwitchNode insn = (SwitchNode) BlockUtils.getLastInsn(sw.getHeader());
SwitchInsn insn = (SwitchInsn) BlockUtils.getLastInsn(sw.getHeader());
Objects.requireNonNull(insn, "Switch insn not found in header");
InsnArg arg = insn.getArg(0);
code.startLine("switch (");
@@ -270,42 +259,48 @@ public class RegionGen extends InsnGen {
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, fallback));
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());
@@ -340,7 +335,7 @@ public class RegionGen extends InsnGen {
}
code.startLine("} catch (");
if (handler.isCatchAll()) {
code.add("Throwable");
useClass(code, ArgType.THROWABLE);
} else {
Iterator<ClassInfo> it = handler.getCatchTypes().iterator();
if (it.hasNext()) {
@@ -353,11 +348,15 @@ public class RegionGen extends InsnGen {
}
code.add(' ');
InsnArg arg = handler.getArg();
if (arg instanceof RegisterArg) {
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,7 +3,6 @@ package jadx.core.codegen;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.deobf.NameMapper;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.LiteralArg;
@@ -71,11 +70,7 @@ 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(lit, cast);
case SHORT:
@@ -13,6 +13,7 @@ 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;
@@ -67,7 +68,7 @@ public class JsonCodeGen {
JsonClass jsonCls = new JsonClass();
jsonCls.setPkg(classInfo.getAliasPkg());
jsonCls.setDex(cls.dex().getDexFile().getName());
jsonCls.setDex(cls.getInputFileName());
jsonCls.setName(classInfo.getFullName());
if (classInfo.hasAlias()) {
jsonCls.setAlias(classInfo.getAliasFullName());
@@ -85,7 +86,7 @@ public class JsonCodeGen {
CodeGenUtils.addComments(cw, cls);
classGen.insertDecompilationProblems(cw, cls);
classGen.addClassDeclaration(cw);
jsonCls.setDeclaration(cw.finish().toString());
jsonCls.setDeclaration(cw.getCodeStr());
addFields(cls, jsonCls, classGen);
addMethods(cls, jsonCls, classGen);
@@ -128,7 +129,7 @@ public class JsonCodeGen {
CodeWriter cw = new CodeWriter();
classGen.addField(cw, field);
jsonField.setDeclaration(cw.finish().toString());
jsonField.setDeclaration(cw.getCodeStr());
jsonField.setAccessFlags(field.getAccessFlags().rawValue());
jsonCls.getFields().add(jsonField);
@@ -153,7 +154,7 @@ public class JsonCodeGen {
MethodGen mthGen = new MethodGen(classGen, mth);
CodeWriter cw = new CodeWriter();
mthGen.addDefinition(cw);
jsonMth.setDeclaration(cw.finish().toString());
jsonMth.setDeclaration(cw.getCodeStr());
jsonMth.setAccessFlags(mth.getAccessFlags().rawValue());
jsonMth.setLines(fillMthCode(mth, mthGen));
jsonMth.setOffset("0x" + Long.toHexString(mth.getMethodCodeOffset()));
@@ -166,14 +167,14 @@ public class JsonCodeGen {
return Collections.emptyList();
}
CodeWriter code = new CodeWriter();
CodeWriter cw = new CodeWriter();
try {
mthGen.addInstructions(code);
mthGen.addInstructions(cw);
} catch (Exception e) {
throw new JadxRuntimeException("Method generation error", e);
}
code.finish();
String codeStr = code.toString();
ICodeInfo code = cw.finish();
String codeStr = code.getCodeStr();
if (codeStr.isEmpty()) {
return Collections.emptyList();
}
@@ -12,7 +12,6 @@ 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;
@@ -26,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);
@@ -39,8 +39,7 @@ 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 LinkedHashMap<>();
@@ -57,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, Path 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);
}
@@ -104,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);
}
}
@@ -121,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();
}
@@ -207,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);
}
@@ -354,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()) {
@@ -369,12 +363,12 @@ public class Deobfuscator {
if (deobfClsInfo != null) {
return deobfClsInfo.getAlias();
}
return makeClsAlias(cls);
return makeClsAlias(cls, true);
}
public String getPkgAlias(ClassNode cls) {
ClassInfo classInfo = cls.getClassInfo();
PackageNode pkg = null;
PackageNode pkg;
DeobfClsInfo deobfClsInfo = clsMap.get(classInfo);
if (deobfClsInfo != null) {
pkg = deobfClsInfo.getPkg();
@@ -390,23 +384,94 @@ public class Deobfuscator {
}
}
private String makeClsAlias(ClassNode cls) {
ClassInfo classInfo = cls.getClassInfo();
private String makeClsAlias(ClassNode cls, boolean badName) {
String alias = null;
if (this.useSourceNameAsAlias) {
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++, prepareNamePart(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);
@@ -430,7 +495,7 @@ public class Deobfuscator {
return null;
}
}
ClassNode otherCls = cls.root().searchClassByName(cls.getPackage() + '.' + name);
ClassNode otherCls = cls.root().resolveClass(cls.getPackage() + '.' + name);
if (otherCls != null) {
return null;
}
@@ -528,6 +593,24 @@ public class Deobfuscator {
return NameMapper.removeInvalidCharsMiddle(name);
}
private String prepareNameFull(String name, String prefix) {
if (name.length() > maxLength) {
return makeHashName(name, prefix);
}
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) {
PackageNode pkg = getPackageNode(cls.getPackage(), false);
@@ -541,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);
}
}
@@ -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;
}
@@ -15,13 +15,13 @@ public enum AFlag {
DONT_WRAP,
DONT_INLINE,
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
RESTART_CODEGEN,
DONT_RENAME, // do not rename during deobfuscation
ADDED_TO_REGION,
@@ -33,6 +33,7 @@ public enum AFlag {
ANONYMOUS_CLASS,
THIS,
SUPER,
/**
* RegisterArg attribute for method arguments
@@ -59,13 +60,23 @@ public enum AFlag {
FALL_THROUGH,
EXPLICIT_GENERICS,
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!)
}
@@ -12,6 +12,7 @@ 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.JumpInfo;
@@ -19,12 +20,14 @@ 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;
@@ -35,6 +38,7 @@ import jadx.core.dex.trycatch.SplitterBlockAttr;
*
* @param <T> attribute class implementation
*/
@SuppressWarnings("InstantiationOfUtilityClass")
public class AType<T extends IAttribute> {
// class, method, field
@@ -60,6 +64,8 @@ public class AType<T extends IAttribute> {
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<>();
@@ -79,6 +85,8 @@ public class AType<T extends IAttribute> {
// instruction
public static final AType<LoopLabelAttr> LOOP_LABEL = 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<>();
@@ -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, "\n");
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 {
@@ -64,7 +64,7 @@ public abstract class AttrNode implements IAttributeNode {
}
@Override
public Annotation getAnnotation(String cls) {
public IAnnotation getAnnotation(String cls) {
return storage.getAnnotation(cls);
}
@@ -118,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,6 +20,13 @@ 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 Map<AType<?>, IAttribute> attributes;
@@ -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);
}
@@ -127,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;
}
@@ -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;
}
@@ -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() {
@@ -2,4 +2,8 @@ package jadx.core.dex.attributes;
public interface IAttribute {
AType<? extends IAttribute> getType();
default String toAttrString() {
return this.toString();
}
}
@@ -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 interface IAttributeNode {
@@ -20,7 +20,7 @@ public interface IAttributeNode {
<T extends IAttribute> T get(AType<T> type);
Annotation getAnnotation(String cls);
IAnnotation getAnnotation(String cls);
<T> List<T> getAll(AType<AttrList<T>> type);
@@ -35,4 +35,6 @@ public interface IAttributeNode {
List<String> getAttributesStringsList();
String getAttributesString();
boolean isAttrStorageEmpty();
}
@@ -1,47 +0,0 @@
package jadx.core.dex.attributes.annotations;
import java.util.Map;
import jadx.core.dex.instructions.args.ArgType;
public class Annotation {
public enum Visibility {
BUILD, RUNTIME, SYSTEM
}
private final Visibility visibility;
private final ArgType atype;
private final Map<String, Object> values;
public Annotation(Visibility visibility, ArgType type, Map<String, Object> values) {
this.visibility = visibility;
this.atype = type;
this.values = values;
}
public Visibility getVisibility() {
return visibility;
}
public ArgType getType() {
return atype;
}
public String getAnnotationClass() {
return atype.getObject();
}
public Map<String, Object> getValues() {
return values;
}
public Object getDefaultValue() {
return values.get("value");
}
@Override
public String toString() {
return "Annotation[" + visibility + ", " + atype + ", " + values + ']';
}
}
@@ -1,33 +1,50 @@
package jadx.core.dex.attributes.annotations;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jetbrains.annotations.Nullable;
import jadx.api.plugins.input.data.annotations.IAnnotation;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.nodes.ICodeNode;
import jadx.core.utils.Utils;
public class AnnotationsList implements IAttribute {
public static final AnnotationsList EMPTY = new AnnotationsList(Collections.emptyList());
private final Map<String, Annotation> map;
public AnnotationsList(List<Annotation> anList) {
map = new HashMap<>(anList.size());
for (Annotation a : anList) {
map.put(a.getAnnotationClass(), a);
public static void attach(ICodeNode node, List<IAnnotation> annotationList) {
AnnotationsList attrList = pack(annotationList);
if (attrList != null) {
node.addAttr(attrList);
}
}
public Annotation get(String className) {
@Nullable
public static AnnotationsList pack(List<IAnnotation> annotationList) {
if (annotationList.isEmpty()) {
return null;
}
Map<String, IAnnotation> annMap = new HashMap<>(annotationList.size());
for (IAnnotation ann : annotationList) {
annMap.put(ann.getAnnotationClass(), ann);
}
return new AnnotationsList(annMap);
}
private final Map<String, IAnnotation> map;
public AnnotationsList(Map<String, IAnnotation> map) {
this.map = map;
}
public IAnnotation get(String className) {
return map.get(className);
}
public Collection<Annotation> getAll() {
public Collection<IAnnotation> getAll() {
return map.values();
}
@@ -3,16 +3,29 @@ package jadx.core.dex.attributes.annotations;
import java.util.ArrayList;
import java.util.List;
import jadx.api.plugins.input.data.annotations.IAnnotation;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.nodes.ICodeNode;
import jadx.core.utils.Utils;
public class MethodParameters implements IAttribute {
public static void attach(ICodeNode node, List<List<IAnnotation>> annotationRefList) {
if (annotationRefList.isEmpty()) {
return;
}
List<AnnotationsList> list = new ArrayList<>(annotationRefList.size());
for (List<IAnnotation> annList : annotationRefList) {
list.add(AnnotationsList.pack(annList));
}
node.addAttr(new MethodParameters(list));
}
private final List<AnnotationsList> paramList;
public MethodParameters(int paramCount) {
paramList = new ArrayList<>(paramCount);
public MethodParameters(List<AnnotationsList> paramsList) {
this.paramList = paramsList;
}
public List<AnnotationsList> getParamList() {
@@ -1,30 +1,27 @@
package jadx.core.dex.attributes.nodes;
import java.util.ArrayList;
import java.util.List;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.mods.ConstructorInsn;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
public class EnumClassAttr implements IAttribute {
public static class EnumField {
private final FieldInfo field;
private final FieldNode field;
private final ConstructorInsn constrInsn;
private final int startArg;
private ClassNode cls;
public EnumField(FieldInfo field, ConstructorInsn co, int startArg) {
public EnumField(FieldNode field, ConstructorInsn co) {
this.field = field;
this.constrInsn = co;
this.startArg = startArg;
}
public FieldInfo getField() {
public FieldNode getField() {
return field;
}
@@ -32,10 +29,6 @@ public class EnumClassAttr implements IAttribute {
return constrInsn;
}
public int getStartArg() {
return startArg;
}
public ClassNode getCls() {
return cls;
}
@@ -53,8 +46,8 @@ public class EnumClassAttr implements IAttribute {
private final List<EnumField> fields;
private MethodNode staticMethod;
public EnumClassAttr(int fieldsCount) {
this.fields = new ArrayList<>(fieldsCount);
public EnumClassAttr(List<EnumField> fields) {
this.fields = fields;
}
public List<EnumField> getFields() {
@@ -0,0 +1,38 @@
package jadx.core.dex.attributes.nodes;
import java.util.List;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.instructions.args.ArgType;
public class GenericInfoAttr implements IAttribute {
private final List<ArgType> genericTypes;
private boolean explicit;
public GenericInfoAttr(List<ArgType> genericTypes) {
this.genericTypes = genericTypes;
}
public List<ArgType> getGenericTypes() {
return genericTypes;
}
public boolean isExplicit() {
return explicit;
}
public void setExplicit(boolean explicit) {
this.explicit = explicit;
}
@Override
public AType<GenericInfoAttr> getType() {
return AType.GENERIC_INFO;
}
@Override
public String toString() {
return "GenericInfoAttr{" + genericTypes + ", explicit=" + explicit + '}';
}
}
@@ -4,6 +4,7 @@ import java.util.Objects;
import org.jetbrains.annotations.NotNull;
import jadx.core.codegen.CodeWriter;
import jadx.core.utils.Utils;
public class JadxError implements Comparable<JadxError> {
@@ -58,7 +59,7 @@ public class JadxError implements Comparable<JadxError> {
str.append(cause.getClass());
str.append(':');
str.append(cause.getMessage());
str.append('\n');
str.append(CodeWriter.NL);
str.append(Utils.getStackTrace(cause));
}
return str.toString();
@@ -2,19 +2,21 @@ package jadx.core.dex.attributes.nodes;
import java.util.List;
import jadx.api.plugins.input.data.ILocalVar;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.visitors.debuginfo.LocalVar;
import jadx.core.utils.Utils;
public class LocalVarsDebugInfoAttr implements IAttribute {
private final List<LocalVar> localVars;
import static jadx.core.codegen.CodeWriter.NL;
public LocalVarsDebugInfoAttr(List<LocalVar> localVars) {
public class LocalVarsDebugInfoAttr implements IAttribute {
private final List<ILocalVar> localVars;
public LocalVarsDebugInfoAttr(List<ILocalVar> localVars) {
this.localVars = localVars;
}
public List<LocalVar> getLocalVars() {
public List<ILocalVar> getLocalVars() {
return localVars;
}
@@ -25,6 +27,6 @@ public class LocalVarsDebugInfoAttr implements IAttribute {
@Override
public String toString() {
return "Debug Info:\n " + Utils.listToString(localVars, "\n ");
return "Debug Info:" + NL + " " + Utils.listToString(localVars, NL + " ");
}
}
@@ -72,6 +72,10 @@ public class LoopInfo {
return edges;
}
public BlockNode getPreHeader() {
return BlockUtils.selectOther(end, start.getPredecessors());
}
public int getId() {
return id;
}
@@ -1,7 +1,10 @@
package jadx.core.dex.attributes.nodes;
import java.util.List;
import java.util.Objects;
import jadx.core.Consts;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.instructions.args.RegisterArg;
@@ -10,7 +13,10 @@ import jadx.core.dex.nodes.MethodNode;
public class MethodInlineAttr implements IAttribute {
public static void markForInline(MethodNode mth, InsnNode replaceInsn) {
private static final MethodInlineAttr INLINE_NOT_NEEDED = new MethodInlineAttr(null, null);
public static MethodInlineAttr markForInline(MethodNode mth, InsnNode replaceInsn) {
Objects.requireNonNull(replaceInsn);
List<RegisterArg> allArgRegs = mth.getAllArgRegs();
int argsCount = allArgRegs.size();
int[] regNums = new int[argsCount];
@@ -18,7 +24,19 @@ public class MethodInlineAttr implements IAttribute {
RegisterArg reg = allArgRegs.get(i);
regNums[i] = reg.getRegNum();
}
mth.addAttr(new MethodInlineAttr(replaceInsn, regNums));
MethodInlineAttr mia = new MethodInlineAttr(replaceInsn, regNums);
mth.addAttr(mia);
if (Consts.DEBUG) {
mth.addAttr(AType.COMMENTS, "Removed for inline");
} else {
mth.add(AFlag.DONT_GENERATE);
}
return mia;
}
public static MethodInlineAttr inlineNotNeeded(MethodNode mth) {
mth.addAttr(INLINE_NOT_NEEDED);
return INLINE_NOT_NEEDED;
}
private final InsnNode insn;
@@ -33,6 +51,10 @@ public class MethodInlineAttr implements IAttribute {
this.argsRegNums = argsRegNums;
}
public boolean notNeeded() {
return insn == null;
}
public InsnNode getInsn() {
return insn;
}
@@ -48,6 +70,9 @@ public class MethodInlineAttr implements IAttribute {
@Override
public String toString() {
if (notNeeded()) {
return "INLINE_NOT_NEEDED";
}
return "INLINE: " + insn;
}
}
@@ -0,0 +1,30 @@
package jadx.core.dex.attributes.nodes;
import java.util.List;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.nodes.IMethodDetails;
public class MethodOverrideAttr implements IAttribute {
private final List<IMethodDetails> overrideList;
public MethodOverrideAttr(List<IMethodDetails> overrideList) {
this.overrideList = overrideList;
}
public List<IMethodDetails> getOverrideList() {
return overrideList;
}
@Override
public AType<MethodOverrideAttr> getType() {
return AType.METHOD_OVERRIDE;
}
@Override
public String toString() {
return "METHOD_OVERRIDE: " + overrideList;
}
}
@@ -0,0 +1,47 @@
package jadx.core.dex.attributes.nodes;
import java.util.Collections;
import java.util.Set;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.instructions.args.ArgType;
import static jadx.core.utils.Utils.isEmpty;
/**
* Set of known type variables at current method
*/
public class MethodTypeVarsAttr implements IAttribute {
private static final MethodTypeVarsAttr EMPTY = new MethodTypeVarsAttr(Collections.emptySet());
public static MethodTypeVarsAttr build(Set<ArgType> typeVars) {
if (isEmpty(typeVars)) {
return EMPTY;
}
return new MethodTypeVarsAttr(typeVars);
}
private final Set<ArgType> typeVars;
private MethodTypeVarsAttr(Set<ArgType> typeVars) {
this.typeVars = typeVars;
}
public Set<ArgType> getTypeVars() {
return typeVars;
}
@Override
public AType<MethodTypeVarsAttr> getType() {
return AType.METHOD_TYPE_VARS;
}
@Override
public String toString() {
if (this == EMPTY) {
return "TYPE_VARS: EMPTY";
}
return "TYPE_VARS: " + typeVars;
}
}
@@ -0,0 +1,45 @@
package jadx.core.dex.attributes.nodes;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.nodes.ICodeNode;
import jadx.core.utils.ErrorsCounter;
public abstract class NotificationAttrNode extends LineAttrNode implements ICodeNode {
private static final Logger LOG = LoggerFactory.getLogger(NotificationAttrNode.class);
public void addError(String errStr, Throwable e) {
ErrorsCounter.error(this, errStr, e);
}
public void addWarn(String warnStr) {
ErrorsCounter.warning(this, warnStr);
}
public void addWarnComment(String warn) {
addWarnComment(warn, null);
}
public void addWarnComment(String warn, @Nullable Throwable exc) {
String commentStr = "JADX WARN: " + warn;
addAttr(AType.COMMENTS, commentStr);
if (exc != null) {
LOG.warn("{} in {}", warn, this, exc);
} else {
LOG.warn("{} in {}", warn, this);
}
}
public void addComment(String commentStr) {
addAttr(AType.COMMENTS, commentStr);
LOG.info("{} in {}", commentStr, this);
}
public void addDebugComment(String commentStr) {
addAttr(AType.COMMENTS, "JADX DEBUG: " + commentStr);
LOG.debug("{} in {}", commentStr, this);
}
}
@@ -3,6 +3,7 @@ package jadx.core.dex.attributes.nodes;
import java.util.LinkedList;
import java.util.List;
import jadx.core.codegen.CodeWriter;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.instructions.PhiInsn;
@@ -28,7 +29,7 @@ public class PhiListAttr implements IAttribute {
sb.append('r').append(phiInsn.getResult().getRegNum()).append(' ');
}
for (PhiInsn phiInsn : list) {
sb.append("\n ").append(phiInsn).append(' ').append(phiInsn.getAttributesString());
sb.append(CodeWriter.NL).append(" ").append(phiInsn).append(' ').append(phiInsn.getAttributesString());
}
return sb.toString();
}
@@ -5,17 +5,12 @@ import java.util.Objects;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.visitors.debuginfo.LocalVar;
public class RegDebugInfoAttr implements IAttribute {
private final ArgType type;
private final String name;
public RegDebugInfoAttr(LocalVar var) {
this(var.getType(), var.getName());
}
public RegDebugInfoAttr(ArgType type, String name) {
this.type = type;
this.name = name;
@@ -44,7 +44,7 @@ public class SkipMethodArgsAttr implements IAttribute {
private final BitSet skipArgs;
private SkipMethodArgsAttr(MethodNode mth) {
this.skipArgs = new BitSet(mth.getArgRegs().size());
this.skipArgs = new BitSet(mth.getMethodInfo().getArgsCount());
}
public void skip(int argNum) {
@@ -55,6 +55,10 @@ public class SkipMethodArgsAttr implements IAttribute {
return skipArgs.get(argNum);
}
public int getSkipCount() {
return skipArgs.cardinality();
}
@Override
public AType<SkipMethodArgsAttr> getType() {
return AType.SKIP_MTH_ARGS;
@@ -1,13 +1,12 @@
package jadx.core.dex.info;
import com.android.dx.rop.code.AccessFlags;
import jadx.api.plugins.input.data.AccessFlags;
import jadx.core.Consts;
import jadx.core.utils.exceptions.JadxRuntimeException;
public class AccessInfo {
public static final int VISIBILITY_FLAGS = AccessFlags.ACC_PUBLIC | AccessFlags.ACC_PROTECTED | AccessFlags.ACC_PRIVATE;
public static final int VISIBILITY_FLAGS = AccessFlags.PUBLIC | AccessFlags.PROTECTED | AccessFlags.PRIVATE;
private final int accFlags;
public enum AFType {
@@ -53,15 +52,15 @@ public class AccessInfo {
}
public boolean isPublic() {
return (accFlags & AccessFlags.ACC_PUBLIC) != 0;
return (accFlags & AccessFlags.PUBLIC) != 0;
}
public boolean isProtected() {
return (accFlags & AccessFlags.ACC_PROTECTED) != 0;
return (accFlags & AccessFlags.PROTECTED) != 0;
}
public boolean isPrivate() {
return (accFlags & AccessFlags.ACC_PRIVATE) != 0;
return (accFlags & AccessFlags.PRIVATE) != 0;
}
public boolean isPackagePrivate() {
@@ -69,59 +68,59 @@ public class AccessInfo {
}
public boolean isAbstract() {
return (accFlags & AccessFlags.ACC_ABSTRACT) != 0;
return (accFlags & AccessFlags.ABSTRACT) != 0;
}
public boolean isInterface() {
return (accFlags & AccessFlags.ACC_INTERFACE) != 0;
return (accFlags & AccessFlags.INTERFACE) != 0;
}
public boolean isAnnotation() {
return (accFlags & AccessFlags.ACC_ANNOTATION) != 0;
return (accFlags & AccessFlags.ANNOTATION) != 0;
}
public boolean isNative() {
return (accFlags & AccessFlags.ACC_NATIVE) != 0;
return (accFlags & AccessFlags.NATIVE) != 0;
}
public boolean isStatic() {
return (accFlags & AccessFlags.ACC_STATIC) != 0;
return (accFlags & AccessFlags.STATIC) != 0;
}
public boolean isFinal() {
return (accFlags & AccessFlags.ACC_FINAL) != 0;
return (accFlags & AccessFlags.FINAL) != 0;
}
public boolean isConstructor() {
return (accFlags & AccessFlags.ACC_CONSTRUCTOR) != 0;
return (accFlags & AccessFlags.CONSTRUCTOR) != 0;
}
public boolean isEnum() {
return (accFlags & AccessFlags.ACC_ENUM) != 0;
return (accFlags & AccessFlags.ENUM) != 0;
}
public boolean isSynthetic() {
return (accFlags & AccessFlags.ACC_SYNTHETIC) != 0;
return (accFlags & AccessFlags.SYNTHETIC) != 0;
}
public boolean isBridge() {
return (accFlags & AccessFlags.ACC_BRIDGE) != 0;
return (accFlags & AccessFlags.BRIDGE) != 0;
}
public boolean isVarArgs() {
return (accFlags & AccessFlags.ACC_VARARGS) != 0;
return (accFlags & AccessFlags.VARARGS) != 0;
}
public boolean isSynchronized() {
return (accFlags & (AccessFlags.ACC_SYNCHRONIZED | AccessFlags.ACC_DECLARED_SYNCHRONIZED)) != 0;
return (accFlags & (AccessFlags.SYNCHRONIZED | AccessFlags.DECLARED_SYNCHRONIZED)) != 0;
}
public boolean isTransient() {
return (accFlags & AccessFlags.ACC_TRANSIENT) != 0;
return (accFlags & AccessFlags.TRANSIENT) != 0;
}
public boolean isVolatile() {
return (accFlags & AccessFlags.ACC_VOLATILE) != 0;
return (accFlags & AccessFlags.VOLATILE) != 0;
}
public AFType getType() {
@@ -174,14 +173,14 @@ public class AccessInfo {
break;
case CLASS:
if ((accFlags & AccessFlags.ACC_STRICT) != 0) {
if ((accFlags & AccessFlags.STRICT) != 0) {
code.append("strict ");
}
if (Consts.DEBUG) {
if ((accFlags & AccessFlags.ACC_SUPER) != 0) {
if ((accFlags & AccessFlags.SUPER) != 0) {
code.append("/* super */ ");
}
if ((accFlags & AccessFlags.ACC_ENUM) != 0) {
if ((accFlags & AccessFlags.ENUM) != 0) {
code.append("/* enum */ ");
}
}
@@ -209,25 +208,12 @@ public class AccessInfo {
throw new JadxRuntimeException("Unknown visibility flags: " + getVisibility());
}
public String rawString() {
switch (type) {
case CLASS:
return AccessFlags.classString(accFlags);
case FIELD:
return AccessFlags.fieldString(accFlags);
case METHOD:
return AccessFlags.methodString(accFlags);
default:
return "?";
}
}
public int rawValue() {
return accFlags;
}
@Override
public String toString() {
return "AccessInfo: " + type + " 0x" + Integer.toHexString(accFlags) + " (" + rawString() + ')';
return "AccessInfo: " + type + " 0x" + Integer.toHexString(accFlags) + " (" + makeString() + ')';
}
}
@@ -7,7 +7,6 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.exceptions.JadxRuntimeException;
@@ -37,13 +36,6 @@ public final class ClassInfo implements Comparable<ClassInfo> {
return root.getInfoStorage().putCls(newClsInfo);
}
public static ClassInfo fromDex(DexNode dex, int clsIndex) {
if (clsIndex == DexNode.NO_INDEX) {
return null;
}
return fromType(dex.root(), dex.getType(clsIndex));
}
public static ClassInfo fromName(RootNode root, String clsName) {
return fromType(root, ArgType.object(clsName));
}
@@ -70,6 +62,8 @@ public final class ClassInfo implements Comparable<ClassInfo> {
ClassAliasInfo newAlias = new ClassAliasInfo(getAliasPkg(), aliasName);
fillAliasFullName(newAlias);
this.alias = newAlias;
} else {
this.alias = null;
}
}
@@ -2,26 +2,28 @@ package jadx.core.dex.info;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.jetbrains.annotations.Nullable;
import jadx.api.JadxArgs;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.FieldInitAttr;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.PrimitiveType;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.parser.FieldInitAttr;
import jadx.core.utils.ErrorsCounter;
import jadx.core.dex.nodes.RootNode;
public class ConstStorage {
private static final class ValueStorage {
private final Map<Object, FieldNode> values = new HashMap<>();
private final Map<Object, FieldNode> values = new ConcurrentHashMap<>();
private final Set<Object> duplicates = new HashSet<>();
public Map<Object, FieldNode> getValues() {
@@ -36,22 +38,33 @@ public class ConstStorage {
* @return true if this value is duplicated
*/
public boolean put(Object value, FieldNode fld) {
if (duplicates.contains(value)) {
values.remove(value);
return true;
}
FieldNode prev = values.put(value, fld);
if (prev != null) {
values.remove(value);
duplicates.add(value);
return true;
}
if (duplicates.contains(value)) {
values.remove(value);
return true;
}
return false;
}
public boolean contains(Object value) {
return duplicates.contains(value) || values.containsKey(value);
}
void removeForCls(ClassNode cls) {
Iterator<Entry<Object, FieldNode>> it = values.entrySet().iterator();
while (it.hasNext()) {
Entry<Object, FieldNode> entry = it.next();
FieldNode field = entry.getValue();
if (field.getParentClass().equals(cls)) {
it.remove();
}
}
}
}
private final boolean replaceEnabled;
@@ -73,15 +86,20 @@ public class ConstStorage {
if (accFlags.isStatic() && accFlags.isFinal()) {
FieldInitAttr fv = f.get(AType.FIELD_INIT);
if (fv != null
&& fv.getValue() != null
&& fv.getValueType() == FieldInitAttr.InitType.CONST
&& fv != FieldInitAttr.NULL_VALUE) {
addConstField(cls, f, fv.getValue(), accFlags.isPublic());
&& fv != FieldInitAttr.NULL_VALUE
&& fv.getEncodedValue() != null) {
addConstField(cls, f, fv.getEncodedValue().getValue(), accFlags.isPublic());
}
}
}
}
public void removeForClass(ClassNode cls) {
classes.remove(cls);
globalValues.removeForCls(cls);
}
private void addConstField(ClassNode cls, FieldNode fld, Object value, boolean isPublic) {
if (isPublic) {
globalValues.put(value, fld);
@@ -91,12 +109,7 @@ public class ConstStorage {
}
private ValueStorage getClsValues(ClassNode cls) {
ValueStorage classValues = classes.get(cls);
if (classValues == null) {
classValues = new ValueStorage();
classes.put(cls, classValues);
}
return classValues;
return classes.computeIfAbsent(cls, c -> new ValueStorage());
}
@Nullable
@@ -104,9 +117,9 @@ public class ConstStorage {
if (!replaceEnabled) {
return null;
}
DexNode dex = cls.dex();
RootNode root = cls.root();
if (value instanceof Integer) {
FieldNode rField = getResourceField((Integer) value, dex);
FieldNode rField = getResourceField((Integer) value, root);
if (rField != null) {
return rField;
}
@@ -131,7 +144,7 @@ public class ConstStorage {
if (parentClass == null) {
break;
}
current = dex.resolveClass(parentClass);
current = root.resolveClass(parentClass);
}
if (searchGlobal) {
return globalValues.get(value);
@@ -140,12 +153,12 @@ public class ConstStorage {
}
@Nullable
private FieldNode getResourceField(Integer value, DexNode dex) {
private FieldNode getResourceField(Integer value, RootNode root) {
String str = resourcesNames.get(value);
if (str == null) {
return null;
}
ClassNode appResClass = dex.root().getAppResClass();
ClassNode appResClass = root.getAppResClass();
if (appResClass == null) {
return null;
}
@@ -160,7 +173,7 @@ public class ConstStorage {
return innerClass.searchFieldByName(fieldName);
}
}
ErrorsCounter.classWarn(appResClass, "Not found resource field with id: " + value + ", name: " + str.replace('/', '.'));
appResClass.addWarn("Not found resource field with id: " + value + ", name: " + str.replace('/', '.'));
return null;
}
@@ -2,11 +2,10 @@ package jadx.core.dex.info;
import java.util.Objects;
import com.android.dex.FieldId;
import jadx.api.plugins.input.data.IFieldData;
import jadx.core.codegen.TypeGen;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.RootNode;
public final class FieldInfo {
@@ -22,17 +21,15 @@ public final class FieldInfo {
this.alias = name;
}
public static FieldInfo from(DexNode dex, ClassInfo declClass, String name, ArgType type) {
public static FieldInfo from(RootNode root, ClassInfo declClass, String name, ArgType type) {
FieldInfo field = new FieldInfo(declClass, name, type);
return dex.root().getInfoStorage().getField(field);
return root.getInfoStorage().getField(field);
}
public static FieldInfo fromDex(DexNode dex, int index) {
FieldId field = dex.getFieldId(index);
return from(dex,
ClassInfo.fromDex(dex, field.getDeclaringClassIndex()),
dex.getString(field.getNameIndex()),
dex.getType(field.getTypeIndex()));
public static FieldInfo fromData(RootNode root, IFieldData fieldData) {
ClassInfo declClass = ClassInfo.fromName(root, fieldData.getParentClassType());
FieldInfo field = new FieldInfo(declClass, fieldData.getName(), ArgType.parse(fieldData.getType()));
return root.getInfoStorage().getField(field);
}
public String getName() {
@@ -4,13 +4,15 @@ import java.util.HashMap;
import java.util.Map;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.DexNode;
public class InfoStorage {
private final Map<ArgType, ClassInfo> classes = new HashMap<>();
private final Map<Integer, MethodInfo> methods = new HashMap<>();
private final Map<FieldInfo, FieldInfo> fields = new HashMap<>();
// use only one MethodInfo instance
private final Map<MethodInfo, MethodInfo> uniqueMethods = new HashMap<>();
// can contain same method with different ids (from different dex files)
private final Map<Integer, MethodInfo> methods = new HashMap<>();
public ClassInfo getCls(ArgType type) {
return classes.get(type);
@@ -23,18 +25,26 @@ public class InfoStorage {
}
}
private int generateMethodLookupId(DexNode dex, int mthId) {
return dex.getDexId() << 16 | mthId;
}
public MethodInfo getMethod(DexNode dex, int mtdId) {
return methods.get(generateMethodLookupId(dex, mtdId));
}
public MethodInfo putMethod(DexNode dex, int mthId, MethodInfo mth) {
public MethodInfo getByUniqId(int id) {
synchronized (methods) {
MethodInfo prev = methods.put(generateMethodLookupId(dex, mthId), mth);
return prev == null ? mth : prev;
return methods.get(id);
}
}
public void putByUniqId(int id, MethodInfo mth) {
synchronized (methods) {
methods.put(id, mth);
}
}
public MethodInfo putMethod(MethodInfo newMth) {
synchronized (uniqueMethods) {
MethodInfo prev = uniqueMethods.get(newMth);
if (prev != null) {
return prev;
}
uniqueMethods.put(newMth, newMth);
return newMth;
}
}
@@ -1,59 +1,57 @@
package jadx.core.dex.info;
import java.util.List;
import java.util.Objects;
import com.android.dex.MethodId;
import com.android.dex.ProtoId;
import org.jetbrains.annotations.Nullable;
import jadx.api.plugins.input.data.IMethodRef;
import jadx.core.codegen.TypeGen;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.Utils;
public final class MethodInfo {
public final class MethodInfo implements Comparable<MethodInfo> {
private final String name;
private final ArgType retType;
private final List<ArgType> args;
private final List<ArgType> argTypes;
private final ClassInfo declClass;
private final String shortId;
private String alias;
private boolean aliasFromPreset;
private MethodInfo(DexNode dex, int mthIndex) {
MethodId mthId = dex.getMethodId(mthIndex);
name = dex.getString(mthId.getNameIndex());
alias = name;
aliasFromPreset = false;
declClass = ClassInfo.fromDex(dex, mthId.getDeclaringClassIndex());
ProtoId proto = dex.getProtoId(mthId.getProtoIndex());
retType = dex.getType(proto.getReturnTypeIndex());
args = dex.readParamList(proto.getParametersOffset());
shortId = makeSignature(true);
}
private MethodInfo(ClassInfo declClass, String name, List<ArgType> args, ArgType retType) {
this.name = name;
this.alias = name;
this.aliasFromPreset = false;
this.declClass = declClass;
this.args = args;
this.argTypes = args;
this.retType = retType;
this.shortId = makeSignature(true);
this.shortId = makeShortId(name, argTypes, retType);
}
public static MethodInfo externalMth(ClassInfo declClass, String name, List<ArgType> args, ArgType retType) {
return new MethodInfo(declClass, name, args, retType);
}
public static MethodInfo fromDex(DexNode dex, int mthIndex) {
MethodInfo mth = dex.root().getInfoStorage().getMethod(dex, mthIndex);
if (mth != null) {
return mth;
public static MethodInfo fromRef(RootNode root, IMethodRef methodRef) {
InfoStorage infoStorage = root.getInfoStorage();
int uniqId = methodRef.getUniqId();
MethodInfo prevMth = infoStorage.getByUniqId(uniqId);
if (prevMth != null) {
return prevMth;
}
mth = new MethodInfo(dex, mthIndex);
return dex.root().getInfoStorage().putMethod(dex, mthIndex, mth);
methodRef.load();
ArgType parentClsType = ArgType.parse(methodRef.getParentClassType());
ClassInfo parentClass = ClassInfo.fromType(root, parentClsType);
ArgType returnType = ArgType.parse(methodRef.getReturnType());
List<ArgType> args = Utils.collectionMap(methodRef.getArgTypes(), ArgType::parse);
MethodInfo newMth = new MethodInfo(parentClass, methodRef.getName(), args, returnType);
MethodInfo uniqMth = infoStorage.putMethod(newMth);
infoStorage.putByUniqId(uniqId, uniqMth);
return uniqMth;
}
public static MethodInfo fromDetails(RootNode root, ClassInfo declClass, String name, List<ArgType> args, ArgType retType) {
MethodInfo newMth = new MethodInfo(declClass, name, args, retType);
return root.getInfoStorage().putMethod(newMth);
}
public String makeSignature(boolean includeRetType) {
@@ -61,17 +59,29 @@ public final class MethodInfo {
}
public String makeSignature(boolean useAlias, boolean includeRetType) {
StringBuilder signature = new StringBuilder();
signature.append(useAlias ? alias : name);
signature.append('(');
for (ArgType arg : args) {
signature.append(TypeGen.signature(arg));
return makeShortId(useAlias ? alias : name,
argTypes,
includeRetType ? retType : null);
}
public static String makeShortId(String name, List<ArgType> argTypes, @Nullable ArgType retType) {
StringBuilder sb = new StringBuilder();
sb.append(name);
sb.append('(');
for (ArgType arg : argTypes) {
sb.append(TypeGen.signature(arg));
}
signature.append(')');
if (includeRetType) {
signature.append(TypeGen.signature(retType));
sb.append(')');
if (retType != null) {
sb.append(TypeGen.signature(retType));
}
return signature.toString();
return sb.toString();
}
public boolean isOverloadedBy(MethodInfo otherMthInfo) {
return argTypes.size() == otherMthInfo.argTypes.size()
&& name.equals(otherMthInfo.name)
&& !Objects.equals(this.shortId, otherMthInfo.shortId);
}
public String getName() {
@@ -106,11 +116,11 @@ public final class MethodInfo {
}
public List<ArgType> getArgumentsTypes() {
return args;
return argTypes;
}
public int getArgsCount() {
return args.size();
return argTypes.size();
}
public boolean isConstructor() {
@@ -143,10 +153,7 @@ public final class MethodInfo {
@Override
public int hashCode() {
int result = declClass.hashCode();
result = 31 * result + retType.hashCode();
result = 31 * result + shortId.hashCode();
return result;
return shortId.hashCode() + 31 * declClass.hashCode();
}
@Override
@@ -159,13 +166,21 @@ public final class MethodInfo {
}
MethodInfo other = (MethodInfo) obj;
return shortId.equals(other.shortId)
&& retType.equals(other.retType)
&& declClass.equals(other.declClass);
}
@Override
public int compareTo(MethodInfo other) {
int clsCmp = declClass.compareTo(other.declClass);
if (clsCmp != 0) {
return clsCmp;
}
return shortId.compareTo(other.shortId);
}
@Override
public String toString() {
return declClass.getFullName() + '.' + name
+ '(' + Utils.listToString(args) + "):" + retType;
+ '(' + Utils.listToString(argTypes) + "):" + retType;
}
}
@@ -1,7 +1,8 @@
package jadx.core.dex.instructions;
import com.android.dx.io.instructions.DecodedInstruction;
import org.jetbrains.annotations.Nullable;
import jadx.api.plugins.input.insns.InsnData;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
@@ -9,41 +10,54 @@ import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.exceptions.JadxRuntimeException;
public class ArithNode extends InsnNode {
private final ArithOp op;
public ArithNode(DecodedInstruction insn, ArithOp op, ArgType type, boolean literal) {
super(InsnType.ARITH, 2);
this.op = op;
setResult(InsnArg.reg(insn, 0, type));
int rc = insn.getRegisterCount();
if (literal) {
if (rc == 1) {
// self
addReg(insn, 0, type);
addLit(insn, type);
} else if (rc == 2) {
// normal
addReg(insn, 1, type);
addLit(insn, type);
}
} else {
if (rc == 2) {
// self
addReg(insn, 0, type);
addReg(insn, 1, type);
} else if (rc == 3) {
// normal
addReg(insn, 1, type);
addReg(insn, 2, type);
}
public static ArithNode build(InsnData insn, ArithOp op, ArgType type) {
RegisterArg resArg = InsnArg.reg(insn, 0, fixResultType(op, type));
ArgType argType = fixArgType(op, type);
switch (insn.getRegsCount()) {
case 2:
return new ArithNode(op, resArg, InsnArg.reg(insn, 0, argType), InsnArg.reg(insn, 1, argType));
case 3:
return new ArithNode(op, resArg, InsnArg.reg(insn, 1, argType), InsnArg.reg(insn, 2, argType));
default:
throw new JadxRuntimeException("Unexpected registers count in " + insn);
}
}
public ArithNode(ArithOp op, RegisterArg res, InsnArg a, InsnArg b) {
public static ArithNode buildLit(InsnData insn, ArithOp op, ArgType type) {
RegisterArg resArg = InsnArg.reg(insn, 0, fixResultType(op, type));
ArgType argType = fixArgType(op, type);
LiteralArg litArg = InsnArg.lit(insn, argType);
switch (insn.getRegsCount()) {
case 1:
return new ArithNode(op, resArg, InsnArg.reg(insn, 0, argType), litArg);
case 2:
return new ArithNode(op, resArg, InsnArg.reg(insn, 1, argType), litArg);
default:
throw new JadxRuntimeException("Unexpected registers count in " + insn);
}
}
private static ArgType fixResultType(ArithOp op, ArgType type) {
if (type == ArgType.INT && op.isBitOp()) {
return ArgType.INT_BOOLEAN;
}
return type;
}
private static ArgType fixArgType(ArithOp op, ArgType type) {
if (type == ArgType.INT && op.isBitOp()) {
return ArgType.NARROW_NUMBERS_NO_FLOAT;
}
return type;
}
private final ArithOp op;
public ArithNode(ArithOp op, @Nullable RegisterArg res, InsnArg a, InsnArg b) {
super(InsnType.ARITH, 2);
this.op = op;
setResult(res);
@@ -97,10 +111,7 @@ public class ArithNode extends InsnNode {
@Override
public InsnNode copy() {
ArithNode copy = new ArithNode(op,
getResult().duplicate(),
getArg(0).duplicate(),
getArg(1).duplicate());
ArithNode copy = new ArithNode(op, null, getArg(0).duplicate(), getArg(1).duplicate());
return copyCommonParams(copy);
}
@@ -24,4 +24,15 @@ public enum ArithOp {
public String getSymbol() {
return this.symbol;
}
public boolean isBitOp() {
switch (this) {
case AND:
case OR:
case XOR:
return true;
default:
return false;
}
}
}
@@ -0,0 +1,25 @@
package jadx.core.dex.instructions;
import org.jetbrains.annotations.Nullable;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.nodes.InsnNode;
public abstract class BaseInvokeNode extends InsnNode {
public BaseInvokeNode(InsnType type, int argsCount) {
super(type, argsCount);
}
public abstract MethodInfo getCallMth();
@Nullable
public abstract InsnArg getInstanceArg();
public abstract boolean isStaticCall();
/**
* Return offset to match method args from {@link #getCallMth()}
*/
public abstract int getFirstArgOffset();
}
@@ -1,19 +0,0 @@
package jadx.core.dex.instructions;
import org.jetbrains.annotations.Nullable;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.RegisterArg;
public interface CallMthInterface {
MethodInfo getCallMth();
@Nullable
RegisterArg getInstanceArg();
/**
* Return offset to match method args from {@link #getCallMth()}
*/
int getFirstArgOffset();
}

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