Compare commits

...

185 Commits

Author SHA1 Message Date
Skylot fcb120a3ed core: suppress type error exception 2014-11-09 15:34:19 +03:00
Skylot 988628a2e7 core: fix variable declaration used in several loops 2014-11-09 14:55:33 +03:00
Skylot c24cdf5cc1 core: fix constructor instruction replacement 2014-11-08 20:38:22 +03:00
Skylot d748e004d2 core: fix missing parenthesis in conditions 2014-11-08 20:38:22 +03:00
Skylot 380b73d1b9 core: sort methods by source line number 2014-11-08 20:38:17 +03:00
Skylot ef85e29a9b core: improve 'break' and 'continue' insertion 2014-11-07 23:03:32 +03:00
Skylot 1daf5d1090 core: fix condition processing (resolve #25) 2014-11-07 21:39:27 +03:00
Skylot 9d2c0e4aea core: fix type inference and const inline for arrays 2014-11-07 20:07:18 +03:00
Skylot 7277ebb9c4 core: expand arrays for vararg arguments 2014-11-04 15:42:48 +03:00
Skylot c18074f6aa core: insert 'continue' instruction 2014-11-03 22:31:21 +03:00
Skylot 8a706193e7 core: fix indexed loop checks 2014-11-03 22:04:42 +03:00
Skylot 9d77f5f5df update dx library and dependencies 2014-11-03 15:22:49 +03:00
Skylot 6951d0e646 core: use NotNull and Nullable annotations 2014-11-03 15:13:52 +03:00
Skylot 73dd55eac2 core: fix code style issues 2014-11-03 15:11:48 +03:00
Skylot b5a9389cc6 core: fix variables inline in 'catch' block 2014-11-03 14:53:27 +03:00
Skylot d905c96fbe core: refactor 'catch' clause variable processing 2014-11-02 19:06:41 +03:00
Skylot 03f03f85af core: replace removed synthetic constructor 2014-11-01 15:46:57 +03:00
Skylot 2b00a8a406 core: disable parenthesis remove (break code in most cases) 2014-10-25 22:36:21 +04:00
Skylot f31c2dcd21 core: fix processing 'if' at loop end 2014-10-24 23:12:42 +04:00
Skylot 7699cfac02 tests: fix build on Windows 2014-10-23 21:30:46 +04:00
Skylot 5c48a457b4 fix code style issues 2014-10-19 19:07:15 +04:00
Skylot b5f439e1aa tests: reformat code 2014-10-19 18:01:32 +04:00
Skylot 202fe5a0a9 core: fix links for fields in nested classes 2014-10-18 23:08:15 +04:00
Skylot 68ccf57bd4 core: fix type detection for method arguments 2014-10-18 23:08:10 +04:00
Skylot 84970759d8 core: fix switch over enum with several enums in class 2014-10-14 22:38:29 +04:00
Skylot 53cac58ebe core: fix processing of last instruction in 'try' block 2014-10-11 22:44:26 +04:00
Skylot adc32ed319 fix minor issues 2014-10-11 22:44:26 +04:00
Skylot 7f0815a7b2 core tests: add option for compile test without debug info 2014-10-11 22:21:40 +04:00
Skylot 68f5565b63 core: improve processing of 'try/catch' and 'break' in loops 2014-10-11 22:21:30 +04:00
Skylot c552fb857d core tests: replace several classes in dynamic class loader, add additional checks 2014-10-07 22:19:54 +04:00
Skylot 8a4ec47b92 core: support break with label for simple cases 2014-09-29 23:44:36 +04:00
Skylot d281126337 core: fix processing try/catch in loop 2014-09-27 20:09:25 +04:00
Skylot 4fb6ada5ec core: fix type inference for phi nodes 2014-09-26 22:19:23 +04:00
Skylot ab924faa1e core: don't remove empty catch blocks 2014-09-22 22:48:25 +04:00
Skylot b12b129af7 travis: add jdk8 to build matrix 2014-09-22 22:34:44 +04:00
Skylot 017c6b4d42 core tests: compile decompiled code 2014-09-22 22:25:42 +04:00
Skylot d55cd5fbb4 core tests: organize directories 2014-09-22 22:02:42 +04:00
Skylot 13a6b1c8c6 core: add 'show inconsistent code' parameter 2014-09-20 13:43:55 +04:00
Skylot 0bc37e5d32 gui: fix jump manager 2014-09-20 13:01:20 +04:00
Skylot 46c8572887 core: restore switch over enum 2014-09-18 20:59:39 +04:00
Skylot e6b919007c gui: add new version notification 2014-09-16 22:03:18 +04:00
Skylot ac5a6096bb core: fix constructor call for moved arg (fix #20) 2014-09-13 18:55:17 +04:00
Skylot db527fbbda core: add api for write tests using smali 2014-09-13 18:55:17 +04:00
skylot 8f201f1fee Merge pull request #19 from NeoSpb/fix3
core: fix processing of debug info (markup of local variables)
2014-09-13 14:19:19 +04:00
Anton Dyachenko d1e0762c12 core: fix processing of debug info (markup of local variables) 2014-09-12 19:24:44 +04:00
Skylot 010ae99c69 core: restore simple for-each loop over iterable object 2014-09-07 16:49:02 +04:00
Skylot a4632d6e86 core: fix high memory usage while process conditions 2014-09-04 22:35:47 +04:00
Skylot 2a3162f869 core: don't set 'skip' flag for failed nested 'if' merge (issue #18) 2014-09-02 22:46:12 +04:00
skylot 2063fd0742 Merge pull request #17 from NeoSpb/fix2
Fix2 by NeoSpb
2014-09-02 20:47:14 +04:00
Anton Dyachenko 128fe8a839 core: fix resolving the instance field in the 2nd and more nested inner class 2014-09-02 20:05:15 +04:00
Anton Dyachenko 2478fc3a1b core: fix instance initializer producing (don't generate super() call) 2014-09-02 19:55:26 +04:00
Skylot 5a68d3bef7 core: restore for-each loop over array 2014-09-01 23:09:04 +04:00
Skylot 195eeceb62 core: restore simple indexed loops 2014-08-30 23:15:51 +04:00
Skylot ec8309af49 core: fix processing 'if' at loop end 2014-08-20 22:02:00 +04:00
skylot 627a4dc802 add contribution section to readme 2014-08-19 23:25:25 +04:00
Skylot e2018535ef core: add ternary conditions processing 2014-08-19 22:27:51 +04:00
Skylot ee56610f06 core: allow method name be same as class name (issue #15) 2014-08-18 20:45:50 +04:00
skylot fb9ff7748a Merge pull request #14 from NeoSpb/gui_preferences
gui: add saving preferences (open/save paths, flatten packages)
2014-08-17 13:22:08 +04:00
Anton Dyachenko cdfb46d9d3 gui: add saving preferences (open/save paths, flatten packages) 2014-08-17 12:01:46 +04:00
Skylot 5545a94a9e core: process nested ternary operators 2014-08-16 17:39:30 +04:00
Skylot 9e811d959b core: add method for print line numbers 2014-08-16 17:16:56 +04:00
Skylot 957d5394d2 refactor: add static methods for create DotGraphVisitor 2014-08-16 17:06:50 +04:00
Skylot 95afe1219e core: don't cache dex strings (old workaround for bug in dx) 2014-08-16 15:07:06 +04:00
Skylot 07937f1d71 bump version to 0.5.3 2014-08-16 15:06:55 +04:00
Skylot 671be0af0a add jadx-gui screenshot 2014-08-15 23:08:18 +04:00
Skylot 7e9278f992 don't hardcode maximum Java heap size 2014-08-15 22:39:34 +04:00
Skylot 9194441c47 add ASM to NOTICE file 2014-08-15 22:38:50 +04:00
Skylot 4f307c0085 core: allow subblock replace for 'if' region 2014-08-14 22:38:29 +04:00
Skylot 3bdda55102 core: inline filled array creation 2014-08-14 22:23:13 +04:00
Skylot b657b0fb1f core: fix 'if' processing in 'do/while' loop 2014-08-12 23:00:29 +04:00
Skylot 4935ae6da5 core: hide value parser constants 2014-08-12 22:58:20 +04:00
Skylot 72a50eae43 core: fix missing blocks in loop region 2014-08-11 22:29:10 +04:00
Skylot fa37b90cff core: fix processing try/catch in other catch 2014-08-10 22:36:42 +04:00
Skylot 052a8db606 core: resolve minor issues 2014-08-09 19:32:13 +04:00
Skylot 88ccba166e core: don't inline variables defined in 'try' and used in 'catch' 2014-08-08 22:10:10 +04:00
Skylot 58998089a6 core: redone 'if' structure checking 2014-08-07 22:20:47 +04:00
Skylot f0a73b329e core: fix processing conditions in loop 2014-08-06 22:28:29 +04:00
Skylot c97678a477 refactor: make ErrorsCounter non static 2014-08-05 22:48:31 +04:00
Skylot 2ad739275f core: handle special values for numbers 2014-08-04 22:07:10 +04:00
Skylot caad78885d core: check for duplicated code generation 2014-08-02 16:39:14 +04:00
Skylot a234227b9f core: fix errors in try/catch processing (issue #13) 2014-08-02 16:33:52 +04:00
Skylot 16f736e773 core: fix missing 'catch' code 2014-07-30 23:05:39 +04:00
Skylot 1fe24ad11d travis: cache dependencies 2014-07-29 23:41:10 +04:00
Skylot 33c5e0827a core: always check arguments before inline 2014-07-29 22:59:53 +04:00
Skylot cbd36aeb8f core: fix unused variables declaration 2014-07-29 22:34:18 +04:00
Skylot 2963bb3f41 core: fix issues reported by coverity 2014-07-28 23:19:48 +04:00
Skylot 09a6ceac63 gui: replace underline to color highlight (experimental) 2014-07-28 22:50:55 +04:00
Skylot 75d8a01cab core: improve error reporting 2014-07-28 22:50:55 +04:00
Skylot 0968f75e9a core: fix condition in loops (issue #9) 2014-07-28 22:50:42 +04:00
Skylot bc0db88afa update gradle and dependencies versions 2014-07-18 23:29:36 +04:00
Skylot 5f11f12d0c core: remove redundant spaces for enums 2014-07-18 21:21:24 +04:00
Skylot 2d18950542 core: add some integration tests 2014-07-17 23:32:18 +04:00
Skylot 50d314445a core: fix code style 2014-07-17 23:31:07 +04:00
Skylot f8d57d9265 core: decompile '.class' files 2014-07-15 23:45:25 +04:00
Skylot ebbe6db378 core: fix complex 'if' processing (issues #9 and #12) 2014-07-12 21:26:14 +04:00
skylot 543cad3a23 Merge pull request #11 from Fruiter/master
core: fix nested try-catch blocks processing
2014-07-07 21:13:23 +04:00
fruiter 41cc83dbf6 core: fix nested try-catch blocks processing 2014-07-06 20:15:20 -04:00
Skylot ce7101be88 core: always inline 'this' (issue #10) 2014-06-28 15:39:35 +04:00
Skylot 0a241e3a9c core tests: add custom string matchers 2014-06-28 15:38:50 +04:00
Skylot 37857e88ea core: fix switch statement processing (issue #9 case 2) 2014-06-24 14:08:20 +04:00
Skylot 6fbcf46a8b core: refactor return remover visitor 2014-06-24 14:08:20 +04:00
Skylot a36bc8f29a core: add serial uid to JadxRuntimeException 2014-06-24 14:08:20 +04:00
Skylot 813b7bca6e core: sort error nodes in execution report 2014-06-23 23:37:39 +04:00
Skylot e2945f2a42 core: limit region traversal iterations count 2014-06-23 23:37:39 +04:00
Skylot eaf623a560 gui: fix sync with editor 2014-06-20 20:20:35 +04:00
Skylot 26aa504590 core: guard endless regions processing 2014-06-20 17:08:07 +04:00
Skylot e4dde3f4b6 core: fix class cast exception 2014-06-15 21:35:34 +04:00
Skylot 9c90699c40 core: fix parsing of generic signature with inner classes 2014-06-15 17:42:48 +04:00
Skylot b67cd50e8a gui: add definitions search window 2014-06-14 19:38:24 +04:00
Skylot d2acaa03f5 core: guess variable name from assign instruction 2014-06-06 21:22:20 +04:00
Skylot f2aa4cd10b core: make better variables naming 2014-06-05 19:40:57 +04:00
Skylot b940b99e75 core: fix issues in variable names and try/catch blocks 2014-06-03 23:08:40 +04:00
Skylot 868e0706ea core: fix source line number parsing and saving 2014-05-31 16:58:03 +04:00
Skylot 324f544ba2 gui: show source line numbers 2014-05-31 16:17:40 +04:00
Skylot 0a1981f90e gui: add hyperlinks for classes and fields 2014-05-25 23:14:29 +04:00
Skylot 0a36bfb088 core: fix try-catch blocks processing 2014-05-24 19:55:29 +04:00
Skylot 0d94af099b core: improve 'if' detection with 'return' instruction 2014-05-18 00:28:43 +04:00
Skylot 4a6115ed64 core: refactor attribute storage 2014-05-14 21:12:39 +04:00
Skylot 42eb319751 fix issues reported by Coverity 2014-05-12 22:08:33 +04:00
Skylot 343bddc6ad core: fix 'break' detection in loop 2014-05-12 22:08:33 +04:00
Skylot 632a742ea9 core: fix method inline 2014-05-12 22:08:33 +04:00
Skylot 08c9d1228a core: inline 'cmp' instruction 2014-05-12 22:08:33 +04:00
Skylot 11d8b28fb4 core: fix variable rename 2014-05-12 22:08:33 +04:00
Skylot 12b6371209 core: fix basic block initial id 2014-05-12 22:08:33 +04:00
Skylot 24d22aaafb core: fix 'if' detection 2014-05-12 22:08:33 +04:00
Skylot ebf7822628 use spock framework for unit tests 2014-05-12 22:08:33 +04:00
Skylot 7669fa1582 update dependencies, add coveralls badge 2014-05-12 22:08:33 +04:00
Skylot e49ba61917 core: use SSA representation for instruction arguments 2014-05-12 22:08:32 +04:00
Skylot 96db1c2479 core: reformat code 2014-05-12 21:59:37 +04:00
skylot 7abdb41a9e Merge pull request #7 from bkerler/master
Add support for dx 1.8 library
2014-05-12 21:28:55 +04:00
Bjoern Kerler 14f6d2f3b0 Add support for dx 1.8 library 2014-05-12 09:12:34 +02:00
Skylot 4e4b4975ad core: fix method redecompilation (issue #6) 2014-04-24 23:44:30 +04:00
skylot 93fafcf886 Merge pull request #5 from dnet/master
removed unused private method getCodePanel(int)
2014-04-02 22:03:57 +04:00
András Veres-Szentkirályi 82cc88a1b9 removed unused private method getCodePanel(int) 2014-04-02 17:10:34 +02:00
Skylot 5c94e0bccc core: improve exceptions handling 2014-03-27 23:24:20 +04:00
Skylot 18a1788d2d gui: fix class members expand 2014-03-25 23:04:00 +04:00
Skylot d0aa19118b core: fix comodification exception 2014-03-25 21:21:30 +04:00
Skylot 039f6eebda core: rename depth traversal class 2014-03-25 21:20:47 +04:00
Skylot 8a464e8274 core: fix condition processing errors 2014-03-23 23:00:25 +04:00
Skylot 066b5a895d core: refactor 'if' regions processing 2014-03-22 17:19:01 +04:00
Skylot 4c4af7928e core: fix dot graph dump 2014-03-19 21:42:10 +04:00
Skylot a0d8d9fcc6 core: fix 'break' detection in loops 2014-03-17 23:39:33 +04:00
Skylot a2142b2ff8 gui: add tabbed pane menu 2014-03-16 16:47:38 +04:00
Skylot 5ed5ec5f7d gui: refactor UI classes 2014-03-16 13:16:19 +04:00
Skylot 95795620d5 core: fix indent for 'break' in 'case' block, refactor tests 2014-03-11 23:56:28 +04:00
Skylot 890c0a9909 refactor: remove deprecated methods 2014-03-10 23:00:14 +04:00
Skylot b73cb40690 core: refactor DotGraphVisitor 2014-03-10 16:19:24 +04:00
Skylot ca448fc4d8 core: fix variable definitions for 'try' blocks 2014-03-10 15:28:12 +04:00
Skylot 7a51c0d087 core: fix signature processing for local variables 2014-03-10 15:06:03 +04:00
Skylot 8762125bbf remove logback from jadx-core dependencies 2014-03-10 12:20:26 +04:00
Skylot 3d0c6e49ab core: fix inline in 'move' instruction 2014-03-10 11:36:20 +04:00
Skylot 03da35b29e core: fix wildcard signature processing 2014-03-09 23:06:17 +04:00
Skylot 3ccab60f43 core: remove redundant parenthesis for arithmetic operations 2014-03-09 19:13:49 +04:00
Skylot ed64b8c121 gui: add hyperlinks for jump to definitions 2014-03-09 17:48:04 +04:00
Skylot 2a60ac47fe core: annotate generated code with reference to used methods 2014-03-09 17:14:58 +04:00
Skylot 9cd72fe1e9 update dependencies version 2014-03-09 17:02:19 +04:00
Skylot 476b2c3735 core: fix inner class handling in classpath and signature parser 2014-03-04 23:37:42 +04:00
Skylot 5258c8363a core: fix NPE in loops processing 2014-03-04 23:32:52 +04:00
Skylot eb6d145dca core: fix indent for anonymous classes 2014-03-03 22:36:38 +04:00
Skylot 63c003a02d core: fix generic types for local variables 2014-03-02 23:34:56 +04:00
Skylot 5557fd814b fix some code style issues 2014-03-02 23:34:40 +04:00
Skylot b1dc26ee06 core: fix missing imports for anonymous classes 2014-03-02 22:30:26 +04:00
Skylot 56c0a588de core: fix imports for inner classes with same names 2014-03-02 16:30:11 +04:00
Skylot 47d65fcd87 core: improve signature parser 2014-03-01 22:38:18 +04:00
Skylot 85ab095630 core: fix class imports 2014-02-26 23:01:00 +04:00
Skylot 1b5f0f6af6 core: move tests 2014-02-26 22:58:22 +04:00
Skylot 2cf28eb2e7 core: fix loop detection 2014-02-25 23:53:30 +04:00
Skylot 2b300341a0 core: improve error reporting for inconsistent code 2014-02-25 23:52:05 +04:00
Skylot 01fabca358 core: fix 'this' reference in anonymous classes 2014-02-25 22:30:27 +04:00
Skylot 4ace552a27 core: fix duplicate cast 2014-02-23 01:19:46 +04:00
Skylot b61daaed33 core: fix synchronized block processing 2014-02-22 23:22:59 +04:00
Skylot c6f0c89cf6 core: fix indent for anonymous class 2014-02-22 18:54:51 +04:00
Skylot 3c84975a09 fix code style issues 2014-02-22 18:54:51 +04:00
Skylot bb4ef4f0a2 core: simplify conditions 2014-02-22 18:54:51 +04:00
Skylot fd00330e6e update gradle to 1.11 2014-02-22 18:54:41 +04:00
Skylot d10efec1ab core: fix type for one time used args 2014-02-13 23:08:24 +04:00
Skylot 3f08c99f19 core: use ternary operator 2014-01-03 23:48:40 +04:00
Skylot e3606d1b53 reformat code (force braces) 2014-01-03 23:30:30 +04:00
Skylot ab593e3cd9 refactor some classes 2014-01-03 22:54:31 +04:00
Skylot 4a0aacf104 gui: fix inner classes opening 2013-12-28 19:35:03 +04:00
Skylot 917cf20d37 core: fix decompiled lines info 2013-12-28 19:33:52 +04:00
Skylot dabaeed8df core: add return type to method short id 2013-12-28 15:58:25 +04:00
Skylot 4923b36e70 core: move instruction remover class to utils 2013-12-28 15:53:25 +04:00
Skylot ebf06fde65 gui: remove not generated elements from class node tree 2013-12-27 23:30:20 +04:00
Skylot 438b3b50d9 gui: fix missed nodes in hierarchical packages tree 2013-12-26 23:33:38 +04:00
425 changed files with 21833 additions and 6224 deletions
+11 -1
View File
@@ -1,12 +1,22 @@
language: java
jdk:
- oraclejdk8
- oraclejdk7
- openjdk7
- openjdk6
before_install:
- chmod +x gradlew
script:
- TERM=dumb ./gradlew clean build dist
after_success:
- TERM=dumb ./gradlew jacocoTestReport coveralls
cache:
directories:
- $HOME/.gradle
notifications:
email:
- skylot@gmail.com
+35
View File
@@ -105,6 +105,41 @@ as published by the Free Software Foundation.
*******************************************************************************
ASM library:
*******************************************************************************
Copyright (c) 2000-2011 INRIA, France Telecom
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holders nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
THE POSSIBILITY OF SUCH DAMAGE.
*******************************************************************************
Jadx-gui components
===================
+37 -10
View File
@@ -1,24 +1,27 @@
## JADX
## JADX
[![Build Status](https://travis-ci.org/skylot/jadx.png?branch=master)](https://travis-ci.org/skylot/jadx)
[![Build Status](https://drone.io/github.com/skylot/jadx/status.png)](https://drone.io/github.com/skylot/jadx/latest)
[![Coverage Status](https://coveralls.io/repos/skylot/jadx/badge.png)](https://coveralls.io/r/skylot/jadx)
[![Coverity Scan Build Status](https://scan.coverity.com/projects/2166/badge.svg)](https://scan.coverity.com/projects/2166)
**jadx** - Dex to Java decompiler
Command line and GUI tools for produce Java source code from Android Dex and Apk files
Note: jadx-gui now in experimental stage
![jadx-gui screenshot](http://skylot.github.io/jadx/jadx-gui.png)
### Downloads
- [unstable](https://drone.io/github.com/skylot/jadx/files)
[![Build Status](https://drone.io/github.com/skylot/jadx/status.png)](https://drone.io/github.com/skylot/jadx/latest)
[![Build Status](https://travis-ci.org/skylot/jadx.png?branch=master)](https://travis-ci.org/skylot/jadx)
- from [github](https://github.com/skylot/jadx/releases)
- from [sourceforge](http://sourceforge.net/projects/jadx/files/)
- from [sourceforge](http://sourceforge.net/projects/jadx/files/)
### Building from source
### Building from source
git clone https://github.com/skylot/jadx.git
cd jadx
./gradlew dist
(on Windows, use `gradlew.bat` instead of `./gradlew`)
Scripts for run jadx will be placed in `build/jadx/bin`
@@ -36,7 +39,7 @@ Run **jadx** on itself:
### Usage
```
jadx[-gui] [options] <input file> (.dex, .apk or .jar)
jadx[-gui] [options] <input file> (.dex, .apk, .jar or .class)
options:
-d, --output-dir - output directory
-j, --threads-count - processing threads count
@@ -49,6 +52,30 @@ Example:
jadx -d out classes.dex
```
### Troubleshooting
##### Out of memory error:
- Reduce processing threads count (`-j` option)
- Increase maximum java heap size:
* command line (example for linux):
`JAVA_OPTS="-Xmx4G" jadx -j 1 some.apk`
* edit 'jadx' script (jadx.bat on Windows) and setup bigger heap size:
`DEFAULT_JVM_OPTS="-Xmx2500M"`
### Contribution
To support this project you can:
- Post thoughts about new features/optimizations that important to you
- Submit bug using one of following patterns:
* Java code examples which decompiles incorrectly
* Error log and link to _public available_ apk file or app page on Google play
And any other comments will be very helpfull,
because at current stage of development it is very time consuming
to **find** new bugs, design and implement new features.
Also I need to **prioritize** these task for complete most important at first.
---------------------------------------
*Licensed under the Apache 2.0 License*
*Copyright 2013 by Skylot*
*Copyright 2014 by Skylot*
+37 -14
View File
@@ -1,40 +1,63 @@
ext.jadxVersion = file('version').readLines().get(0)
version = jadxVersion
apply plugin: 'sonar-runner'
subprojects {
apply plugin: 'java'
apply plugin: 'idea'
apply plugin: 'eclipse'
sourceCompatibility = 1.6
targetCompatibility = 1.6
apply plugin: 'groovy'
apply plugin: 'jacoco'
apply plugin: 'coveralls'
version = jadxVersion
gradle.projectsEvaluated {
tasks.withType(Compile) {
if (!"${it}".contains(":jadx-samples:")) {
options.compilerArgs << "-Xlint" << "-Xlint:unchecked" << "-Xlint:deprecation"
}
tasks.withType(JavaCompile) {
sourceCompatibility = JavaVersion.VERSION_1_6
targetCompatibility = JavaVersion.VERSION_1_6
if (!"$it".contains(':jadx-samples:')) {
options.compilerArgs << '-Xlint' << '-Xlint:unchecked' << '-Xlint:deprecation'
}
}
jar {
version = jadxVersion
manifest {
mainAttributes('jadx-version' : jadxVersion)
mainAttributes('jadx-version': jadxVersion)
}
}
dependencies {
compile 'org.slf4j:slf4j-api:1.7.5'
compile 'org.slf4j:slf4j-api:1.7.7'
testCompile 'ch.qos.logback:logback-classic:1.1.2'
testCompile 'junit:junit:4.11'
testCompile "org.mockito:mockito-core:1.9.5"
testCompile 'org.mockito:mockito-core:1.10.10'
testCompile 'org.spockframework:spock-core:0.7-groovy-2.0'
testCompile 'cglib:cglib-nodep:3.1'
}
repositories {
mavenCentral()
jcenter()
}
jacocoTestReport {
reports {
xml.enabled = true // coveralls plugin depends on xml format report
html.enabled = true
}
}
}
buildscript {
repositories {
mavenCentral()
}
dependencies {
// setup coveralls (http://coveralls.io/) see http://github.com/kt3k/coveralls-gradle-plugin
classpath 'org.kt3k.gradle.plugin:coveralls-gradle-plugin:0.6.1'
}
}
@@ -66,5 +89,5 @@ task clean(type: Delete) {
}
task wrapper(type: Wrapper) {
gradleVersion = '1.9'
gradleVersion = '2.0'
}
Binary file not shown.
+1 -1
View File
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=http\://services.gradle.org/distributions/gradle-1.9-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-2.0-bin.zip
+2 -11
View File
@@ -5,17 +5,8 @@ applicationName = 'jadx'
dependencies {
compile(project(':jadx-core'))
compile 'com.beust:jcommander:1.30'
}
startScripts {
doLast {
// increase default max heap size
String var = 'DEFAULT_JVM_OPTS='
String args = '-Xmx1300M'
unixScript.text = unixScript.text.replace(var + '""', var + '"' + args + '"')
windowsScript.text = windowsScript.text.replace(var, var + args)
}
compile 'com.beust:jcommander:1.35'
compile 'ch.qos.logback:logback-classic:1.1.2'
}
applicationDistribution.with {
+23 -23
View File
@@ -1,7 +1,6 @@
package jadx.cli;
import jadx.api.Decompiler;
import jadx.core.utils.ErrorsCounter;
import jadx.api.JadxDecompiler;
import jadx.core.utils.exceptions.JadxException;
import java.io.File;
@@ -12,39 +11,39 @@ import org.slf4j.LoggerFactory;
public class JadxCLI {
private static final Logger LOG = LoggerFactory.getLogger(JadxCLI.class);
public static void main(String[] args) {
public static void main(String[] args) throws JadxException {
try {
JadxCLIArgs jadxArgs = new JadxCLIArgs(args);
checkArgs(jadxArgs);
processAndSave(jadxArgs);
} catch (Exception e) {
LOG.error(e.getMessage());
JadxCLIArgs jadxArgs = new JadxCLIArgs();
if (processArgs(jadxArgs, args)) {
processAndSave(jadxArgs);
}
} catch (Throwable e) {
LOG.error("jadx error: " + e.getMessage(), e);
System.exit(1);
}
}
private static void processAndSave(JadxCLIArgs jadxArgs) {
try {
Decompiler jadx = new Decompiler(jadxArgs);
jadx.loadFiles(jadxArgs.getInput());
jadx.setOutputDir(jadxArgs.getOutDir());
jadx.save();
static void processAndSave(JadxCLIArgs jadxArgs) throws JadxException {
JadxDecompiler jadx = new JadxDecompiler(jadxArgs);
jadx.setOutputDir(jadxArgs.getOutDir());
jadx.loadFiles(jadxArgs.getInput());
jadx.save();
if (jadx.getErrorsCount() != 0) {
jadx.printErrorsReport();
LOG.error("finished with errors");
} else {
LOG.info("done");
} catch (Throwable e) {
LOG.error("jadx error:", e);
}
int errorsCount = ErrorsCounter.getErrorCount();
if (errorsCount != 0) {
ErrorsCounter.printReport();
}
System.exit(errorsCount);
}
private static void checkArgs(JadxCLIArgs jadxArgs) throws JadxException {
static boolean processArgs(JadxCLIArgs jadxArgs, String[] args) throws JadxException {
if (!jadxArgs.processArgs(args)) {
return false;
}
if (jadxArgs.getInput().isEmpty()) {
LOG.error("Please specify input file");
jadxArgs.printUsage();
System.exit(1);
return false;
}
File outputDir = jadxArgs.getOutDir();
if (outputDir == null) {
@@ -64,5 +63,6 @@ public class JadxCLI {
if (outputDir.exists() && !outputDir.isDirectory()) {
throw new JadxException("Output directory exists as file " + outputDir);
}
return true;
}
}
@@ -1,7 +1,7 @@
package jadx.cli;
import jadx.api.IJadxArgs;
import jadx.core.Consts;
import jadx.api.JadxDecompiler;
import jadx.core.utils.exceptions.JadxException;
import java.io.File;
@@ -20,7 +20,7 @@ import com.beust.jcommander.ParameterException;
public final class JadxCLIArgs implements IJadxArgs {
@Parameter(description = "<input file> (.dex, .apk or .jar)")
@Parameter(description = "<input file> (.dex, .apk, .jar or .class)")
protected List<String> files;
@Parameter(names = {"-d", "--output-dir"}, description = "output directory")
@@ -29,9 +29,12 @@ public final class JadxCLIArgs implements IJadxArgs {
@Parameter(names = {"-j", "--threads-count"}, description = "processing threads count")
protected int threadsCount = Runtime.getRuntime().availableProcessors();
@Parameter(names = {"-f", "--fallback"}, description = "make simple dump (using goto instead of 'if', 'for', etc)", help = true)
@Parameter(names = {"-f", "--fallback"}, description = "make simple dump (using goto instead of 'if', 'for', etc)")
protected boolean fallbackMode = false;
@Parameter(names = {"--show-bad-code"}, description = "show inconsistent code (incorrectly decompiled)")
protected boolean showInconsistentCode = false;
@Parameter(names = {"--cfg"}, description = "save methods control flow graph to dot file")
protected boolean cfgOutput = false;
@@ -47,46 +50,46 @@ public final class JadxCLIArgs implements IJadxArgs {
private final List<File> input = new ArrayList<File>(1);
private File outputDir;
public JadxCLIArgs(String[] args) {
parse(args);
processArgs();
public boolean processArgs(String[] args) {
return parse(args) && process();
}
private void parse(String[] args) {
private boolean parse(String[] args) {
try {
new JCommander(this, args);
return true;
} catch (ParameterException e) {
System.err.println("Arguments parse error: " + e.getMessage());
printUsage();
System.exit(1);
return false;
}
}
public void processArgs() {
private boolean process() {
if (isPrintHelp()) {
printUsage();
System.exit(0);
return false;
}
try {
if (threadsCount <= 0)
if (threadsCount <= 0) {
throw new JadxException("Threads count must be positive");
}
if (files != null) {
for (String fileName : files) {
File file = new File(fileName);
if (file.exists())
if (file.exists()) {
input.add(file);
else
} else {
throw new JadxException("File not found: " + file);
}
}
}
if (input.size() > 1)
if (input.size() > 1) {
throw new JadxException("Only one input file is supported");
if (outDirName != null)
}
if (outDirName != null) {
outputDir = new File(outDirName);
}
if (isVerbose()) {
ch.qos.logback.classic.Logger rootLogger =
(ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
@@ -95,8 +98,9 @@ public final class JadxCLIArgs implements IJadxArgs {
} catch (JadxException e) {
System.err.println("ERROR: " + e.getMessage());
printUsage();
System.exit(1);
return false;
}
return true;
}
public void printUsage() {
@@ -104,7 +108,7 @@ public final class JadxCLIArgs implements IJadxArgs {
// print usage in not sorted fields order (by default its sorted by description)
PrintStream out = System.out;
out.println();
out.println("jadx - dex to java decompiler, version: " + Consts.JADX_VERSION);
out.println("jadx - dex to java decompiler, version: " + JadxDecompiler.getVersion());
out.println();
out.println("usage: jadx [options] " + jc.getMainParameterDescription());
out.println("options:");
@@ -114,8 +118,9 @@ public final class JadxCLIArgs implements IJadxArgs {
int maxNamesLen = 0;
for (ParameterDescription p : params) {
int len = p.getNames().length();
if (len > maxNamesLen)
if (len > maxNamesLen) {
maxNamesLen = len;
}
}
Field[] fields = this.getClass().getDeclaredFields();
@@ -137,14 +142,16 @@ public final class JadxCLIArgs implements IJadxArgs {
}
private static void addSpaces(StringBuilder str, int count) {
for (int i = 0; i < count; i++)
for (int i = 0; i < count; i++) {
str.append(' ');
}
}
public List<File> getInput() {
return input;
}
@Override
public File getOutDir() {
return outputDir;
}
@@ -177,6 +184,11 @@ public final class JadxCLIArgs implements IJadxArgs {
return fallbackMode;
}
@Override
public boolean isShowInconsistentCode() {
return showInconsistentCode;
}
@Override
public boolean isVerbose() {
return verbose;
+11 -3
View File
@@ -1,9 +1,17 @@
ext.jadxClasspath = 'clsp-data/android-4.3.jar'
dependencies {
compile 'com.google.android.tools:dx:1.7'
compile 'ch.qos.logback:logback-classic:1.0.13'
runtime files(jadxClasspath)
compile files('lib/dx-1.10.jar')
compile 'org.ow2.asm:asm:5.0.3'
compile 'com.intellij:annotations:12.0'
testCompile 'org.smali:smali:2.0.3'
}
task packTests(type: Jar) {
classifier = 'tests'
from sourceSets.test.output
}
Binary file not shown.
@@ -0,0 +1,58 @@
package jadx.api;
public final class CodePosition {
private final JavaClass cls;
private final int line;
private final int offset;
public CodePosition(JavaClass cls, int line, int offset) {
this.cls = cls;
this.line = line;
this.offset = offset;
}
public CodePosition(int line, int offset) {
this.cls = null;
this.line = line;
this.offset = offset;
}
public JavaClass getJavaClass() {
return cls;
}
public int getLine() {
return line;
}
public int getOffset() {
return offset;
}
public boolean isSet() {
return line != 0 || offset != 0;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
CodePosition that = (CodePosition) o;
return line == that.line && offset == that.offset;
}
@Override
public int hashCode() {
return line + 31 * offset;
}
@Override
public String toString() {
return line + ":" + offset + (cls != null ? " " + cls : "");
}
}
@@ -1,7 +1,14 @@
package jadx.api;
import java.io.File;
public class DefaultJadxArgs implements IJadxArgs {
@Override
public File getOutDir() {
return new File("jadx-output");
}
@Override
public int getThreadsCount() {
return Runtime.getRuntime().availableProcessors();
@@ -22,6 +29,11 @@ public class DefaultJadxArgs implements IJadxArgs {
return false;
}
@Override
public boolean isShowInconsistentCode() {
return false;
}
@Override
public boolean isVerbose() {
return false;
@@ -1,6 +1,10 @@
package jadx.api;
import java.io.File;
public interface IJadxArgs {
File getOutDir();
int getThreadsCount();
boolean isCFGOutput();
@@ -9,5 +13,7 @@ public interface IJadxArgs {
boolean isFallbackMode();
boolean isShowInconsistentCode();
boolean isVerbose();
}
@@ -7,16 +7,14 @@ import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.dex.visitors.SaveCode;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.InputFile;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
@@ -24,7 +22,6 @@ import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
@@ -33,7 +30,7 @@ import org.slf4j.LoggerFactory;
/**
* Jadx API usage example:
* <pre><code>
* Decompiler jadx = new Decompiler();
* JadxDecompiler jadx = new JadxDecompiler();
* jadx.loadFile(new File("classes.dex"));
* jadx.setOutputDir(new File("out"));
* jadx.save();
@@ -46,8 +43,8 @@ import org.slf4j.LoggerFactory;
* }
* </code></pre>
*/
public final class Decompiler {
private static final Logger LOG = LoggerFactory.getLogger(Decompiler.class);
public final class JadxDecompiler {
private static final Logger LOG = LoggerFactory.getLogger(JadxDecompiler.class);
private final IJadxArgs args;
private final List<InputFile> inputFiles = new ArrayList<InputFile>();
@@ -56,14 +53,16 @@ public final class Decompiler {
private RootNode root;
private List<IDexTreeVisitor> passes;
private List<JavaClass> classes;
public Decompiler() {
this.args = new DefaultJadxArgs();
init();
public JadxDecompiler() {
this(new DefaultJadxArgs());
}
public Decompiler(IJadxArgs jadxArgs) {
public JadxDecompiler(IJadxArgs jadxArgs) {
this.args = jadxArgs;
this.outDir = jadxArgs.getOutDir();
reset();
init();
}
@@ -74,22 +73,36 @@ public final class Decompiler {
void init() {
if (outDir == null) {
outDir = new File("jadx-output");
outDir = new DefaultJadxArgs().getOutDir();
}
this.passes = Jadx.getPassesList(args, outDir);
}
public void loadFile(File file) throws IOException, DecodeException {
loadFiles(Arrays.asList(file));
void reset() {
ClassInfo.clearCache();
classes = null;
root = null;
}
public void loadFiles(List<File> files) throws IOException, DecodeException {
public static String getVersion() {
return Jadx.getVersion();
}
public void loadFile(File file) throws JadxException {
loadFiles(Collections.singletonList(file));
}
public void loadFiles(List<File> files) throws JadxException {
if (files.isEmpty()) {
throw new JadxRuntimeException("Empty file list");
throw new JadxException("Empty file list");
}
inputFiles.clear();
for (File file : files) {
inputFiles.add(new InputFile(file));
try {
inputFiles.add(new InputFile(file));
} catch (IOException e) {
throw new JadxException("Error load file: " + file, e);
}
}
parse();
}
@@ -97,54 +110,56 @@ public final class Decompiler {
public void save() {
try {
ExecutorService ex = getSaveExecutor();
ex.shutdown();
ex.awaitTermination(1, TimeUnit.DAYS);
} catch (InterruptedException e) {
LOG.error("Save interrupted", e);
throw new JadxRuntimeException("Save interrupted", e);
}
}
public ThreadPoolExecutor getSaveExecutor() {
public ExecutorService getSaveExecutor() {
if (root == null) {
throw new JadxRuntimeException("No loaded files");
}
int threadsCount = args.getThreadsCount();
LOG.debug("processing threads count: {}", threadsCount);
ArrayList<IDexTreeVisitor> passList = new ArrayList<IDexTreeVisitor>(passes);
SaveCode savePass = new SaveCode(outDir, args);
passList.add(savePass);
LOG.info("processing ...");
ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(threadsCount);
for (ClassNode cls : root.getClasses(false)) {
if (cls.getCode() == null) {
ProcessClass job = new ProcessClass(cls, passList);
executor.execute(job);
} else {
try {
savePass.visit(cls);
} catch (CodegenException e) {
LOG.error("Can't save class {}", cls, e);
ExecutorService executor = Executors.newFixedThreadPool(threadsCount);
for (final JavaClass cls : getClasses()) {
executor.execute(new Runnable() {
@Override
public void run() {
cls.decompile();
SaveCode.save(outDir, args, cls.getClassNode());
}
}
});
}
executor.shutdown();
return executor;
}
public List<JavaClass> getClasses() {
List<ClassNode> classNodeList = root.getClasses(false);
List<JavaClass> classes = new ArrayList<JavaClass>(classNodeList.size());
for (ClassNode classNode : classNodeList) {
classes.add(new JavaClass(this, classNode));
if (root == null) {
return Collections.emptyList();
}
return Collections.unmodifiableList(classes);
if (classes == null) {
List<ClassNode> classNodeList = root.getClasses(false);
List<JavaClass> clsList = new ArrayList<JavaClass>(classNodeList.size());
for (ClassNode classNode : classNodeList) {
clsList.add(new JavaClass(classNode, this));
}
classes = Collections.unmodifiableList(clsList);
}
return classes;
}
public List<JavaPackage> getPackages() {
List<JavaClass> classes = getClasses();
List<JavaClass> classList = getClasses();
if (classList.isEmpty()) {
return Collections.emptyList();
}
Map<String, List<JavaClass>> map = new HashMap<String, List<JavaClass>>();
for (JavaClass javaClass : classes) {
for (JavaClass javaClass : classList) {
String pkg = javaClass.getPackage();
List<JavaClass> clsList = map.get(pkg);
if (clsList == null) {
@@ -162,7 +177,7 @@ public final class Decompiler {
Collections.sort(pkg.getClasses(), new Comparator<JavaClass>() {
@Override
public int compare(JavaClass o1, JavaClass o2) {
return o1.getShortName().compareTo(o2.getShortName());
return o1.getName().compareTo(o2.getName());
}
});
}
@@ -170,29 +185,48 @@ public final class Decompiler {
}
public int getErrorsCount() {
return ErrorsCounter.getErrorCount();
if (root == null) {
return 0;
}
return root.getErrorsCounter().getErrorCount();
}
public void printErrorsReport() {
if (root == null) {
return;
}
root.getErrorsCounter().printReport();
}
void parse() throws DecodeException {
ClassInfo.clearCache();
ErrorsCounter.reset();
reset();
root = new RootNode();
LOG.info("loading ...");
root.load(inputFiles);
}
void processClass(ClassNode cls) {
try {
ProcessClass job = new ProcessClass(cls, passes);
LOG.info("processing class {} ...", cls);
job.run();
} catch (Throwable e) {
LOG.error("Process class error", e);
}
ProcessClass.process(cls, passes);
}
RootNode getRoot() {
return root;
}
JavaClass findJavaClass(ClassNode cls) {
if (cls == null) {
return null;
}
for (JavaClass javaClass : getClasses()) {
if (javaClass.getClassNode().equals(cls)) {
return javaClass;
}
}
return null;
}
@Override
public String toString() {
return "jadx decompiler " + getVersion();
}
}
+128 -34
View File
@@ -1,6 +1,8 @@
package jadx.api;
import jadx.core.codegen.CodeWriter;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
@@ -10,49 +12,90 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
public final class JavaClass {
public final class JavaClass implements JavaNode {
private final Decompiler decompiler;
private final JadxDecompiler decompiler;
private final ClassNode cls;
private final List<JavaClass> innerClasses;
private final List<JavaField> fields;
private final List<JavaMethod> methods;
private final JavaClass parent;
JavaClass(Decompiler decompiler, ClassNode classNode) {
private List<JavaClass> innerClasses = Collections.emptyList();
private List<JavaField> fields = Collections.emptyList();
private List<JavaMethod> methods = Collections.emptyList();
JavaClass(ClassNode classNode, JadxDecompiler decompiler) {
this.decompiler = decompiler;
this.cls = classNode;
this.parent = null;
}
/**
* Inner classes constructor
*/
JavaClass(ClassNode classNode, JavaClass parent) {
this.decompiler = null;
this.cls = classNode;
this.parent = parent;
}
public String getCode() {
CodeWriter code = cls.getCode();
if (code == null) {
decompile();
code = cls.getCode();
}
if (code == null) {
return "";
}
return code.toString();
}
public void decompile() {
if (decompiler == null) {
return;
}
if (cls.getCode() == null) {
decompiler.processClass(cls);
load();
}
}
ClassNode getClassNode() {
return cls;
}
private void load() {
int inClsCount = cls.getInnerClasses().size();
if (inClsCount == 0) {
this.innerClasses = Collections.emptyList();
} else {
if (inClsCount != 0) {
List<JavaClass> list = new ArrayList<JavaClass>(inClsCount);
for (ClassNode inner : cls.getInnerClasses()) {
list.add(new JavaClass(decompiler, inner));
if (!inner.contains(AFlag.DONT_GENERATE)) {
JavaClass javaClass = new JavaClass(inner, this);
javaClass.load();
list.add(javaClass);
}
}
this.innerClasses = Collections.unmodifiableList(list);
}
int fieldsCount = cls.getFields().size();
if (fieldsCount == 0) {
this.fields = Collections.emptyList();
} else {
if (fieldsCount != 0) {
List<JavaField> flds = new ArrayList<JavaField>(fieldsCount);
for (FieldNode f : cls.getFields()) {
flds.add(new JavaField(f));
if (!f.contains(AFlag.DONT_GENERATE)) {
flds.add(new JavaField(f, this));
}
}
this.fields = Collections.unmodifiableList(flds);
}
int methodsCount = cls.getMethods().size();
if (methodsCount == 0) {
this.methods = Collections.emptyList();
} else {
if (methodsCount != 0) {
List<JavaMethod> mths = new ArrayList<JavaMethod>(methodsCount);
for (MethodNode m : cls.getMethods()) {
if (!m.getAccessFlags().isSynthetic()) {
mths.add(new JavaMethod(m));
if (!m.contains(AFlag.DONT_GENERATE)) {
mths.add(new JavaMethod(this, m));
}
}
Collections.sort(mths, new Comparator<JavaMethod>() {
@@ -65,49 +108,100 @@ public final class JavaClass {
}
}
public String getCode() {
CodeWriter code = cls.getCode();
if (code == null) {
decompiler.processClass(cls);
code = cls.getCode();
}
return code != null ? code.toString() : "error processing class";
private Map<CodePosition, Object> getCodeAnnotations() {
decompile();
return cls.getCode().getAnnotations();
}
public CodePosition getDefinitionPosition(int line, int offset) {
Map<CodePosition, Object> map = getCodeAnnotations();
Object obj = map.get(new CodePosition(line, offset));
if (!(obj instanceof LineAttrNode)) {
return null;
}
ClassNode clsNode = null;
if (obj instanceof ClassNode) {
clsNode = (ClassNode) obj;
} else if (obj instanceof MethodNode) {
clsNode = ((MethodNode) obj).getParentClass();
} else if (obj instanceof FieldNode) {
clsNode = ((FieldNode) obj).getParentClass();
}
if (clsNode == null) {
return null;
}
clsNode = clsNode.getTopParentClass();
JavaClass jCls = decompiler.findJavaClass(clsNode);
if (jCls == null) {
return null;
}
jCls.decompile();
int defLine = ((LineAttrNode) obj).getDecompiledLine();
if (defLine == 0) {
return null;
}
return new CodePosition(jCls, defLine, 0);
}
public Integer getSourceLine(int decompiledLine) {
decompile();
return cls.getCode().getLineMapping().get(decompiledLine);
}
@Override
public String getName() {
return cls.getShortName();
}
@Override
public String getFullName() {
return cls.getFullName();
}
public String getShortName() {
return cls.getShortName();
}
public String getPackage() {
return cls.getPackage();
}
@Override
public JavaClass getDeclaringClass() {
return parent;
}
public AccessInfo getAccessInfo() {
return cls.getAccessFlags();
}
public List<JavaClass> getInnerClasses() {
decompile();
return innerClasses;
}
public List<JavaField> getFields() {
decompile();
return fields;
}
public List<JavaMethod> getMethods() {
decompile();
return methods;
}
public int getDecompiledLine() {
return cls.getDecompiledLine();
}
@Override
public boolean equals(Object o) {
return this == o || o instanceof JavaClass && cls.equals(((JavaClass) o).cls);
}
@Override
public int hashCode() {
return cls.hashCode();
}
@Override
public String toString() {
return getFullName();
}
public int getDecompiledLine() {
return cls.getDecompiledLine();
}
}
@@ -4,18 +4,31 @@ import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.FieldNode;
public final class JavaField {
public final class JavaField implements JavaNode {
private final FieldNode field;
private final JavaClass parent;
public JavaField(FieldNode f) {
JavaField(FieldNode f, JavaClass cls) {
this.field = f;
this.parent = cls;
}
@Override
public String getName() {
return field.getName();
}
@Override
public String getFullName() {
return parent.getFullName() + "." + field.getName();
}
@Override
public JavaClass getDeclaringClass() {
return parent;
}
public AccessInfo getAccessFlags() {
return field.getAccessFlags();
}
@@ -1,27 +1,33 @@
package jadx.api;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.MethodNode;
import java.util.List;
public final class JavaMethod {
public final class JavaMethod implements JavaNode {
private final MethodNode mth;
private final JavaClass parent;
public JavaMethod(MethodNode m) {
JavaMethod(JavaClass cls, MethodNode m) {
this.parent = cls;
this.mth = m;
}
@Override
public String getName() {
MethodInfo mi = mth.getMethodInfo();
if (mi.isConstructor()) {
return mth.getParentClass().getShortName();
} else if (mi.isClassInit()) {
return "static";
}
return mi.getName();
return mth.getName();
}
@Override
public String getFullName() {
return mth.getMethodInfo().getFullName();
}
@Override
public JavaClass getDeclaringClass() {
return parent;
}
public AccessInfo getAccessFlags() {
@@ -0,0 +1,10 @@
package jadx.api;
public interface JavaNode {
String getName();
String getFullName();
JavaClass getDeclaringClass();
}
@@ -2,7 +2,7 @@ package jadx.api;
import java.util.List;
public final class JavaPackage implements Comparable<JavaPackage> {
public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
private final String name;
private final List<JavaClass> classes;
@@ -11,14 +11,26 @@ public final class JavaPackage implements Comparable<JavaPackage> {
this.classes = classes;
}
@Override
public String getName() {
return name;
}
@Override
public String getFullName() {
// TODO: store full package name
return name;
}
public List<JavaClass> getClasses() {
return classes;
}
@Override
public JavaClass getDeclaringClass() {
return null;
}
@Override
public int compareTo(JavaPackage o) {
return name.compareTo(o.name);
@@ -26,11 +38,14 @@ public final class JavaPackage implements Comparable<JavaPackage> {
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
JavaPackage that = (JavaPackage) o;
if (!name.equals(that.name)) return false;
return true;
return name.equals(that.name);
}
@Override
@@ -1,8 +1,6 @@
package jadx.core;
public class Consts {
public static final String JADX_VERSION = Jadx.getVersion();
public static final boolean DEBUG = false;
public static final String CLASS_OBJECT = "java.lang.Object";
@@ -21,4 +19,6 @@ public class Consts {
public static final String DEFAULT_PACKAGE_NAME = "defpackage";
public static final String ANONYMOUS_CLASS_PREFIX = "AnonymousClass_";
public static final String MTH_TOSTRING_SIGNATURE = "toString()Ljava/lang/String;";
}
+49 -29
View File
@@ -6,24 +6,29 @@ import jadx.core.dex.visitors.BlockMakerVisitor;
import jadx.core.dex.visitors.ClassModifier;
import jadx.core.dex.visitors.CodeShrinker;
import jadx.core.dex.visitors.ConstInlinerVisitor;
import jadx.core.dex.visitors.DebugInfoVisitor;
import jadx.core.dex.visitors.DotGraphVisitor;
import jadx.core.dex.visitors.EnumVisitor;
import jadx.core.dex.visitors.FallbackModeVisitor;
import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.dex.visitors.MethodInlinerVisitor;
import jadx.core.dex.visitors.MethodInlineVisitor;
import jadx.core.dex.visitors.ModVisitor;
import jadx.core.dex.visitors.PrepareForCodeGen;
import jadx.core.dex.visitors.ReSugarCode;
import jadx.core.dex.visitors.SimplifyVisitor;
import jadx.core.dex.visitors.regions.CheckRegions;
import jadx.core.dex.visitors.regions.CleanRegions;
import jadx.core.dex.visitors.regions.PostRegionVisitor;
import jadx.core.dex.visitors.regions.IfRegionVisitor;
import jadx.core.dex.visitors.regions.LoopRegionVisitor;
import jadx.core.dex.visitors.regions.ProcessVariables;
import jadx.core.dex.visitors.regions.RegionMakerVisitor;
import jadx.core.dex.visitors.typeresolver.FinishTypeResolver;
import jadx.core.dex.visitors.typeresolver.TypeResolver;
import jadx.core.dex.visitors.regions.ReturnVisitor;
import jadx.core.dex.visitors.ssa.EliminatePhiNodes;
import jadx.core.dex.visitors.ssa.SSATransform;
import jadx.core.dex.visitors.typeinference.FinishTypeInference;
import jadx.core.dex.visitors.typeinference.TypeInference;
import jadx.core.utils.Utils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
@@ -37,10 +42,12 @@ public class Jadx {
private static final Logger LOG = LoggerFactory.getLogger(Jadx.class);
static {
if (Consts.DEBUG)
if (Consts.DEBUG) {
LOG.info("debug enabled");
if (Jadx.class.desiredAssertionStatus())
}
if (Jadx.class.desiredAssertionStatus()) {
LOG.info("assertions enabled");
}
}
public static List<IDexTreeVisitor> getPassesList(IJadxArgs args, File outDir) {
@@ -49,34 +56,43 @@ public class Jadx {
passes.add(new FallbackModeVisitor());
} else {
passes.add(new BlockMakerVisitor());
passes.add(new TypeResolver());
if (args.isRawCFGOutput())
passes.add(new DotGraphVisitor(outDir, false, true));
passes.add(new SSATransform());
passes.add(new DebugInfoVisitor());
passes.add(new TypeInference());
if (args.isRawCFGOutput()) {
passes.add(DotGraphVisitor.dumpRaw(outDir));
}
passes.add(new ConstInlinerVisitor());
passes.add(new FinishTypeResolver());
passes.add(new FinishTypeInference());
passes.add(new EliminatePhiNodes());
passes.add(new ModVisitor());
passes.add(new EnumVisitor());
if (args.isCFGOutput())
passes.add(new DotGraphVisitor(outDir, false));
passes.add(new CodeShrinker());
passes.add(new ReSugarCode());
if (args.isCFGOutput()) {
passes.add(DotGraphVisitor.dump(outDir));
}
passes.add(new RegionMakerVisitor());
passes.add(new PostRegionVisitor());
passes.add(new IfRegionVisitor());
passes.add(new ReturnVisitor());
passes.add(new CodeShrinker());
passes.add(new SimplifyVisitor());
passes.add(new ProcessVariables());
passes.add(new CheckRegions());
if (args.isCFGOutput())
passes.add(new DotGraphVisitor(outDir, true));
passes.add(new MethodInlinerVisitor());
if (args.isCFGOutput()) {
passes.add(DotGraphVisitor.dumpRegions(outDir));
}
passes.add(new MethodInlineVisitor());
passes.add(new ClassModifier());
passes.add(new CleanRegions());
passes.add(new PrepareForCodeGen());
passes.add(new LoopRegionVisitor());
passes.add(new ProcessVariables());
}
passes.add(new CodeGen(args));
return passes;
@@ -84,14 +100,18 @@ public class Jadx {
public static String getVersion() {
try {
Enumeration<URL> resources = Utils.class.getClassLoader().getResources("META-INF/MANIFEST.MF");
while (resources.hasMoreElements()) {
Manifest manifest = new Manifest(resources.nextElement().openStream());
String ver = manifest.getMainAttributes().getValue("jadx-version");
if (ver != null)
return ver;
ClassLoader classLoader = Utils.class.getClassLoader();
if (classLoader != null) {
Enumeration<URL> resources = classLoader.getResources("META-INF/MANIFEST.MF");
while (resources.hasMoreElements()) {
Manifest manifest = new Manifest(resources.nextElement().openStream());
String ver = manifest.getMainAttributes().getValue("jadx-version");
if (ver != null) {
return ver;
}
}
}
} catch (IOException e) {
} catch (Exception e) {
LOG.error("Can't get manifest file", e);
}
return "dev";
@@ -1,35 +1,28 @@
package jadx.core;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.visitors.DepthTraverser;
import jadx.core.dex.visitors.DepthTraversal;
import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.utils.exceptions.DecodeException;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class ProcessClass implements Runnable {
public final class ProcessClass {
private static final Logger LOG = LoggerFactory.getLogger(ProcessClass.class);
private final ClassNode cls;
private final List<IDexTreeVisitor> passes;
public ProcessClass(ClassNode cls, List<IDexTreeVisitor> passes) {
this.cls = cls;
this.passes = passes;
private ProcessClass() {
}
@Override
public void run() {
public static void process(ClassNode cls, List<IDexTreeVisitor> passes) {
try {
cls.load();
for (IDexTreeVisitor visitor : passes) {
DepthTraverser.visit(visitor, cls);
DepthTraversal.visit(visitor, cls);
}
} catch (DecodeException e) {
LOG.error("Decode exception: " + cls, e);
} catch (Exception e) {
LOG.error("Class process exception: {}", cls, e);
} finally {
cls.unload();
}
@@ -3,9 +3,9 @@ package jadx.core.clsp;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.FileUtils;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
@@ -65,6 +65,9 @@ public class ClsSet {
for (ClassNode cls : list) {
if (cls.getAccessFlags().isPublic()) {
NClass nClass = getCls(cls.getRawName(), names);
if (nClass == null) {
throw new JadxRuntimeException("Missing class: " + cls);
}
nClass.setParents(makeParentsArray(cls, names));
classes[k] = nClass;
k++;
@@ -93,49 +96,56 @@ public class ClsSet {
private static NClass getCls(String fullName, Map<String, NClass> names) {
NClass id = names.get(fullName);
if (id == null && !names.containsKey(fullName)) {
LOG.warn("Class not found: " + fullName);
LOG.warn("Class not found: {}", fullName);
}
return id;
}
void save(File output) throws IOException {
Utils.makeDirsForFile(output);
FileUtils.makeDirsForFile(output);
BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(output));
String outputName = output.getName();
if (outputName.endsWith(CLST_EXTENSION)) {
save(outputStream);
} else if (outputName.endsWith(".jar")) {
ZipOutputStream out = new ZipOutputStream(outputStream);
try {
out.putNextEntry(new ZipEntry(CLST_PKG_PATH + "/" + CLST_FILENAME));
save(out);
out.closeEntry();
} finally {
out.close();
outputStream.close();
try {
String outputName = output.getName();
if (outputName.endsWith(CLST_EXTENSION)) {
save(outputStream);
} else if (outputName.endsWith(".jar")) {
ZipOutputStream out = new ZipOutputStream(outputStream);
try {
out.putNextEntry(new ZipEntry(CLST_PKG_PATH + "/" + CLST_FILENAME));
save(out);
out.closeEntry();
} finally {
out.close();
}
} else {
throw new JadxRuntimeException("Unknown file format: " + outputName);
}
} else {
throw new JadxRuntimeException("Unknown file format: " + outputName);
} finally {
outputStream.close();
}
}
public void save(OutputStream output) throws IOException {
DataOutputStream out = new DataOutputStream(output);
out.writeBytes(JADX_CLS_SET_HEADER);
out.writeByte(VERSION);
try {
out.writeBytes(JADX_CLS_SET_HEADER);
out.writeByte(VERSION);
LOG.info("Classes count: " + classes.length);
out.writeInt(classes.length);
for (NClass cls : classes) {
writeString(out, cls.getName());
}
for (NClass cls : classes) {
NClass[] parents = cls.getParents();
out.writeByte(parents.length);
for (NClass parent : parents) {
out.writeInt(parent.getId());
LOG.info("Classes count: {}", classes.length);
out.writeInt(classes.length);
for (NClass cls : classes) {
writeString(out, cls.getName());
}
for (NClass cls : classes) {
NClass[] parents = cls.getParents();
out.writeByte(parents.length);
for (NClass parent : parents) {
out.writeInt(parent.getId());
}
}
} finally {
out.close();
}
}
@@ -144,55 +154,67 @@ public class ClsSet {
if (input == null) {
throw new JadxRuntimeException("Can't load classpath file: " + CLST_FILENAME);
}
load(input);
try {
load(input);
} finally {
input.close();
}
}
public void load(File input) throws IOException, DecodeException {
String name = input.getName();
InputStream inputStream = new FileInputStream(input);
if (name.endsWith(CLST_EXTENSION)) {
load(inputStream);
} else if (name.endsWith(".jar")) {
ZipInputStream in = new ZipInputStream(inputStream);
try {
ZipEntry entry = in.getNextEntry();
while (entry != null) {
if (entry.getName().endsWith(CLST_EXTENSION)) {
load(in);
try {
if (name.endsWith(CLST_EXTENSION)) {
load(inputStream);
} else if (name.endsWith(".jar")) {
ZipInputStream in = new ZipInputStream(inputStream);
try {
ZipEntry entry = in.getNextEntry();
while (entry != null) {
if (entry.getName().endsWith(CLST_EXTENSION)) {
load(in);
}
entry = in.getNextEntry();
}
entry = in.getNextEntry();
} finally {
in.close();
}
} finally {
in.close();
} else {
throw new JadxRuntimeException("Unknown file format: " + name);
}
} else {
throw new JadxRuntimeException("Unknown file format: " + name);
} finally {
inputStream.close();
}
}
public void load(InputStream input) throws IOException, DecodeException {
DataInputStream in = new DataInputStream(input);
byte[] header = new byte[JADX_CLS_SET_HEADER.length()];
int readHeaderLength = in.read(header);
int version = in.readByte();
if (readHeaderLength != JADX_CLS_SET_HEADER.length()
|| !JADX_CLS_SET_HEADER.equals(new String(header, STRING_CHARSET))
|| version != VERSION) {
throw new DecodeException("Wrong jadx class set header");
}
int count = in.readInt();
classes = new NClass[count];
for (int i = 0; i < count; i++) {
String name = readString(in);
classes[i] = new NClass(name, i);
}
for (int i = 0; i < count; i++) {
int pCount = in.readByte();
NClass[] parents = new NClass[pCount];
for (int j = 0; j < pCount; j++) {
parents[j] = classes[in.readInt()];
try {
byte[] header = new byte[JADX_CLS_SET_HEADER.length()];
int readHeaderLength = in.read(header);
int version = in.readByte();
if (readHeaderLength != JADX_CLS_SET_HEADER.length()
|| !JADX_CLS_SET_HEADER.equals(new String(header, STRING_CHARSET))
|| version != VERSION) {
throw new DecodeException("Wrong jadx class set header");
}
classes[i].setParents(parents);
int count = in.readInt();
classes = new NClass[count];
for (int i = 0; i < count; i++) {
String name = readString(in);
classes[i] = new NClass(name, i);
}
for (int i = 0; i < count; i++) {
int pCount = in.readByte();
NClass[] parents = new NClass[pCount];
for (int j = 0; j < pCount; j++) {
parents[j] = classes[in.readInt()];
}
classes[i].setParents(parents);
}
} finally {
in.close();
}
}
@@ -5,6 +5,7 @@ import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -44,35 +45,47 @@ public class ClspGraph {
throw new JadxRuntimeException("Classpath must be loaded first");
}
int size = classes.size();
for (ClassNode cls : classes) {
size += cls.getInnerClasses().size();
}
NClass[] nClasses = new NClass[size];
for (int i = 0; i < size; i++) {
ClassNode cls = classes.get(i);
NClass nClass = new NClass(cls.getRawName(), -1);
nClasses[i] = nClass;
nameMap.put(cls.getRawName(), nClass);
int k = 0;
for (ClassNode cls : classes) {
nClasses[k++] = addClass(cls);
for (ClassNode inner : cls.getInnerClasses()) {
nClasses[k++] = addClass(inner);
}
}
for (int i = 0; i < size; i++) {
nClasses[i].setParents(ClsSet.makeParentsArray(classes.get(i), nameMap));
}
}
private NClass addClass(ClassNode cls) {
NClass nClass = new NClass(cls.getRawName(), -1);
nameMap.put(cls.getRawName(), nClass);
return nClass;
}
public boolean isImplements(String clsName, String implClsName) {
Set<String> anc = getAncestors(clsName);
return anc.contains(implClsName);
}
public String getCommonAncestor(String clsName, String implClsName) {
if (clsName.equals(implClsName)) {
return clsName;
}
NClass cls = nameMap.get(implClsName);
if (cls == null) {
LOG.debug("Missing class: {}", implClsName);
return null;
}
if (isImplements(clsName, implClsName)) {
return implClsName;
}
Set<String> anc = getAncestors(clsName);
NClass cls = nameMap.get(implClsName);
if (cls != null) {
return searchCommonParent(anc, cls);
} else {
LOG.debug("Missing class: {}", implClsName);
return null;
}
return searchCommonParent(anc, cls);
}
private String searchCommonParent(Set<String> anc, NClass cls) {
@@ -80,10 +93,10 @@ public class ClspGraph {
String name = p.getName();
if (anc.contains(name)) {
return name;
} else {
String r = searchCommonParent(anc, p);
if (r != null)
return r;
}
String r = searchCommonParent(anc, p);
if (r != null) {
return r;
}
}
return null;
@@ -91,16 +104,20 @@ public class ClspGraph {
private Set<String> getAncestors(String clsName) {
Set<String> result = ancestorCache.get(clsName);
if (result == null) {
result = new HashSet<String>();
ancestorCache.put(clsName, result);
NClass cls = nameMap.get(clsName);
if (cls != null) {
addAncestorsNames(cls, result);
} else {
LOG.debug("Missing class: {}", clsName);
}
if (result != null) {
return result;
}
NClass cls = nameMap.get(clsName);
if (cls == null) {
LOG.debug("Missing class: {}", clsName);
return Collections.emptySet();
}
result = new HashSet<String>();
addAncestorsNames(cls, result);
if (result.isEmpty()) {
result = Collections.emptySet();
}
ancestorCache.put(clsName, result);
return result;
}
@@ -39,7 +39,7 @@ public class ConvertToClsSet {
}
}
for (InputFile inputFile : inputFiles) {
LOG.info("Loaded: " + inputFile.getFile());
LOG.info("Loaded: {}", inputFile.getFile());
}
RootNode root = new RootNode();
@@ -48,12 +48,15 @@ public class ConvertToClsSet {
ClsSet set = new ClsSet();
set.load(root);
set.save(output);
LOG.info("Output: " + output);
LOG.info("Output: {}", output);
LOG.info("done");
}
private static void addFilesFromDirectory(File dir, List<InputFile> inputFiles) throws IOException, DecodeException {
File[] files = dir.listFiles();
if (files == null) {
return;
}
for (File file : files) {
if (file.isDirectory()) {
addFilesFromDirectory(file, inputFiles);
@@ -41,11 +41,14 @@ public class NClass {
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
NClass nClass = (NClass) o;
if (!name.equals(nClass.name)) return false;
return true;
return name.equals(nClass.name);
}
@Override
@@ -1,7 +1,7 @@
package jadx.core.codegen;
import jadx.core.Consts;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttributeNode;
import jadx.core.dex.attributes.annotations.Annotation;
import jadx.core.dex.attributes.annotations.AnnotationsList;
@@ -43,20 +43,20 @@ public class AnnotationGen {
public void addForParameter(CodeWriter code, MethodParameters paramsAnnotations, int n) {
AnnotationsList aList = paramsAnnotations.getParamList().get(n);
if (aList == null || aList.size() == 0)
if (aList == null || aList.isEmpty()) {
return;
}
for (Annotation a : aList.getAll()) {
code.add(formatAnnotation(a));
formatAnnotation(code, a);
code.add(' ');
}
}
private void add(IAttributeNode node, CodeWriter code) {
AnnotationsList aList = (AnnotationsList) node.getAttributes().get(AttributeType.ANNOTATION_LIST);
if (aList == null || aList.size() == 0)
AnnotationsList aList = node.get(AType.ANNOTATION_LIST);
if (aList == null || aList.isEmpty()) {
return;
}
for (Annotation a : aList.getAll()) {
String aCls = a.getAnnotationClass();
if (aCls.startsWith(Consts.DALVIK_ANNOTATION_PKG)) {
@@ -66,52 +66,52 @@ public class AnnotationGen {
}
} else {
code.startLine();
code.add(formatAnnotation(a));
formatAnnotation(code, a);
}
}
}
private CodeWriter formatAnnotation(Annotation a) {
CodeWriter code = new CodeWriter();
private void formatAnnotation(CodeWriter code, Annotation a) {
code.add('@');
code.add(classGen.useClass(a.getType()));
classGen.useType(code, a.getType());
Map<String, Object> vl = a.getValues();
if (vl.size() != 0) {
if (!vl.isEmpty()) {
code.add('(');
if (vl.size() == 1 && vl.containsKey("value")) {
code.add(encValueToString(vl.get("value")));
encodeValue(code, vl.get("value"));
} else {
for (Iterator<Entry<String, Object>> it = vl.entrySet().iterator(); it.hasNext(); ) {
Entry<String, Object> e = it.next();
code.add(e.getKey());
code.add(" = ");
code.add(encValueToString(e.getValue()));
if (it.hasNext())
encodeValue(code, e.getValue());
if (it.hasNext()) {
code.add(", ");
}
}
}
code.add(')');
}
return code;
}
@SuppressWarnings("unchecked")
public void addThrows(MethodNode mth, CodeWriter code) {
Annotation an = mth.getAttributes().getAnnotation(Consts.DALVIK_THROWS);
Annotation an = mth.getAnnotation(Consts.DALVIK_THROWS);
if (an != null) {
Object exs = an.getDefaultValue();
code.add(" throws ");
for (Iterator<ArgType> it = ((List<ArgType>) exs).iterator(); it.hasNext(); ) {
ArgType ex = it.next();
code.add(TypeGen.translate(classGen, ex));
if (it.hasNext())
classGen.useType(code, ex);
if (it.hasNext()) {
code.add(", ");
}
}
}
}
public Object getAnnotationDefaultValue(String name) {
Annotation an = cls.getAttributes().getAnnotation(Consts.DALVIK_ANNOTATION_DEFAULT);
Annotation an = cls.getAnnotation(Consts.DALVIK_ANNOTATION_DEFAULT);
if (an != null) {
Annotation defAnnotation = (Annotation) an.getDefaultValue();
return defAnnotation.getValues().get(name);
@@ -120,65 +120,52 @@ public class AnnotationGen {
}
// TODO: refactor this boilerplate code
@SuppressWarnings("unchecked")
public String encValueToString(Object val) {
if (val == null)
return "null";
if (val instanceof String)
return StringUtils.unescapeString((String) val);
if (val instanceof Integer)
return TypeGen.formatInteger((Integer) val);
if (val instanceof Character)
return StringUtils.unescapeChar((Character) val);
if (val instanceof Boolean)
return Boolean.TRUE.equals(val) ? "true" : "false";
if (val instanceof Float)
return TypeGen.formatFloat((Float) val);
if (val instanceof Double)
return TypeGen.formatDouble((Double) val);
if (val instanceof Long)
return TypeGen.formatLong((Long) val);
if (val instanceof Short)
return TypeGen.formatShort((Short) val);
if (val instanceof Byte)
return TypeGen.formatByte((Byte) val);
if (val instanceof ArgType)
return TypeGen.translate(classGen, (ArgType) val) + ".class";
if (val instanceof FieldInfo) {
public void encodeValue(CodeWriter code, Object val) {
if (val == null) {
code.add("null");
return;
}
if (val instanceof String) {
code.add(StringUtils.unescapeString((String) val));
} else if (val instanceof Integer) {
code.add(TypeGen.formatInteger((Integer) val));
} else if (val instanceof Character) {
code.add(StringUtils.unescapeChar((Character) val));
} else if (val instanceof Boolean) {
code.add(Boolean.TRUE.equals(val) ? "true" : "false");
} else if (val instanceof Float) {
code.add(TypeGen.formatFloat((Float) val));
} else if (val instanceof Double) {
code.add(TypeGen.formatDouble((Double) val));
} else if (val instanceof Long) {
code.add(TypeGen.formatLong((Long) val));
} else if (val instanceof Short) {
code.add(TypeGen.formatShort((Short) val));
} else if (val instanceof Byte) {
code.add(TypeGen.formatByte((Byte) val));
} else if (val instanceof ArgType) {
classGen.useType(code, (ArgType) val);
code.add(".class");
} else if (val instanceof FieldInfo) {
// must be a static field
FieldInfo field = (FieldInfo) val;
// FIXME: !!code from InsnGen.sfield
String thisClass = cls.getFullName();
if (field.getDeclClass().getFullName().equals(thisClass)) {
return field.getName();
} else {
return classGen.useClass(field.getDeclClass()) + '.' + field.getName();
}
}
if (val instanceof List) {
StringBuilder str = new StringBuilder();
str.append('{');
List<Object> list = (List<Object>) val;
for (Iterator<Object> it = list.iterator(); it.hasNext(); ) {
InsnGen.makeStaticFieldAccess(code, field, classGen);
} else if (val instanceof Iterable) {
code.add('{');
Iterator<?> it = ((Iterable) val).iterator();
while (it.hasNext()) {
Object obj = it.next();
str.append(encValueToString(obj));
if (it.hasNext())
str.append(", ");
encodeValue(code, obj);
if (it.hasNext()) {
code.add(", ");
}
}
str.append('}');
return str.toString();
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() + ")");
}
if (val instanceof Annotation) {
return formatAnnotation((Annotation) val).toString();
}
// TODO: also can be method values
throw new JadxRuntimeException("Can't decode value: " + val + " (" + val.getClass() + ")");
}
}
@@ -1,17 +1,18 @@
package jadx.core.codegen;
import jadx.api.IJadxArgs;
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.AttributeFlag;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.EnumClassAttr;
import jadx.core.dex.attributes.EnumClassAttr.EnumField;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.attributes.SourceFileAttr;
import jadx.core.dex.attributes.nodes.EnumClassAttr;
import jadx.core.dex.attributes.nodes.EnumClassAttr.EnumField;
import jadx.core.dex.attributes.nodes.SourceFileAttr;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.PrimitiveType;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.FieldNode;
@@ -23,6 +24,7 @@ import jadx.core.utils.exceptions.CodegenException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
@@ -38,12 +40,27 @@ import com.android.dx.rop.code.AccessFlags;
public class ClassGen {
private static final Logger LOG = LoggerFactory.getLogger(ClassGen.class);
public static final Comparator<MethodNode> METHOD_LINE_COMPARATOR = new Comparator<MethodNode>() {
@Override
public int compare(MethodNode a, MethodNode b) {
return Utils.compare(a.getSourceLine(), b.getSourceLine());
}
};
private final ClassNode cls;
private final ClassGen parentGen;
private final AnnotationGen annotationGen;
private final boolean fallback;
private boolean showInconsistentCode = false;
private final Set<ClassInfo> imports = new HashSet<ClassInfo>();
private int clsDeclLine;
public ClassGen(ClassNode cls, ClassGen parentClsGen, IJadxArgs jadxArgs) {
this(cls, parentClsGen, jadxArgs.isFallbackMode());
this.showInconsistentCode = jadxArgs.isShowInconsistentCode();
}
public ClassGen(ClassNode cls, ClassGen parentClsGen, boolean fallback) {
this.cls = cls;
@@ -62,12 +79,10 @@ public class ClassGen {
addClassCode(clsBody);
CodeWriter clsCode = new CodeWriter();
if (!"".equals(cls.getPackage())) {
clsCode.add("package ").add(cls.getPackage()).add(';');
clsCode.newLine();
}
int importsCount = imports.size();
if (importsCount != 0) {
List<String> sortImports = new ArrayList<String>(importsCount);
@@ -84,37 +99,43 @@ public class ClassGen {
sortImports.clear();
imports.clear();
}
clsCode.add(clsBody);
return clsCode;
}
public void addClassCode(CodeWriter code) throws CodegenException {
if (cls.getAttributes().contains(AttributeFlag.DONT_GENERATE))
if (cls.contains(AFlag.DONT_GENERATE)) {
return;
if (cls.getAttributes().contains(AttributeFlag.INCONSISTENT_CODE))
}
if (cls.contains(AFlag.INCONSISTENT_CODE)) {
code.startLine("// jadx: inconsistent code");
makeClassDeclaration(code);
makeClassBody(code);
code.newLine();
}
addClassDeclaration(code);
addClassBody(code);
}
public void makeClassDeclaration(CodeWriter clsCode) {
public void addClassDeclaration(CodeWriter clsCode) {
AccessInfo af = cls.getAccessFlags();
if (af.isInterface()) {
af = af.remove(AccessFlags.ACC_ABSTRACT);
} else if (af.isEnum()) {
af = af.remove(AccessFlags.ACC_FINAL).remove(AccessFlags.ACC_ABSTRACT);
af = af.remove(AccessFlags.ACC_FINAL)
.remove(AccessFlags.ACC_ABSTRACT)
.remove(AccessFlags.ACC_STATIC);
}
// 'static' modifier not allowed for top classes (not inner)
if (!cls.getClassInfo().isInner()) {
af = af.remove(AccessFlags.ACC_STATIC);
}
annotationGen.addForClass(clsCode);
insertSourceFileInfo(clsCode, cls);
clsCode.startLine(af.makeString());
if (af.isInterface()) {
if (af.isAnnotation())
if (af.isAnnotation()) {
clsCode.add('@');
}
clsCode.add("interface ");
} else if (af.isEnum()) {
clsCode.add("enum ");
@@ -123,39 +144,42 @@ public class ClassGen {
}
clsCode.add(cls.getShortName());
makeGenericMap(clsCode, cls.getGenericMap());
addGenericMap(clsCode, cls.getGenericMap());
clsCode.add(' ');
ClassInfo sup = cls.getSuperClass();
if (sup != null
&& !sup.getFullName().equals(Consts.CLASS_OBJECT)
&& !sup.getFullName().equals(Consts.CLASS_ENUM)) {
clsCode.add("extends ").add(useClass(sup)).add(' ');
clsCode.add("extends ");
useClass(clsCode, sup);
clsCode.add(' ');
}
if (cls.getInterfaces().size() > 0 && !af.isAnnotation()) {
if (cls.getAccessFlags().isInterface())
if (!cls.getInterfaces().isEmpty() && !af.isAnnotation()) {
if (cls.getAccessFlags().isInterface()) {
clsCode.add("extends ");
else
} else {
clsCode.add("implements ");
}
for (Iterator<ClassInfo> it = cls.getInterfaces().iterator(); it.hasNext(); ) {
ClassInfo interf = it.next();
clsCode.add(useClass(interf));
if (it.hasNext())
useClass(clsCode, interf);
if (it.hasNext()) {
clsCode.add(", ");
}
}
if (!cls.getInterfaces().isEmpty())
if (!cls.getInterfaces().isEmpty()) {
clsCode.add(' ');
}
}
clsCode.attachAnnotation(cls);
clsCode.attachDefinition(cls);
}
public boolean makeGenericMap(CodeWriter code, Map<ArgType, List<ArgType>> gmap) {
if (gmap == null || gmap.isEmpty())
public boolean addGenericMap(CodeWriter code, Map<ArgType, List<ArgType>> gmap) {
if (gmap == null || gmap.isEmpty()) {
return false;
}
code.add('<');
int i = 0;
for (Entry<ArgType, List<ArgType>> e : gmap.entrySet()) {
@@ -164,12 +188,20 @@ public class ClassGen {
if (i != 0) {
code.add(", ");
}
code.add(useClass(type));
if (type.isGenericType()) {
code.add(type.getObject());
} else {
useClass(code, ClassInfo.fromType(type));
}
if (list != null && !list.isEmpty()) {
code.add(" extends ");
for (Iterator<ArgType> it = list.iterator(); it.hasNext(); ) {
ArgType g = it.next();
code.add(useClass(g));
if (g.isGenericType()) {
code.add(g.getObject());
} else {
useClass(code, ClassInfo.fromType(g));
}
if (it.hasNext()) {
code.add(" & ");
}
@@ -181,182 +213,241 @@ public class ClassGen {
return true;
}
public void makeClassBody(CodeWriter clsCode) throws CodegenException {
public void addClassBody(CodeWriter clsCode) throws CodegenException {
clsCode.add('{');
CodeWriter mthsCode = makeMethods(clsCode, cls.getMethods());
CodeWriter fieldsCode = makeFields(clsCode, cls, cls.getFields());
clsCode.add(fieldsCode);
if (fieldsCode.notEmpty() && mthsCode.notEmpty())
clsCode.newLine();
// insert inner classes code
if (cls.getInnerClasses().size() != 0) {
clsCode.add(makeInnerClasses(cls, clsCode.getIndent()));
if (mthsCode.notEmpty())
clsCode.newLine();
}
clsCode.add(mthsCode);
clsDeclLine = clsCode.getLine();
clsCode.incIndent();
addFields(clsCode);
addInnerClasses(clsCode, cls);
addMethods(clsCode);
clsCode.decIndent();
clsCode.startLine('}');
}
private CodeWriter makeInnerClasses(ClassNode cls, int indent) throws CodegenException {
CodeWriter innerClsCode = new CodeWriter(indent + 1);
for (ClassNode inCls : cls.getInnerClasses()) {
if (inCls.isAnonymous())
private void addInnerClasses(CodeWriter code, ClassNode cls) throws CodegenException {
for (ClassNode innerCls : cls.getInnerClasses()) {
if (innerCls.contains(AFlag.DONT_GENERATE)
|| innerCls.isAnonymous()) {
continue;
ClassGen inClGen = new ClassGen(inCls, parentGen == null ? this : parentGen, fallback);
inClGen.addClassCode(innerClsCode);
}
ClassGen inClGen = new ClassGen(innerCls, getParentGen(), fallback);
code.newLine();
inClGen.addClassCode(code);
imports.addAll(inClGen.getImports());
}
return innerClsCode;
}
private CodeWriter makeMethods(CodeWriter clsCode, List<MethodNode> mthList) {
CodeWriter code = new CodeWriter(clsCode.getIndent() + 1);
for (Iterator<MethodNode> it = mthList.iterator(); it.hasNext(); ) {
MethodNode mth = it.next();
if (mth.getAttributes().contains(AttributeFlag.DONT_GENERATE))
private boolean isInnerClassesPresents() {
for (ClassNode innerCls : cls.getInnerClasses()) {
if (!innerCls.isAnonymous()) {
return true;
}
}
return false;
}
private void addMethods(CodeWriter code) {
List<MethodNode> methods = sortMethodsByLine(cls.getMethods());
for (MethodNode mth : methods) {
if (mth.contains(AFlag.DONT_GENERATE)) {
continue;
}
if (code.getLine() != clsDeclLine) {
code.newLine();
}
try {
if (mth.getAccessFlags().isAbstract() || mth.getAccessFlags().isNative()) {
MethodGen mthGen = new MethodGen(this, mth);
mthGen.addDefinition(code);
if (cls.getAccessFlags().isAnnotation()) {
Object def = annotationGen.getAnnotationDefaultValue(mth.getName());
if (def != null) {
String v = annotationGen.encValueToString(def);
code.add(" default ").add(v);
}
}
code.add(';');
} else {
if (mth.isNoCode())
continue;
MethodGen mthGen = new MethodGen(this, mth);
if (mth.getAttributes().contains(AttributeFlag.INCONSISTENT_CODE)) {
code.startLine("/* JADX WARNING: inconsistent code */");
LOG.error(ErrorsCounter.formatErrorMsg(mth, " Inconsistent code"));
mthGen.makeMethodDump(code);
}
if (mthGen.addDefinition(code)) {
code.add(' ');
}
code.add('{');
insertSourceFileInfo(code, mth);
code.add(mthGen.makeInstructions(code.getIndent()));
code.startLine('}');
}
} catch (Throwable e) {
addMethod(code, mth);
} catch (Exception e) {
String msg = ErrorsCounter.methodError(mth, "Method generation error", e);
code.startLine("/* " + msg + CodeWriter.NL + Utils.getStackTrace(e) + " */");
}
if (it.hasNext())
code.newLine();
}
return code;
}
private CodeWriter makeFields(CodeWriter clsCode, ClassNode cls, List<FieldNode> fields) throws CodegenException {
CodeWriter code = new CodeWriter(clsCode.getIndent() + 1);
private static List<MethodNode> sortMethodsByLine(List<MethodNode> methods) {
List<MethodNode> out = new ArrayList<MethodNode>(methods);
Collections.sort(out, METHOD_LINE_COMPARATOR);
return out;
}
EnumClassAttr enumFields = (EnumClassAttr) cls.getAttributes().get(AttributeType.ENUM_CLASS);
if (enumFields != null) {
InsnGen igen = null;
for (Iterator<EnumField> it = enumFields.getFields().iterator(); it.hasNext(); ) {
EnumField f = it.next();
code.startLine(f.getName());
if (f.getArgs().size() != 0) {
code.add('(');
for (Iterator<InsnArg> aIt = f.getArgs().iterator(); aIt.hasNext(); ) {
InsnArg arg = aIt.next();
if (igen == null) {
// don't init mth gen if this is simple enum
MethodGen mthGen = new MethodGen(this, enumFields.getStaticMethod());
igen = new InsnGen(mthGen, enumFields.getStaticMethod(), false);
}
code.add(igen.arg(arg));
if (aIt.hasNext())
code.add(", ");
}
code.add(')');
}
if (f.getCls() != null) {
new ClassGen(f.getCls(), this, fallback).makeClassBody(code);
}
if (it.hasNext())
code.add(',');
private boolean isMethodsPresents() {
for (MethodNode mth : cls.getMethods()) {
if (!mth.contains(AFlag.DONT_GENERATE)) {
return true;
}
if (enumFields.getFields().isEmpty())
code.startLine();
code.add(';');
code.newLine();
}
return false;
}
for (FieldNode f : fields) {
if(f.getAttributes().contains(AttributeFlag.DONT_GENERATE)) {
private void addMethod(CodeWriter code, MethodNode mth) throws CodegenException {
if (mth.getAccessFlags().isAbstract() || mth.getAccessFlags().isNative()) {
MethodGen mthGen = new MethodGen(this, mth);
mthGen.addDefinition(code);
if (cls.getAccessFlags().isAnnotation()) {
Object def = annotationGen.getAnnotationDefaultValue(mth.getName());
if (def != null) {
code.add(" default ");
annotationGen.encodeValue(code, def);
}
}
code.add(';');
} else {
boolean badCode = mth.contains(AFlag.INCONSISTENT_CODE);
if (badCode) {
code.startLine("/* JADX WARNING: inconsistent code. */");
code.startLine("/* Code decompiled incorrectly, please refer to instructions dump. */");
ErrorsCounter.methodError(mth, "Inconsistent code");
if (showInconsistentCode) {
mth.remove(AFlag.INCONSISTENT_CODE);
}
}
MethodGen mthGen;
if (badCode || mth.contains(AType.JADX_ERROR)) {
mthGen = MethodGen.getFallbackMethodGen(mth);
} else {
mthGen = new MethodGen(this, mth);
}
if (mthGen.addDefinition(code)) {
code.add(' ');
}
code.add('{');
code.incIndent();
insertSourceFileInfo(code, mth);
mthGen.addInstructions(code);
code.decIndent();
code.startLine('}');
}
}
private void addFields(CodeWriter code) throws CodegenException {
addEnumFields(code);
for (FieldNode f : cls.getFields()) {
if (f.contains(AFlag.DONT_GENERATE)) {
continue;
}
annotationGen.addForField(code, f);
code.startLine(f.getAccessFlags().makeString());
code.add(TypeGen.translate(this, f.getType()));
useType(code, f.getType());
code.add(' ');
code.add(f.getName());
FieldValueAttr fv = (FieldValueAttr) f.getAttributes().get(AttributeType.FIELD_VALUE);
FieldValueAttr fv = f.get(AType.FIELD_VALUE);
if (fv != null) {
code.add(" = ");
if (fv.getValue() == null) {
code.add(TypeGen.literalToString(0, f.getType()));
} else {
code.add(annotationGen.encValueToString(fv.getValue()));
annotationGen.encodeValue(code, fv.getValue());
}
}
code.add(';');
code.attachAnnotation(f);
code.attachDefinition(f);
}
return code;
}
public String useClass(ArgType clsType) {
if (clsType.isGenericType()) {
return clsType.getObject();
private boolean isFieldsPresents() {
for (FieldNode field : cls.getFields()) {
if (!field.contains(AFlag.DONT_GENERATE)) {
return true;
}
}
return useClass(ClassInfo.fromType(clsType));
return false;
}
public String useClass(ClassInfo classInfo) {
private void addEnumFields(CodeWriter code) throws CodegenException {
EnumClassAttr enumFields = cls.get(AType.ENUM_CLASS);
if (enumFields == null) {
return;
}
InsnGen igen = null;
for (Iterator<EnumField> it = enumFields.getFields().iterator(); it.hasNext(); ) {
EnumField f = it.next();
code.startLine(f.getName());
if (!f.getArgs().isEmpty()) {
code.add('(');
for (Iterator<InsnArg> aIt = f.getArgs().iterator(); aIt.hasNext(); ) {
InsnArg arg = aIt.next();
if (igen == null) {
// don't init mth gen if this is simple enum
MethodGen mthGen = new MethodGen(this, enumFields.getStaticMethod());
igen = new InsnGen(mthGen, false);
}
igen.addArg(code, arg);
if (aIt.hasNext()) {
code.add(", ");
}
}
code.add(')');
}
if (f.getCls() != null) {
code.add(' ');
new ClassGen(f.getCls(), this, fallback).addClassBody(code);
}
if (it.hasNext()) {
code.add(',');
}
}
if (isMethodsPresents() || isFieldsPresents() || isInnerClassesPresents()) {
if (enumFields.getFields().isEmpty()) {
code.startLine();
}
code.add(';');
}
}
public void useType(CodeWriter code, ArgType type) {
PrimitiveType stype = type.getPrimitiveType();
if (stype == null) {
code.add(type.toString());
} else if (stype == PrimitiveType.OBJECT) {
if (type.isGenericType()) {
code.add(type.getObject());
} else {
useClass(code, ClassInfo.fromType(type));
}
} else if (stype == PrimitiveType.ARRAY) {
useType(code, type.getArrayElement());
code.add("[]");
} else {
code.add(stype.getLongName());
}
}
public void useClass(CodeWriter code, ClassInfo classInfo) {
ClassNode classNode = cls.dex().resolveClass(classInfo);
if (classNode != null) {
code.attachAnnotation(classNode);
}
String baseClass = useClassInternal(cls.getClassInfo(), classInfo);
code.add(baseClass);
ArgType[] generics = classInfo.getType().getGenericTypes();
if (generics != null) {
StringBuilder sb = new StringBuilder();
sb.append(baseClass);
sb.append('<');
code.add('<');
int len = generics.length;
for (int i = 0; i < len; i++) {
if (i != 0) {
sb.append(", ");
code.add(", ");
}
ArgType gt = generics[i];
if (gt.isTypeKnown())
sb.append(TypeGen.translate(this, gt));
else
sb.append('?');
ArgType wt = gt.getWildcardType();
if (wt != null) {
code.add('?');
int bounds = gt.getWildcardBounds();
if (bounds != 0) {
code.add(bounds == -1 ? " super " : " extends ");
useType(code, wt);
}
} else {
useType(code, gt);
}
}
sb.append('>');
return sb.toString();
} else {
return baseClass;
code.add('>');
}
}
private String useClassInternal(ClassInfo useCls, ClassInfo classInfo) {
String clsStr = classInfo.getFullName();
String fullName = classInfo.getFullName();
if (fallback) {
return clsStr;
return fullName;
}
String shortName = classInfo.getShortName();
if (classInfo.getPackage().equals("java.lang") && classInfo.getParentClass() == null) {
@@ -370,16 +461,25 @@ public class ClassGen {
if (classInfo.getPackage().equals(useCls.getPackage()) && !classInfo.isInner()) {
return shortName;
}
// don't add import if class not public (must be accessed using inheritance)
ClassNode classNode = cls.dex().resolveClass(classInfo);
if (classNode != null && !classNode.getAccessFlags().isPublic()) {
return shortName;
}
if (searchCollision(cls.dex(), useCls, classInfo)) {
return fullName;
}
if (classInfo.getPackage().equals(useCls.getPackage())) {
clsStr = classInfo.getNameWithoutPackage();
fullName = classInfo.getNameWithoutPackage();
}
if (searchCollision(cls.dex(), useCls, shortName)) {
return clsStr;
}
for (ClassInfo cls : imports) {
if (!cls.equals(classInfo)) {
if (cls.getShortName().equals(shortName)) {
return clsStr;
for (ClassInfo importCls : getImports()) {
if (!importCls.equals(classInfo)
&& importCls.getShortName().equals(shortName)) {
if (classInfo.isInner()) {
String parent = useClassInternal(useCls, classInfo.getParentClass());
return parent + "." + shortName;
} else {
return fullName;
}
}
}
@@ -396,6 +496,14 @@ public class ClassGen {
}
}
private Set<ClassInfo> getImports() {
if (parentGen != null) {
return parentGen.getImports();
} else {
return imports;
}
}
private static boolean isClassInnerFor(ClassInfo inner, ClassInfo parent) {
if (inner.isInner()) {
ClassInfo p = inner.getParentClass();
@@ -404,38 +512,35 @@ public class ClassGen {
return false;
}
private static boolean searchCollision(DexNode dex, ClassInfo useCls, String shortName) {
private static boolean searchCollision(DexNode dex, ClassInfo useCls, ClassInfo searchCls) {
if (useCls == null) {
return false;
}
String shortName = searchCls.getShortName();
if (useCls.getShortName().equals(shortName)) {
return true;
}
ClassNode classNode = dex.resolveClass(useCls);
if (classNode != null) {
for (ClassNode inner : classNode.getInnerClasses()) {
if (inner.getShortName().equals(shortName)) {
if (inner.getShortName().equals(shortName)
&& !inner.getClassInfo().equals(searchCls)) {
return true;
}
}
}
return searchCollision(dex, useCls.getParentClass(), shortName);
return searchCollision(dex, useCls.getParentClass(), searchCls);
}
private void insertSourceFileInfo(CodeWriter code, AttrNode node) {
IAttribute sourceFileAttr = node.getAttributes().get(AttributeType.SOURCE_FILE);
SourceFileAttr sourceFileAttr = node.get(AType.SOURCE_FILE);
if (sourceFileAttr != null) {
code.startLine("// compiled from: ");
code.add(((SourceFileAttr) sourceFileAttr).getFileName());
code.startLine("// compiled from: ").add(sourceFileAttr.getFileName());
}
}
public Set<ClassInfo> getImports() {
return imports;
}
public ClassGen getParentGen() {
return parentGen;
return parentGen == null ? this : parentGen;
}
public AnnotationGen getAnnotationGen() {
@@ -15,15 +15,11 @@ public class CodeGen extends AbstractVisitor {
@Override
public boolean visit(ClassNode cls) throws CodegenException {
ClassGen clsGen = new ClassGen(cls, null, isFallbackMode());
ClassGen clsGen = new ClassGen(cls, null, args);
CodeWriter clsCode = clsGen.makeClass();
clsCode.finish();
cls.setCode(clsCode);
return false;
}
public boolean isFallbackMode() {
return args.isFallbackMode();
}
}
@@ -1,13 +1,16 @@
package jadx.core.codegen;
import jadx.core.dex.attributes.LineAttrNode;
import jadx.core.utils.Utils;
import jadx.api.CodePosition;
import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.utils.files.FileUtils;
import java.io.File;
import java.io.PrintWriter;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -17,105 +20,11 @@ public class CodeWriter {
private static final int MAX_FILENAME_LENGTH = 128;
public static final String NL = System.getProperty("line.separator");
private static final String INDENT = "\t";
public static final String INDENT = " ";
private final StringBuilder buf = new StringBuilder();
private String indentStr;
private int indent;
private static final boolean ADD_LINE_NUMBERS = false;
private int line = 1;
private Map<Object, Integer> annotations = Collections.emptyMap();
public CodeWriter() {
this.indent = 0;
this.indentStr = "";
}
public CodeWriter(int indent) {
this.indent = indent;
updateIndent();
}
public CodeWriter startLine() {
addLine();
buf.append(indentStr);
return this;
}
public CodeWriter startLine(char c) {
addLine();
buf.append(indentStr);
buf.append(c);
return this;
}
public CodeWriter startLine(String str) {
addLine();
buf.append(indentStr);
buf.append(str);
return this;
}
public CodeWriter startLine(int ind, String str) {
addLine();
buf.append(indentStr);
for (int i = 0; i < ind; i++)
buf.append(INDENT);
buf.append(str);
return this;
}
public CodeWriter add(String str) {
buf.append(str);
return this;
}
public CodeWriter add(char c) {
buf.append(c);
return this;
}
public CodeWriter add(CodeWriter code) {
line--;
for (Map.Entry<Object, Integer> entry : code.annotations.entrySet()) {
attachAnnotation(entry.getKey(), line + entry.getValue());
}
line += code.line;
buf.append(code.toString());
return this;
}
public CodeWriter newLine() {
addLine();
return this;
}
private void addLine() {
buf.append(NL);
line++;
}
public int getLine() {
return line;
}
public Object attachAnnotation(Object obj) {
return attachAnnotation(obj, line);
}
public Object attachAnnotation(Object obj, int line) {
if (annotations.isEmpty()) {
annotations = new HashMap<Object, Integer>();
}
return annotations.put(obj, line);
}
public CodeWriter indent() {
buf.append(indentStr);
return this;
}
private static final String[] INDENT_CACHE = new String[]{
private static final String[] INDENT_CACHE = {
"",
INDENT,
INDENT + INDENT,
@@ -124,9 +33,115 @@ public class CodeWriter {
INDENT + INDENT + INDENT + INDENT + INDENT,
};
private final StringBuilder buf = new StringBuilder();
private String indentStr;
private int indent;
private int line = 1;
private int offset = 0;
private Map<CodePosition, Object> annotations = Collections.emptyMap();
private Map<Integer, Integer> lineMap = Collections.emptyMap();
public CodeWriter() {
this.indent = 0;
this.indentStr = "";
if (ADD_LINE_NUMBERS) {
incIndent(2);
}
}
public CodeWriter startLine() {
addLine();
addLineIndent();
return this;
}
public CodeWriter startLine(char c) {
addLine();
addLineIndent();
add(c);
return this;
}
public CodeWriter startLine(String str) {
addLine();
addLineIndent();
add(str);
return this;
}
public CodeWriter startLineWithNum(int sourceLine) {
if (sourceLine == 0) {
startLine();
return this;
}
if (ADD_LINE_NUMBERS) {
newLine();
attachSourceLine(sourceLine);
String ln = "/* " + sourceLine + " */ ";
add(ln);
if (indentStr.length() > ln.length()) {
add(indentStr.substring(ln.length()));
}
} else {
startLine();
attachSourceLine(sourceLine);
}
return this;
}
public CodeWriter add(String str) {
buf.append(str);
offset += str.length();
return this;
}
public CodeWriter add(char c) {
buf.append(c);
offset++;
return this;
}
CodeWriter add(CodeWriter code) {
line--;
for (Map.Entry<CodePosition, Object> entry : code.annotations.entrySet()) {
CodePosition pos = entry.getKey();
attachAnnotation(entry.getValue(), new CodePosition(line + pos.getLine(), pos.getOffset()));
}
for (Map.Entry<Integer, Integer> entry : code.lineMap.entrySet()) {
attachSourceLine(line + entry.getKey(), entry.getValue());
}
line += code.line;
offset = code.offset;
buf.append(code);
return this;
}
public CodeWriter newLine() {
addLine();
return this;
}
public CodeWriter addIndent() {
add(INDENT);
return this;
}
private void addLine() {
buf.append(NL);
line++;
offset = 0;
}
private CodeWriter addLineIndent() {
buf.append(indentStr);
offset += indentStr.length();
return this;
}
private void updateIndent() {
int curIndent = indent;
if (curIndent < 6) {
if (curIndent < INDENT_CACHE.length) {
this.indentStr = INDENT_CACHE[curIndent];
} else {
StringBuilder s = new StringBuilder(curIndent * INDENT.length());
@@ -137,10 +152,6 @@ public class CodeWriter {
}
}
public int getIndent() {
return indent;
}
public void incIndent() {
incIndent(1);
}
@@ -163,24 +174,86 @@ public class CodeWriter {
updateIndent();
}
public int getLine() {
return line;
}
private static class DefinitionWrapper {
private final LineAttrNode node;
private DefinitionWrapper(LineAttrNode node) {
this.node = node;
}
public LineAttrNode getNode() {
return node;
}
}
public Object attachDefinition(LineAttrNode obj) {
return attachAnnotation(new DefinitionWrapper(obj), new CodePosition(line, offset));
}
public Object attachAnnotation(Object obj) {
return attachAnnotation(obj, new CodePosition(line, offset + 1));
}
private Object attachAnnotation(Object obj, CodePosition pos) {
if (annotations.isEmpty()) {
annotations = new HashMap<CodePosition, Object>();
}
return annotations.put(pos, obj);
}
public Map<CodePosition, Object> getAnnotations() {
return annotations;
}
public void attachSourceLine(int sourceLine) {
if (sourceLine == 0) {
return;
}
attachSourceLine(line, sourceLine);
}
private void attachSourceLine(int decompiledLine, int sourceLine) {
if (lineMap.isEmpty()) {
lineMap = new TreeMap<Integer, Integer>();
}
lineMap.put(decompiledLine, sourceLine);
}
public Map<Integer, Integer> getLineMapping() {
return lineMap;
}
public void finish() {
buf.trimToSize();
for (Map.Entry<Object, Integer> entry : annotations.entrySet()) {
Object v = entry.getKey();
if (v instanceof LineAttrNode) {
LineAttrNode l = (LineAttrNode) v;
l.setDecompiledLine(entry.getValue());
Iterator<Map.Entry<CodePosition, Object>> it = annotations.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<CodePosition, Object> entry = it.next();
Object v = entry.getValue();
if (v instanceof DefinitionWrapper) {
LineAttrNode l = ((DefinitionWrapper) v).getNode();
l.setDecompiledLine(entry.getKey().getLine());
it.remove();
}
}
annotations.clear();
}
private static String removeFirstEmptyLine(String str) {
if (str.startsWith(NL)) {
return str.substring(NL.length());
} else {
return str;
}
return str;
}
public int length() {
return buf.length();
}
public boolean isEmpty() {
return buf.length() == 0;
}
public boolean notEmpty() {
@@ -205,26 +278,44 @@ public class CodeWriter {
if (name.length() > MAX_FILENAME_LENGTH) {
int dotIndex = name.indexOf('.');
int cutAt = MAX_FILENAME_LENGTH - name.length() + dotIndex - 1;
if (cutAt <= 0)
if (cutAt <= 0) {
name = name.substring(0, MAX_FILENAME_LENGTH - 1);
else
} else {
name = name.substring(0, cutAt) + name.substring(dotIndex);
}
file = new File(file.getParentFile(), name);
}
PrintWriter out = null;
try {
Utils.makeDirsForFile(file);
FileUtils.makeDirsForFile(file);
out = new PrintWriter(file, "UTF-8");
String code = buf.toString();
code = removeFirstEmptyLine(code);
out.print(code);
out.println(code);
} catch (Exception e) {
LOG.error("Save file error", e);
} finally {
if (out != null)
if (out != null) {
out.close();
}
}
}
@Override
public int hashCode() {
return buf.toString().hashCode();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof CodeWriter)) {
return false;
}
CodeWriter that = (CodeWriter) o;
return buf.toString().equals(that.buf.toString());
}
}
@@ -0,0 +1,201 @@
package jadx.core.codegen;
import jadx.core.dex.instructions.ArithNode;
import jadx.core.dex.instructions.IfOp;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.InsnWrapArg;
import jadx.core.dex.instructions.args.LiteralArg;
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;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Queue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ConditionGen extends InsnGen {
private static final Logger LOG = LoggerFactory.getLogger(ConditionGen.class);
private static class CondStack {
private final Queue<IfCondition> stack = new LinkedList<IfCondition>();
public Queue<IfCondition> getStack() {
return stack;
}
public void push(IfCondition cond) {
stack.add(cond);
}
public IfCondition pop() {
return stack.poll();
}
}
public ConditionGen(InsnGen insnGen) {
super(insnGen.mgen, insnGen.fallback);
}
void add(CodeWriter code, IfCondition condition) throws CodegenException {
add(code, new CondStack(), condition);
}
void wrap(CodeWriter code, IfCondition condition) throws CodegenException {
wrap(code, new CondStack(), condition);
}
private void add(CodeWriter code, CondStack stack, IfCondition condition) throws CodegenException {
stack.push(condition);
switch (condition.getMode()) {
case COMPARE:
addCompare(code, stack, condition.getCompare());
break;
case TERNARY:
addTernary(code, stack, condition);
break;
case NOT:
addNot(code, stack, condition);
break;
case AND:
case OR:
addAndOr(code, stack, condition);
break;
default:
throw new JadxRuntimeException("Unknown condition mode: " + condition.getMode());
}
stack.pop();
}
private void wrap(CodeWriter code, CondStack stack, IfCondition cond) throws CodegenException {
boolean wrap = isWrapNeeded(cond);
if (wrap) {
code.add('(');
}
add(code, stack, cond);
if (wrap) {
code.add(')');
}
}
private void wrap(CodeWriter code, InsnArg firstArg) throws CodegenException {
boolean wrap = isArgWrapNeeded(firstArg);
if (wrap) {
code.add('(');
}
addArg(code, firstArg, false);
if (wrap) {
code.add(')');
}
}
private void addCompare(CodeWriter code, CondStack stack, Compare compare) throws CodegenException {
IfOp op = compare.getOp();
InsnArg firstArg = compare.getA();
InsnArg secondArg = compare.getB();
if (firstArg.getType().equals(ArgType.BOOLEAN)
&& secondArg.isLiteral()
&& secondArg.getType().equals(ArgType.BOOLEAN)) {
LiteralArg lit = (LiteralArg) secondArg;
if (lit.getLiteral() == 0) {
op = op.invert();
}
if (op == IfOp.EQ) {
// == true
if (stack.getStack().size() == 1) {
addArg(code, firstArg, false);
} else {
wrap(code, firstArg);
}
return;
} else if (op == IfOp.NE) {
// != true
code.add('!');
wrap(code, firstArg);
return;
}
LOG.warn(ErrorsCounter.formatErrorMsg(mth, "Unsupported boolean condition " + op.getSymbol()));
}
addArg(code, firstArg, isArgWrapNeeded(firstArg));
code.add(' ').add(op.getSymbol()).add(' ');
addArg(code, secondArg, isArgWrapNeeded(secondArg));
}
private void addTernary(CodeWriter code, CondStack stack, IfCondition condition) throws CodegenException {
add(code, stack, condition.first());
code.add(" ? ");
add(code, stack, condition.second());
code.add(" : ");
add(code, stack, condition.third());
}
private void addNot(CodeWriter code, CondStack stack, IfCondition condition) throws CodegenException {
code.add('!');
wrap(code, stack, condition.getArgs().get(0));
}
private void addAndOr(CodeWriter code, CondStack stack, IfCondition condition) throws CodegenException {
String mode = condition.getMode() == Mode.AND ? " && " : " || ";
Iterator<IfCondition> it = condition.getArgs().iterator();
while (it.hasNext()) {
wrap(code, stack, it.next());
if (it.hasNext()) {
code.add(mode);
}
}
}
private boolean isWrapNeeded(IfCondition condition) {
if (condition.isCompare()) {
return false;
}
if (condition.getMode() != Mode.NOT) {
return true;
}
return false;
}
private static boolean isArgWrapNeeded(InsnArg arg) {
if (!arg.isInsnWrap()) {
return false;
}
InsnNode insn = ((InsnWrapArg) arg).getWrapInsn();
InsnType insnType = insn.getType();
if (insnType == InsnType.ARITH) {
switch (((ArithNode) insn).getOp()) {
case ADD:
case SUB:
case MUL:
case DIV:
case REM:
return false;
}
} else {
switch (insnType) {
case INVOKE:
case SGET:
case IGET:
case AGET:
case CONST:
case ARRAY_LENGTH:
return false;
default:
return true;
}
}
return true;
}
}
File diff suppressed because it is too large Load Diff
@@ -1,19 +1,16 @@
package jadx.core.codegen;
import jadx.core.Consts;
import jadx.core.dex.attributes.AttributeFlag;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.AttributesList;
import jadx.core.dex.attributes.JadxErrorAttr;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.annotations.MethodParameters;
import jadx.core.dex.attributes.nodes.JadxErrorAttr;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.NamedArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.trycatch.CatchAttr;
import jadx.core.dex.visitors.DepthTraverser;
import jadx.core.dex.visitors.DepthTraversal;
import jadx.core.dex.visitors.FallbackModeVisitor;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.InsnUtils;
@@ -21,10 +18,8 @@ import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.DecodeException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -36,60 +31,63 @@ public class MethodGen {
private final MethodNode mth;
private final ClassGen classGen;
private final boolean fallback;
private final AnnotationGen annotationGen;
private final Set<String> varNames = new HashSet<String>();
private final NameGen nameGen;
public MethodGen(ClassGen classGen, MethodNode mth) {
this.mth = mth;
this.classGen = classGen;
this.fallback = classGen.isFallbackMode();
this.annotationGen = classGen.getAnnotationGen();
List<RegisterArg> args = mth.getArguments(true);
for (RegisterArg arg : args) {
varNames.add(makeArgName(arg));
}
this.nameGen = new NameGen(classGen.isFallbackMode());
}
public ClassGen getClassGen() {
return classGen;
}
public NameGen getNameGen() {
return nameGen;
}
public MethodNode getMethodNode() {
return mth;
}
public boolean addDefinition(CodeWriter code) {
if (mth.getMethodInfo().isClassInit()) {
code.startLine("static");
code.attachAnnotation(mth);
code.attachDefinition(mth);
return true;
}
if (mth.getAttributes().contains(AttributeFlag.ANONYMOUS_CONSTRUCTOR)) {
if (mth.contains(AFlag.ANONYMOUS_CONSTRUCTOR)) {
// don't add method name and arguments
code.startLine();
code.attachAnnotation(mth);
code.attachDefinition(mth);
return false;
}
annotationGen.addForMethod(code, mth);
AccessInfo clsAccFlags = mth.getParentClass().getAccessFlags();
AccessInfo ai = mth.getAccessFlags();
// don't add 'abstract' to methods in interface
// don't add 'abstract' and 'public' to methods in interface
if (clsAccFlags.isInterface()) {
ai = ai.remove(AccessFlags.ACC_ABSTRACT);
ai = ai.remove(AccessFlags.ACC_PUBLIC);
}
// don't add 'public' for annotations
if (clsAccFlags.isAnnotation()) {
ai = ai.remove(AccessFlags.ACC_PUBLIC);
}
code.startLine(ai.makeString());
code.startLineWithNum(mth.getSourceLine());
code.add(ai.makeString());
if (classGen.makeGenericMap(code, mth.getGenericMap())) {
if (classGen.addGenericMap(code, mth.getGenericMap())) {
code.add(' ');
}
if (mth.getAccessFlags().isConstructor()) {
code.add(classGen.getClassNode().getShortName()); // constructor
} else {
code.add(TypeGen.translate(classGen, mth.getReturnType()));
classGen.useType(code, mth.getReturnType());
code.add(' ');
code.add(mth.getName());
}
@@ -97,7 +95,7 @@ public class MethodGen {
List<RegisterArg> args = mth.getArguments(false);
if (mth.getMethodInfo().isConstructor()
&& mth.getParentClass().getAttributes().contains(AttributeType.ENUM_CLASS)) {
&& mth.getParentClass().contains(AType.ENUM_CLASS)) {
if (args.size() == 2) {
args.clear();
} else if (args.size() > 2) {
@@ -105,203 +103,123 @@ public class MethodGen {
} else {
LOG.warn(ErrorsCounter.formatErrorMsg(mth,
"Incorrect number of args for enum constructor: " + args.size()
+ " (expected >= 2)"));
+ " (expected >= 2)"
));
}
}
code.add(makeArguments(args));
code.add(")");
addMethodArguments(code, args);
code.add(')');
annotationGen.addThrows(mth, code);
code.attachAnnotation(mth);
code.attachDefinition(mth);
return true;
}
public CodeWriter makeArguments(List<RegisterArg> args) {
CodeWriter argsCode = new CodeWriter();
MethodParameters paramsAnnotation =
(MethodParameters) mth.getAttributes().get(AttributeType.ANNOTATION_MTH_PARAMETERS);
private void addMethodArguments(CodeWriter argsCode, List<RegisterArg> args) {
MethodParameters paramsAnnotation = mth.get(AType.ANNOTATION_MTH_PARAMETERS);
int i = 0;
for (Iterator<RegisterArg> it = args.iterator(); it.hasNext(); ) {
RegisterArg arg = it.next();
// add argument annotation
if (paramsAnnotation != null)
if (paramsAnnotation != null) {
annotationGen.addForParameter(argsCode, paramsAnnotation, i);
}
if (!it.hasNext() && mth.getAccessFlags().isVarArgs()) {
// change last array argument to varargs
ArgType type = arg.getType();
if (type.isArray()) {
ArgType elType = type.getArrayElement();
argsCode.add(TypeGen.translate(classGen, elType));
argsCode.add(" ...");
classGen.useType(argsCode, elType);
argsCode.add("...");
} else {
LOG.warn(ErrorsCounter.formatErrorMsg(mth, "Last argument in varargs method not array"));
argsCode.add(TypeGen.translate(classGen, arg.getType()));
classGen.useType(argsCode, arg.getType());
}
} else {
argsCode.add(TypeGen.translate(classGen, arg.getType()));
classGen.useType(argsCode, arg.getType());
}
argsCode.add(' ');
argsCode.add(makeArgName(arg));
argsCode.add(nameGen.assignArg(arg));
i++;
if (it.hasNext())
if (it.hasNext()) {
argsCode.add(", ");
}
}
return argsCode;
}
/**
* Make variable name for register,
* Name contains register number and
* variable type or name (if debug info available)
*/
public String makeArgName(RegisterArg arg) {
String name = arg.getTypedVar().getName();
String base = "r" + arg.getRegNum();
if (fallback) {
if (name != null)
return base + "_" + name;
else
return base;
public void addInstructions(CodeWriter code) throws CodegenException {
if (mth.contains(AType.JADX_ERROR)
|| mth.contains(AFlag.INCONSISTENT_CODE)
|| mth.getRegion() == null) {
code.startLine("throw new UnsupportedOperationException(\"Method not decompiled: ")
.add(mth.toString())
.add("\");");
if (mth.contains(AType.JADX_ERROR)) {
JadxErrorAttr err = mth.get(AType.JADX_ERROR);
code.startLine("/* JADX: method processing error */");
Throwable cause = err.getCause();
if (cause != null) {
code.newLine();
code.add("/*");
code.startLine("Error: ").add(Utils.getStackTrace(cause));
code.add("*/");
}
}
code.startLine("/*");
addFallbackMethodCode(code);
code.startLine("*/");
} else {
if (name != null) {
if (name.equals("this"))
return name;
else if (Consts.DEBUG)
return name + "_" + base;
else
return name;
} else {
ArgType type = arg.getType();
if (type.isPrimitive())
return base + type.getPrimitiveType().getShortName().toLowerCase();
else
return base + "_" + Utils.escape(TypeGen.translate(classGen, arg.getType()));
}
RegionGen regionGen = new RegionGen(this);
regionGen.makeRegion(code, mth.getRegion());
}
}
/**
* Put variable declaration and return variable name (used for assignments)
*
* @param arg register variable
* @return variable name
*/
public String assignArg(RegisterArg arg) {
String name = makeArgName(arg);
if (varNames.add(name) || fallback)
return name;
name = getUniqVarName(name);
arg.getTypedVar().setName(name);
return name;
}
public String assignNamedArg(NamedArg arg) {
String name = arg.getName();
if (varNames.add(name) || fallback)
return name;
name = getUniqVarName(name);
arg.setName(name);
return name;
}
private String getUniqVarName(String name) {
String r;
int i = 2;
do {
r = name + "_" + i;
i++;
} while (varNames.contains(r));
varNames.add(r);
return r;
}
public CodeWriter makeInstructions(int mthIndent) throws CodegenException {
CodeWriter code = new CodeWriter(mthIndent + 1);
if (mth.getAttributes().contains(AttributeType.JADX_ERROR)) {
code.startLine("throw new UnsupportedOperationException(\"Method not decompiled: ");
code.add(mth.toString());
code.add("\");");
JadxErrorAttr err = (JadxErrorAttr) mth.getAttributes().get(AttributeType.JADX_ERROR);
code.startLine("// jadx: method processing error");
Throwable cause = err.getCause();
if (cause != null) {
code.newLine();
code.add("/*");
code.startLine("Error: ").add(Utils.getStackTrace(cause));
code.add("*/");
}
makeMethodDump(code);
} else {
if (mth.getRegion() != null) {
CodeWriter insns = new CodeWriter(mthIndent + 1);
(new RegionGen(this, mth)).makeRegion(insns, mth.getRegion());
code.add(insns);
} else {
makeFallbackMethod(code, mth);
}
}
return code;
}
public void makeMethodDump(CodeWriter code) {
code.startLine("/*");
getFallbackMethodGen(mth).addDefinition(code);
code.add(" {");
code.incIndent();
makeFallbackMethod(code, mth);
code.decIndent();
code.startLine('}');
code.startLine("*/");
}
private void makeFallbackMethod(CodeWriter code, MethodNode mth) {
public void addFallbackMethodCode(CodeWriter code) {
if (mth.getInstructions() == null) {
// loadFile original instructions
// load original instructions
try {
mth.load();
DepthTraverser.visit(new FallbackModeVisitor(), mth);
DepthTraversal.visit(new FallbackModeVisitor(), mth);
} catch (DecodeException e) {
// ignore
code.startLine("Can't loadFile method instructions");
LOG.error("Error reload instructions in fallback mode:", e);
code.startLine("// Can't loadFile method instructions: " + e.getMessage());
return;
}
}
if (mth.getThisArg() != null) {
code.startLine(getFallbackMethodGen(mth).makeArgName(mth.getThisArg())).add(" = this;");
InsnNode[] insnArr = mth.getInstructions();
if (insnArr == null) {
code.startLine("// Can't load method instructions.");
return;
}
makeFallbackInsns(code, mth, mth.getInstructions(), true);
if (mth.getThisArg() != null) {
code.startLine(nameGen.useArg(mth.getThisArg())).add(" = this;");
}
addFallbackInsns(code, mth, insnArr, true);
}
public static void makeFallbackInsns(CodeWriter code, MethodNode mth, List<InsnNode> insns, boolean addLabels) {
InsnGen insnGen = new InsnGen(getFallbackMethodGen(mth), mth, true);
for (InsnNode insn : insns) {
AttributesList attrs = insn.getAttributes();
if (addLabels) {
if (attrs.contains(AttributeType.JUMP)
|| attrs.contains(AttributeType.EXC_HANDLER)) {
code.decIndent();
code.startLine(getLabelName(insn.getOffset()) + ":");
code.incIndent();
}
public static void addFallbackInsns(CodeWriter code, MethodNode mth, InsnNode[] insnArr, boolean addLabels) {
InsnGen insnGen = new InsnGen(getFallbackMethodGen(mth), true);
for (InsnNode insn : insnArr) {
if (insn == null) {
continue;
}
if (addLabels && (insn.contains(AType.JUMP) || insn.contains(AType.EXC_HANDLER))) {
code.decIndent();
code.startLine(getLabelName(insn.getOffset()) + ":");
code.incIndent();
}
try {
if (insnGen.makeInsn(insn, code)) {
CatchAttr catchAttr = (CatchAttr) attrs.get(AttributeType.CATCH_BLOCK);
if (catchAttr != null)
code.add("\t //" + catchAttr);
CatchAttr catchAttr = insn.get(AType.CATCH_BLOCK);
if (catchAttr != null) {
code.add("\t " + catchAttr);
}
}
} catch (CodegenException e) {
LOG.debug("Error generate fallback instruction: ", e.getCause());
code.startLine("// error: " + insn);
}
}
@@ -310,7 +228,7 @@ public class MethodGen {
/**
* Return fallback variant of method codegen
*/
private static MethodGen getFallbackMethodGen(MethodNode mth) {
static MethodGen getFallbackMethodGen(MethodNode mth) {
ClassGen clsGen = new ClassGen(mth.getParentClass(), null, true);
return new MethodGen(clsGen, mth);
}
@@ -0,0 +1,230 @@
package jadx.core.codegen;
import jadx.core.Consts;
import jadx.core.deobf.NameMapper;
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.instructions.InvokeNode;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.InsnWrapArg;
import jadx.core.dex.instructions.args.NamedArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.instructions.mods.ConstructorInsn;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.Utils;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class NameGen {
private static final Map<String, String> OBJ_ALIAS;
private final Set<String> varNames = new HashSet<String>();
private final boolean fallback;
static {
OBJ_ALIAS = new HashMap<String, String>();
OBJ_ALIAS.put(Consts.CLASS_STRING, "str");
OBJ_ALIAS.put(Consts.CLASS_CLASS, "cls");
OBJ_ALIAS.put(Consts.CLASS_THROWABLE, "th");
OBJ_ALIAS.put(Consts.CLASS_OBJECT, "obj");
OBJ_ALIAS.put("java.util.Iterator", "it");
OBJ_ALIAS.put("java.lang.Boolean", "bool");
OBJ_ALIAS.put("java.lang.Short", "sh");
OBJ_ALIAS.put("java.lang.Integer", "num");
OBJ_ALIAS.put("java.lang.Character", "ch");
OBJ_ALIAS.put("java.lang.Byte", "b");
OBJ_ALIAS.put("java.lang.Float", "f");
OBJ_ALIAS.put("java.lang.Long", "l");
OBJ_ALIAS.put("java.lang.Double", "d");
}
public NameGen(boolean fallback) {
this.fallback = fallback;
}
public String assignArg(RegisterArg arg) {
String name = makeArgName(arg);
if (fallback) {
return name;
}
name = getUniqueVarName(name);
arg.setName(name);
return name;
}
public String assignNamedArg(NamedArg arg) {
String name = arg.getName();
if (fallback) {
return name;
}
name = getUniqueVarName(name);
arg.setName(name);
return name;
}
public String useArg(RegisterArg arg) {
String name = arg.getName();
if (name == null) {
return getFallbackName(arg);
}
return name;
}
// TODO: avoid name collision with variables names
public String getLoopLabel(LoopLabelAttr attr) {
String name = "loop" + attr.getLoop().getId();
varNames.add(name);
return name;
}
private String getUniqueVarName(String name) {
String r = name;
int i = 2;
while (varNames.contains(r)) {
r = name + i;
i++;
}
varNames.add(r);
return r;
}
private String makeArgName(RegisterArg arg) {
if (fallback) {
return getFallbackName(arg);
}
String name = arg.getName();
String varName;
if (name != null) {
if ("this".equals(name)) {
return name;
}
varName = name;
} else {
varName = makeNameForType(arg.getType());
}
if (NameMapper.isReserved(varName)) {
return varName + "R";
}
return varName;
}
private String getFallbackName(RegisterArg arg) {
String name = arg.getName();
String base = "r" + arg.getRegNum();
if (name != null && !name.equals("this")) {
return base + "_" + name;
}
return base;
}
private static String makeNameForType(ArgType type) {
if (type.isPrimitive()) {
return makeNameForPrimitive(type);
} else if (type.isArray()) {
return makeNameForType(type.getArrayRootElement()) + "Arr";
} else {
return makeNameForObject(type);
}
}
private static String makeNameForPrimitive(ArgType type) {
return type.getPrimitiveType().getShortName().toLowerCase();
}
private static String makeNameForObject(ArgType type) {
if (type.isObject()) {
String alias = getAliasForObject(type.getObject());
if (alias != null) {
return alias;
}
ClassInfo clsInfo = ClassInfo.fromType(type);
String shortName = clsInfo.getShortName();
String vName = fromName(shortName);
if (vName != null) {
return vName;
}
}
return Utils.escape(type.toString());
}
private static String fromName(String name) {
if (name == null || name.isEmpty()) {
return null;
}
if (name.toUpperCase().equals(name)) {
// all characters are upper case
return name.toLowerCase();
}
String v1 = Character.toLowerCase(name.charAt(0)) + name.substring(1);
if (!v1.equals(name)) {
return v1;
}
if (name.length() < 3) {
return name + "Var";
}
return null;
}
public static void guessName(RegisterArg arg) {
SSAVar sVar = arg.getSVar();
if (sVar == null || sVar.getName() != null) {
return;
}
RegisterArg assignArg = sVar.getAssign();
InsnNode assignInsn = assignArg.getParentInsn();
String name = makeNameFromInsn(assignInsn);
if (name != null && !NameMapper.isReserved(name)) {
assignArg.setName(name);
}
}
private static String getAliasForObject(String name) {
return OBJ_ALIAS.get(name);
}
private static String makeNameFromInsn(InsnNode insn) {
switch (insn.getType()) {
case INVOKE:
InvokeNode inv = (InvokeNode) insn;
String name = inv.getCallMth().getName();
if (name.startsWith("get") || name.startsWith("set")) {
return fromName(name.substring(3));
}
if ("iterator".equals(name)) {
return "it";
}
return name;
case CONSTRUCTOR:
ConstructorInsn co = (ConstructorInsn) insn;
return makeNameForObject(co.getClassType().getType());
case ARRAY_LENGTH:
return "length";
case ARITH:
case TERNARY:
case CAST:
for (InsnArg arg : insn.getArguments()) {
if (arg.isInsnWrap()) {
InsnNode wrapInsn = ((InsnWrapArg) arg).getWrapInsn();
String wName = makeNameFromInsn(wrapInsn);
if (wName != null) {
return wName;
}
}
}
break;
default:
break;
}
return null;
}
}
@@ -1,39 +1,38 @@
package jadx.core.codegen;
import jadx.core.dex.attributes.AttributeFlag;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.DeclareVariableAttr;
import jadx.core.dex.attributes.ForceReturnAttr;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
import jadx.core.dex.attributes.nodes.ForceReturnAttr;
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.ArithNode;
import jadx.core.dex.instructions.IfOp;
import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.SwitchNode;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.InsnWrapArg;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.NamedArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.IBlock;
import jadx.core.dex.nodes.IContainer;
import jadx.core.dex.nodes.IRegion;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.regions.IfCondition;
import jadx.core.dex.regions.IfRegion;
import jadx.core.dex.regions.LoopRegion;
import jadx.core.dex.regions.Region;
import jadx.core.dex.regions.SwitchRegion;
import jadx.core.dex.regions.SynchronizedRegion;
import jadx.core.dex.trycatch.CatchAttr;
import jadx.core.dex.regions.TryCatchRegion;
import jadx.core.dex.regions.conditions.IfCondition;
import jadx.core.dex.regions.conditions.IfRegion;
import jadx.core.dex.regions.loops.ForEachLoop;
import jadx.core.dex.regions.loops.ForLoop;
import jadx.core.dex.regions.loops.LoopRegion;
import jadx.core.dex.regions.loops.LoopType;
import jadx.core.dex.trycatch.ExceptionHandler;
import jadx.core.dex.trycatch.TryCatchBlock;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.RegionUtils;
import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import java.util.List;
@@ -43,51 +42,53 @@ import org.slf4j.LoggerFactory;
public class RegionGen extends InsnGen {
private static final Logger LOG = LoggerFactory.getLogger(RegionGen.class);
public RegionGen(MethodGen mgen, MethodNode mth) {
super(mgen, mth, false);
public RegionGen(MethodGen mgen) {
super(mgen, false);
}
public void makeRegion(CodeWriter code, IContainer cont) throws CodegenException {
assert cont != null;
if (cont instanceof IBlock) {
makeSimpleBlock((IBlock) cont, code);
} else if (cont instanceof IRegion) {
declareVars(code, cont);
if (cont instanceof Region) {
Region r = (Region) cont;
CatchAttr tc = (CatchAttr) r.getAttributes().get(AttributeType.CATCH_BLOCK);
if (tc != null) {
makeTryCatch(cont, tc.getTryBlock(), code);
} else {
for (IContainer c : r.getSubBlocks())
makeRegion(code, c);
makeSimpleRegion(code, (Region) cont);
} else {
declareVars(code, cont);
if (cont instanceof IfRegion) {
makeIf((IfRegion) cont, code, true);
} else if (cont instanceof SwitchRegion) {
makeSwitch((SwitchRegion) cont, code);
} else if (cont instanceof LoopRegion) {
makeLoop((LoopRegion) cont, code);
} else if (cont instanceof TryCatchRegion) {
makeTryCatch((TryCatchRegion) cont, code);
} else if (cont instanceof SynchronizedRegion) {
makeSynchronizedRegion((SynchronizedRegion) cont, code);
}
} else if (cont instanceof IfRegion) {
code.startLine();
makeIf((IfRegion) cont, code);
} else if (cont instanceof SwitchRegion) {
makeSwitch((SwitchRegion) cont, code);
} else if (cont instanceof LoopRegion) {
makeLoop((LoopRegion) cont, code);
} else if (cont instanceof SynchronizedRegion) {
makeSynchronizedRegion((SynchronizedRegion) cont, code);
}
} else {
throw new CodegenException("Not processed container: " + cont.toString());
throw new CodegenException("Not processed container: " + cont);
}
}
private void declareVars(CodeWriter code, IContainer cont) {
DeclareVariableAttr declVars =
(DeclareVariableAttr) cont.getAttributes().get(AttributeType.DECLARE_VARIABLE);
DeclareVariablesAttr declVars = cont.get(AType.DECLARE_VARIABLES);
if (declVars != null) {
for (RegisterArg v : declVars.getVars()) {
code.startLine(declareVar(v)).add(';');
code.startLine();
declareVar(code, v);
code.add(';');
}
}
}
private void makeSimpleRegion(CodeWriter code, Region region) throws CodegenException {
declareVars(code, region);
for (IContainer c : region.getSubBlocks()) {
makeRegion(code, c);
}
}
public void makeRegionIndent(CodeWriter code, IContainer region) throws CodegenException {
code.incIndent();
makeRegion(code, region);
@@ -96,57 +97,76 @@ public class RegionGen extends InsnGen {
private void makeSimpleBlock(IBlock block, CodeWriter code) throws CodegenException {
for (InsnNode insn : block.getInstructions()) {
makeInsn(insn, code);
}
if (block.getAttributes().contains(AttributeFlag.BREAK)) {
code.startLine("break;");
} else {
IAttribute attr;
if ((attr = block.getAttributes().get(AttributeType.FORCE_RETURN)) != null) {
ForceReturnAttr retAttr = (ForceReturnAttr) attr;
makeInsn(retAttr.getReturnInsn(), code);
if (!insn.contains(AFlag.SKIP)) {
makeInsn(insn, code);
}
}
ForceReturnAttr retAttr = block.get(AType.FORCE_RETURN);
if (retAttr != null) {
makeInsn(retAttr.getReturnInsn(), code);
}
}
private void makeIf(IfRegion region, CodeWriter code) throws CodegenException {
code.add("if (").add(makeCondition(region.getCondition())).add(") {");
private void makeIf(IfRegion region, CodeWriter code, boolean newLine) throws CodegenException {
if (newLine) {
code.startLineWithNum(region.getSourceLine());
} else {
code.attachSourceLine(region.getSourceLine());
}
code.add("if (");
new ConditionGen(this).add(code, region.getCondition());
code.add(") {");
makeRegionIndent(code, region.getThenRegion());
code.startLine('}');
IContainer els = region.getElseRegion();
if (els != null && RegionUtils.notEmpty(els)) {
code.add(" else ");
// connect if-else-if block
if (els instanceof Region) {
Region re = (Region) els;
List<IContainer> subBlocks = re.getSubBlocks();
if (subBlocks.size() == 1 && subBlocks.get(0) instanceof IfRegion) {
makeIf((IfRegion) subBlocks.get(0), code);
return;
}
if (connectElseIf(code, els)) {
return;
}
code.add('{');
makeRegionIndent(code, els);
code.startLine('}');
}
}
/**
* Connect if-else-if block
*/
private boolean connectElseIf(CodeWriter code, IContainer els) throws CodegenException {
if (!els.contains(AFlag.ELSE_IF_CHAIN)) {
return false;
}
if (!(els instanceof Region)) {
return false;
}
List<IContainer> subBlocks = ((Region) els).getSubBlocks();
if (subBlocks.size() == 1
&& subBlocks.get(0) instanceof IfRegion) {
makeIf((IfRegion) subBlocks.get(0), code, false);
return true;
}
return false;
}
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) {
// write not inlined instructions from header
mth.getAttributes().add(AttributeFlag.INCONSISTENT_CODE);
for (int i = 0; i < headerInsns.size() - 1; i++) {
ErrorsCounter.methodError(mth, "Found not inlined instructions from loop header");
int last = headerInsns.size() - 1;
for (int i = 0; i < last; i++) {
InsnNode insn = headerInsns.get(i);
makeInsn(insn, code);
}
}
}
LoopLabelAttr labelAttr = region.getInfo().getStart().get(AType.LOOP_LABEL);
if (labelAttr != null) {
code.startLine(mgen.getNameGen().getLoopLabel(labelAttr)).add(':');
}
IfCondition condition = region.getCondition();
if (condition == null) {
@@ -156,13 +176,45 @@ public class RegionGen extends InsnGen {
code.startLine('}');
return code;
}
ConditionGen conditionGen = new ConditionGen(this);
LoopType type = region.getType();
if (type != null) {
if (type instanceof ForLoop) {
ForLoop forLoop = (ForLoop) type;
code.startLine("for (");
makeInsn(forLoop.getInitInsn(), code, Flags.INLINE);
code.add("; ");
conditionGen.add(code, condition);
code.add("; ");
makeInsn(forLoop.getIncrInsn(), code, Flags.INLINE);
code.add(") {");
makeRegionIndent(code, region.getBody());
code.startLine('}');
return code;
}
if (type instanceof ForEachLoop) {
ForEachLoop forEachLoop = (ForEachLoop) type;
code.startLine("for (");
declareVar(code, forEachLoop.getVarArg());
code.add(" : ");
addArg(code, forEachLoop.getIterableArg(), false);
code.add(") {");
makeRegionIndent(code, region.getBody());
code.startLine('}');
return code;
}
throw new JadxRuntimeException("Unknown loop type: " + type.getClass());
}
if (region.isConditionAtEnd()) {
code.startLine("do {");
makeRegionIndent(code, region.getBody());
code.startLine("} while (").add(makeCondition(condition)).add(");");
code.startLine("} while (");
conditionGen.add(code, condition);
code.add(");");
} else {
code.startLine("while (").add(makeCondition(condition)).add(") {");
code.startLine("while (");
conditionGen.add(code, condition);
code.add(") {");
makeRegionIndent(code, region.getBody());
code.startLine('}');
}
@@ -170,84 +222,20 @@ public class RegionGen extends InsnGen {
}
private void makeSynchronizedRegion(SynchronizedRegion cont, CodeWriter code) throws CodegenException {
code.startLine("synchronized(").add(arg(cont.getInsn().getArg(0))).add(") {");
code.startLine("synchronized (");
addArg(code, cont.getEnterInsn().getArg(0));
code.add(") {");
makeRegionIndent(code, cont.getRegion());
code.startLine('}');
}
private String makeCondition(IfCondition condition) throws CodegenException {
switch (condition.getMode()) {
case COMPARE:
return makeCompare(condition.getCompare());
case NOT:
return "!" + makeCondition(condition.getArgs().get(0));
case AND:
case OR:
String mode = condition.getMode() == IfCondition.Mode.AND ? " && " : " || ";
StringBuilder sb = new StringBuilder();
for (IfCondition arg : condition.getArgs()) {
if (sb.length() != 0) {
sb.append(mode);
}
String s = makeCondition(arg);
if (arg.isCompare()) {
sb.append(s);
} else {
sb.append('(').append(s).append(')');
}
}
return sb.toString();
default:
return "??" + condition.toString();
}
}
private String makeCompare(IfCondition.Compare compare) throws CodegenException {
IfOp op = compare.getOp();
InsnArg firstArg = compare.getA();
InsnArg secondArg = compare.getB();
if (firstArg.getType().equals(ArgType.BOOLEAN)
&& secondArg.isLiteral()
&& secondArg.getType().equals(ArgType.BOOLEAN)) {
LiteralArg lit = (LiteralArg) secondArg;
if (lit.getLiteral() == 0) {
op = op.invert();
}
if (op == IfOp.EQ) {
return arg(firstArg, false); // == true
} else if (op == IfOp.NE) {
return "!" + arg(firstArg); // != true
}
LOG.warn(ErrorsCounter.formatErrorMsg(mth, "Unsupported boolean condition " + op.getSymbol()));
}
return arg(firstArg, isWrapNeeded(firstArg))
+ " " + op.getSymbol() + " "
+ arg(secondArg, isWrapNeeded(secondArg));
}
private boolean isWrapNeeded(InsnArg arg) {
if (!arg.isInsnWrap()) {
return false;
}
InsnNode insn = ((InsnWrapArg) arg).getWrapInsn();
if (insn.getType() == InsnType.ARITH) {
ArithNode arith = ((ArithNode) insn);
switch (arith.getOp()) {
case ADD:
case SUB:
case MUL:
case DIV:
case REM:
return false;
}
}
return true;
}
private CodeWriter makeSwitch(SwitchRegion sw, CodeWriter code) throws CodegenException {
SwitchNode insn = (SwitchNode) sw.getHeader().getInstructions().get(0);
InsnArg arg = insn.getArg(0);
code.startLine("switch(").add(arg(arg)).add(") {");
code.startLine("switch (");
addArg(code, arg, false);
code.add(") {");
code.incIndent();
int size = sw.getKeys().size();
for (int i = 0; i < size; i++) {
@@ -255,10 +243,16 @@ public class RegionGen extends InsnGen {
IContainer c = sw.getCases().get(i);
for (Object k : keys) {
code.startLine("case ");
if (k instanceof IndexInsnNode) {
code.add(sfield((FieldInfo) ((IndexInsnNode) k).getIndex()));
}
else {
if (k instanceof FieldNode) {
FieldNode fn = (FieldNode) k;
if (fn.getParentClass().isEnum()) {
code.add(fn.getName());
} else {
staticField(code, fn.getFieldInfo());
}
} else if (k instanceof IndexInsnNode) {
staticField(code, (FieldInfo) ((IndexInsnNode) k).getIndex());
} else {
code.add(TypeGen.literalToString((Integer) k, arg.getType()));
}
code.add(':');
@@ -269,57 +263,69 @@ public class RegionGen extends InsnGen {
code.startLine("default:");
makeCaseBlock(sw.getDefaultCase(), code);
}
code.decIndent();
code.startLine('}');
return code;
}
private void makeCaseBlock(IContainer c, CodeWriter code) throws CodegenException {
boolean addBreak = true;
if (RegionUtils.notEmpty(c)) {
makeRegionIndent(code, c);
if (RegionUtils.hasExitEdge(c)) {
code.startLine(1, "break;");
if (!RegionUtils.hasExitEdge(c)) {
addBreak = false;
}
} else {
code.startLine(1, "break;");
}
if (addBreak) {
code.startLine().addIndent().add("break;");
}
}
private void makeTryCatch(IContainer region, TryCatchBlock tryCatchBlock, CodeWriter code)
throws CodegenException {
private void makeTryCatch(TryCatchRegion region, CodeWriter code) throws CodegenException {
TryCatchBlock tryCatchBlock = region.geTryCatchBlock();
code.startLine("try {");
region.getAttributes().remove(AttributeType.CATCH_BLOCK);
makeRegionIndent(code, region);
makeRegionIndent(code, region.getTryRegion());
// TODO: move search of 'allHandler' to 'TryCatchRegion'
ExceptionHandler allHandler = null;
for (ExceptionHandler handler : tryCatchBlock.getHandlers()) {
if (!handler.isCatchAll()) {
makeCatchBlock(code, handler);
} else {
if (allHandler != null)
LOG.warn("Several 'all' handlers in try/catch block in " + mth);
if (allHandler != null) {
LOG.warn("Several 'all' handlers in try/catch block in {}", mth);
}
allHandler = handler;
}
}
if (allHandler != null) {
makeCatchBlock(code, allHandler);
}
if (tryCatchBlock.getFinalBlock() != null) {
if (tryCatchBlock.getFinalRegion() != null) {
code.startLine("} finally {");
makeRegionIndent(code, tryCatchBlock.getFinalBlock());
makeRegionIndent(code, tryCatchBlock.getFinalRegion());
}
code.startLine('}');
}
private void makeCatchBlock(CodeWriter code, ExceptionHandler handler)
throws CodegenException {
private void makeCatchBlock(CodeWriter code, ExceptionHandler handler) throws CodegenException {
IContainer region = handler.getHandlerRegion();
if (region != null /* && RegionUtils.notEmpty(region) */) {
code.startLine("} catch (");
code.add(handler.isCatchAll() ? "Throwable" : useClass(handler.getCatchType()));
code.add(' ');
code.add(mgen.assignNamedArg(handler.getArg()));
code.add(") {");
makeRegionIndent(code, region);
if (region == null) {
return;
}
code.startLine("} catch (");
InsnArg arg = handler.getArg();
if (arg instanceof RegisterArg) {
declareVar(code, (RegisterArg) arg);
} else if (arg instanceof NamedArg) {
if (handler.isCatchAll()) {
code.add("Throwable");
} else {
useClass(code, handler.getCatchType());
}
code.add(' ');
code.add(mgen.getNameGen().assignNamedArg((NamedArg) arg));
}
code.add(") {");
makeRegionIndent(code, region);
}
}
@@ -6,24 +6,17 @@ import jadx.core.utils.StringUtils;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TypeGen {
private static final Logger LOG = LoggerFactory.getLogger(TypeGen.class);
public static String translate(ClassGen clsGen, ArgType type) {
final PrimitiveType stype = type.getPrimitiveType();
if (stype == null)
return type.toString();
if (stype == PrimitiveType.OBJECT) {
return clsGen.useClass(type);
}
if (stype == PrimitiveType.ARRAY) {
return translate(clsGen, type.getArrayElement()) + "[]";
}
return stype.getLongName();
private TypeGen() {
}
public static String signature(ArgType type) {
final PrimitiveType stype = type.getPrimitiveType();
PrimitiveType stype = type.getPrimitiveType();
if (stype == PrimitiveType.OBJECT) {
return Utils.makeQualifiedObjectName(type.getObject());
}
@@ -69,8 +62,10 @@ public class TypeGen {
case OBJECT:
case ARRAY:
if (lit != 0)
throw new JadxRuntimeException("Wrong object literal: " + type + " = " + lit);
if (lit != 0) {
LOG.warn("Wrong object literal: " + lit + " for type: " + type);
return Long.toString(lit);
}
return "null";
default:
@@ -79,36 +74,91 @@ public class TypeGen {
}
public static String formatShort(short s) {
return "(short) " + wrapNegNum(s < 0, Short.toString(s));
if (s == Short.MAX_VALUE) {
return "Short.MAX_VALUE";
}
if (s == Short.MIN_VALUE) {
return "Short.MIN_VALUE";
}
return "(short) " + Short.toString(s);
}
public static String formatByte(byte b) {
return "(byte) " + wrapNegNum(b < 0, Byte.toString(b));
if (b == Byte.MAX_VALUE) {
return "Byte.MAX_VALUE";
}
if (b == Byte.MIN_VALUE) {
return "Byte.MIN_VALUE";
}
return "(byte) " + Byte.toString(b);
}
public static String formatInteger(int i) {
return wrapNegNum(i < 0, Integer.toString(i));
if (i == Integer.MAX_VALUE) {
return "Integer.MAX_VALUE";
}
if (i == Integer.MIN_VALUE) {
return "Integer.MIN_VALUE";
}
return Integer.toString(i);
}
public static String formatLong(long l) {
if (l == Long.MAX_VALUE) {
return "Long.MAX_VALUE";
}
if (l == Long.MIN_VALUE) {
return "Long.MIN_VALUE";
}
String str = Long.toString(l);
if (Math.abs(l) >= Integer.MAX_VALUE) {
str += "L";
}
return str;
}
public static String formatDouble(double d) {
return wrapNegNum(d < 0, Double.toString(d) + "d");
if (Double.isNaN(d)) {
return "Double.NaN";
}
if (d == Double.NEGATIVE_INFINITY) {
return "Double.NEGATIVE_INFINITY";
}
if (d == Double.POSITIVE_INFINITY) {
return "Double.POSITIVE_INFINITY";
}
if (d == Double.MIN_VALUE) {
return "Double.MIN_VALUE";
}
if (d == Double.MAX_VALUE) {
return "Double.MAX_VALUE";
}
if (d == Double.MIN_NORMAL) {
return "Double.MIN_NORMAL";
}
return Double.toString(d) + "d";
}
public static String formatFloat(float f) {
return wrapNegNum(f < 0, Float.toString(f) + "f");
if (Float.isNaN(f)) {
return "Float.NaN";
}
if (f == Float.NEGATIVE_INFINITY) {
return "Float.NEGATIVE_INFINITY";
}
if (f == Float.POSITIVE_INFINITY) {
return "Float.POSITIVE_INFINITY";
}
if (f == Float.MIN_VALUE) {
return "Float.MIN_VALUE";
}
if (f == Float.MAX_VALUE) {
return "Float.MAX_VALUE";
}
if (f == Float.MIN_NORMAL) {
return "Float.MIN_NORMAL";
}
return Float.toString(f) + "f";
}
public static String formatLong(long lit) {
String l = Long.toString(lit);
if (lit == Long.MIN_VALUE || Math.abs(lit) >= Integer.MAX_VALUE)
l += "L";
return wrapNegNum(lit < 0, l);
}
private static String wrapNegNum(boolean lz, String str) {
// if (lz)
// return "(" + str + ")";
// else
return str;
}
}
@@ -61,7 +61,8 @@ public class NameMapper {
"void",
"volatile",
"while",
}));
})
);
public static boolean isReserved(String str) {
return RESERVED_NAMES.contains(str);
@@ -1,6 +1,6 @@
package jadx.core.dex.attributes;
public enum AttributeFlag {
public enum AFlag {
TRY_ENTER,
TRY_LEAVE,
@@ -9,15 +9,23 @@ public enum AttributeFlag {
SYNTHETIC,
BREAK,
RETURN, // block contains only return instruction
DECLARE_VAR,
DONT_WRAP,
DONT_SHRINK,
DONT_INLINE,
DONT_GENERATE,
SKIP,
SKIP_FIRST_ARG,
ANONYMOUS_CONSTRUCTOR,
ELSE_IF_CHAIN,
WRAPPED,
ARITH_ONEARG,
INCONSISTENT_CODE, // warning about incorrect decompilation
}
@@ -0,0 +1,54 @@
package jadx.core.dex.attributes;
import jadx.core.dex.attributes.annotations.AnnotationsList;
import jadx.core.dex.attributes.annotations.MethodParameters;
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
import jadx.core.dex.attributes.nodes.EnumClassAttr;
import jadx.core.dex.attributes.nodes.EnumMapAttr;
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
import jadx.core.dex.attributes.nodes.ForceReturnAttr;
import jadx.core.dex.attributes.nodes.JadxErrorAttr;
import jadx.core.dex.attributes.nodes.JumpInfo;
import jadx.core.dex.attributes.nodes.LoopInfo;
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
import jadx.core.dex.attributes.nodes.MethodInlineAttr;
import jadx.core.dex.attributes.nodes.PhiListAttr;
import jadx.core.dex.attributes.nodes.SourceFileAttr;
import jadx.core.dex.nodes.parser.FieldValueAttr;
import jadx.core.dex.trycatch.CatchAttr;
import jadx.core.dex.trycatch.ExcHandlerAttr;
import jadx.core.dex.trycatch.SplitterBlockAttr;
/**
* Attribute types enumeration,
* uses generic type for omit cast after in 'AttributeStorage.get' method
*
* @param <T> attribute class implementation
*/
public class AType<T extends IAttribute> {
private AType() {
}
public static final int FIELDS_COUNT = 18;
public static final AType<AttrList<JumpInfo>> JUMP = new AType<AttrList<JumpInfo>>();
public static final AType<AttrList<LoopInfo>> LOOP = new AType<AttrList<LoopInfo>>();
public static final AType<ExcHandlerAttr> EXC_HANDLER = new AType<ExcHandlerAttr>();
public static final AType<CatchAttr> CATCH_BLOCK = new AType<CatchAttr>();
public static final AType<SplitterBlockAttr> SPLITTER_BLOCK = new AType<SplitterBlockAttr>();
public static final AType<ForceReturnAttr> FORCE_RETURN = new AType<ForceReturnAttr>();
public static final AType<FieldValueAttr> FIELD_VALUE = new AType<FieldValueAttr>();
public static final AType<FieldReplaceAttr> FIELD_REPLACE = new AType<FieldReplaceAttr>();
public static final AType<JadxErrorAttr> JADX_ERROR = new AType<JadxErrorAttr>();
public static final AType<MethodInlineAttr> METHOD_INLINE = new AType<MethodInlineAttr>();
public static final AType<EnumClassAttr> ENUM_CLASS = new AType<EnumClassAttr>();
public static final AType<EnumMapAttr> ENUM_MAP = new AType<EnumMapAttr>();
public static final AType<AnnotationsList> ANNOTATION_LIST = new AType<AnnotationsList>();
public static final AType<MethodParameters> ANNOTATION_MTH_PARAMETERS = new AType<MethodParameters>();
public static final AType<PhiListAttr> PHI_LIST = new AType<PhiListAttr>();
public static final AType<SourceFileAttr> SOURCE_FILE = new AType<SourceFileAttr>();
public static final AType<DeclareVariablesAttr> DECLARE_VARIABLES = new AType<DeclareVariablesAttr>();
public static final AType<LoopLabelAttr> LOOP_LABEL = new AType<LoopLabelAttr>();
}
@@ -0,0 +1,30 @@
package jadx.core.dex.attributes;
import jadx.core.utils.Utils;
import java.util.LinkedList;
import java.util.List;
public class AttrList<T> implements IAttribute {
private final AType<AttrList<T>> type;
private final List<T> list = new LinkedList<T>();
public AttrList(AType<AttrList<T>> type) {
this.type = type;
}
public List<T> getList() {
return list;
}
@Override
public AType<AttrList<T>> getType() {
return type;
}
@Override
public String toString() {
return Utils.listToString(list);
}
}
@@ -1,14 +1,96 @@
package jadx.core.dex.attributes;
import jadx.core.dex.attributes.annotations.Annotation;
import java.util.List;
public abstract class AttrNode implements IAttributeNode {
private AttributesList attributesList;
private static final AttributeStorage EMPTY_ATTR_STORAGE = new EmptyAttrStorage();
private AttributeStorage storage = EMPTY_ATTR_STORAGE;
@Override
public AttributesList getAttributes() {
if (attributesList == null)
attributesList = new AttributesList();
return attributesList;
public void add(AFlag flag) {
initStorage().add(flag);
}
@Override
public void addAttr(IAttribute attr) {
initStorage().add(attr);
}
@Override
public <T> void addAttr(AType<AttrList<T>> type, T obj) {
initStorage().add(type, obj);
}
@Override
public void copyAttributesFrom(AttrNode attrNode) {
initStorage().addAll(attrNode.storage);
}
AttributeStorage initStorage() {
AttributeStorage store = storage;
if (store == EMPTY_ATTR_STORAGE) {
store = new AttributeStorage();
storage = store;
}
return store;
}
@Override
public boolean contains(AFlag flag) {
return storage.contains(flag);
}
@Override
public <T extends IAttribute> boolean contains(AType<T> type) {
return storage.contains(type);
}
@Override
public <T extends IAttribute> T get(AType<T> type) {
return storage.get(type);
}
@Override
public Annotation getAnnotation(String cls) {
return storage.getAnnotation(cls);
}
@Override
public <T> List<T> getAll(AType<AttrList<T>> type) {
return storage.getAll(type);
}
@Override
public void remove(AFlag flag) {
storage.remove(flag);
}
@Override
public <T extends IAttribute> void remove(AType<T> type) {
storage.remove(type);
}
@Override
public void removeAttr(IAttribute attr) {
storage.remove(attr);
}
@Override
public void clearAttributes() {
storage.clear();
}
@Override
public List<String> getAttributesStringsList() {
return storage.getAttributeStrings();
}
@Override
public String getAttributesString() {
return storage.toString();
}
}
@@ -0,0 +1,122 @@
package jadx.core.dex.attributes;
import jadx.core.dex.attributes.annotations.Annotation;
import jadx.core.dex.attributes.annotations.AnnotationsList;
import jadx.core.utils.Utils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Storage for different attribute types:
* 1. flags - boolean attribute (set or not)
* 2. attribute - class instance associated with attribute type.
*/
public class AttributeStorage {
private final Set<AFlag> flags;
private final Map<AType<?>, IAttribute> attributes;
public AttributeStorage() {
flags = EnumSet.noneOf(AFlag.class);
attributes = new IdentityHashMap<AType<?>, IAttribute>(AType.FIELDS_COUNT);
}
public void add(AFlag flag) {
flags.add(flag);
}
public void add(IAttribute attr) {
attributes.put(attr.getType(), attr);
}
public <T> void add(AType<AttrList<T>> type, T obj) {
AttrList<T> list = get(type);
if (list == null) {
list = new AttrList<T>(type);
add(list);
}
list.getList().add(obj);
}
public void addAll(AttributeStorage otherList) {
flags.addAll(otherList.flags);
attributes.putAll(otherList.attributes);
}
public boolean contains(AFlag flag) {
return flags.contains(flag);
}
public <T extends IAttribute> boolean contains(AType<T> type) {
return attributes.containsKey(type);
}
@SuppressWarnings("unchecked")
public <T extends IAttribute> T get(AType<T> type) {
return (T) attributes.get(type);
}
public Annotation getAnnotation(String cls) {
AnnotationsList aList = get(AType.ANNOTATION_LIST);
return aList == null ? null : aList.get(cls);
}
public <T> List<T> getAll(AType<AttrList<T>> type) {
AttrList<T> attrList = get(type);
if (attrList == null) {
return Collections.emptyList();
}
return Collections.unmodifiableList(attrList.getList());
}
public void remove(AFlag flag) {
flags.remove(flag);
}
public <T extends IAttribute> void remove(AType<T> type) {
attributes.remove(type);
}
public void remove(IAttribute attr) {
AType<?> type = attr.getType();
IAttribute a = attributes.get(type);
if (a == attr) {
attributes.remove(type);
}
}
public void clear() {
flags.clear();
attributes.clear();
}
public List<String> getAttributeStrings() {
int size = flags.size() + attributes.size() + attributes.size();
if (size == 0) {
return Collections.emptyList();
}
List<String> list = new ArrayList<String>(size);
for (AFlag a : flags) {
list.add(a.toString());
}
for (IAttribute a : attributes.values()) {
list.add(a.toString());
}
return list;
}
@Override
public String toString() {
List<String> list = getAttributeStrings();
if (list.isEmpty()) {
return "";
}
return "A:{" + Utils.listToString(list) + "}";
}
}
@@ -1,62 +0,0 @@
package jadx.core.dex.attributes;
public enum AttributeType {
/* Multi attributes */
JUMP(false),
LOOP(false),
CATCH_BLOCK(false),
/* Uniq attributes */
EXC_HANDLER(true),
SPLITTER_BLOCK(true),
FORCE_RETURN(true),
FIELD_VALUE(true),
JADX_ERROR(true),
METHOD_INLINE(true),
ENUM_CLASS(true),
ANNOTATION_LIST(true),
ANNOTATION_MTH_PARAMETERS(true),
SOURCE_FILE(true),
DECLARE_VARIABLE(true);
private static final int NOT_UNIQ_COUNT;
private final boolean uniq;
static {
// place all not unique attributes at first
int last = -1;
AttributeType[] vals = AttributeType.values();
for (int i = 0; i < vals.length; i++) {
AttributeType type = vals[i];
if (type.notUniq())
last = i;
}
NOT_UNIQ_COUNT = last + 1;
}
public static int getNotUniqCount() {
return NOT_UNIQ_COUNT;
}
private AttributeType(boolean isUniq) {
this.uniq = isUniq;
}
public boolean isUniq() {
return uniq;
}
public boolean notUniq() {
return !uniq;
}
}
@@ -1,192 +0,0 @@
package jadx.core.dex.attributes;
import jadx.core.dex.attributes.annotations.Annotation;
import jadx.core.dex.attributes.annotations.AnnotationsList;
import jadx.core.utils.Utils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Storage for different attribute types:
* 1. flags - boolean attribute (set or not)
* 2. attribute - class instance associated for attribute type,
* only one attached to node for unique attributes, multiple for others
*/
public final class AttributesList {
private final Set<AttributeFlag> flags;
private final Map<AttributeType, IAttribute> uniqAttr;
private final List<IAttribute> attributes;
private final int[] attrCount;
public AttributesList() {
flags = EnumSet.noneOf(AttributeFlag.class);
uniqAttr = new EnumMap<AttributeType, IAttribute>(AttributeType.class);
attributes = new ArrayList<IAttribute>(0);
attrCount = new int[AttributeType.getNotUniqCount()];
}
// Flags
public void add(AttributeFlag flag) {
flags.add(flag);
}
public boolean contains(AttributeFlag flag) {
return flags.contains(flag);
}
public void remove(AttributeFlag flag) {
flags.remove(flag);
}
// Attributes
public void add(IAttribute attr) {
if (attr.getType().isUniq())
uniqAttr.put(attr.getType(), attr);
else
addMultiAttribute(attr);
}
private void addMultiAttribute(IAttribute attr) {
attributes.add(attr);
attrCount[attr.getType().ordinal()]++;
}
private int getMultiCountInternal(AttributeType type) {
return attrCount[type.ordinal()];
}
public void addAll(AttributesList otherList) {
flags.addAll(otherList.flags);
uniqAttr.putAll(otherList.uniqAttr);
for (IAttribute attr : otherList.attributes)
addMultiAttribute(attr);
}
public boolean contains(AttributeType type) {
if (type.isUniq())
return uniqAttr.containsKey(type);
else
return getMultiCountInternal(type) != 0;
}
public IAttribute get(AttributeType type) {
if (type.isUniq()) {
return uniqAttr.get(type);
} else {
if (getMultiCountInternal(type) != 0) {
for (IAttribute attr : attributes)
if (attr.getType() == type)
return attr;
}
return null;
}
}
public int getCount(AttributeType type) {
if (type.isUniq()) {
return uniqAttr.containsKey(type) ? 1 : 0;
} else {
return getMultiCountInternal(type);
}
}
public Annotation getAnnotation(String cls) {
AnnotationsList aList = (AnnotationsList) get(AttributeType.ANNOTATION_LIST);
if (aList == null || aList.size() == 0)
return null;
return aList.get(cls);
}
public List<IAttribute> getAll(AttributeType type) {
assert type.notUniq();
int count = getMultiCountInternal(type);
if (count == 0) {
return Collections.emptyList();
} else {
List<IAttribute> attrs = new ArrayList<IAttribute>(count);
for (IAttribute attr : attributes) {
if (attr.getType() == type)
attrs.add(attr);
}
return attrs;
}
}
public void remove(AttributeType type) {
if (type.isUniq()) {
uniqAttr.remove(type);
} else {
for (Iterator<IAttribute> it = attributes.iterator(); it.hasNext(); ) {
IAttribute attr = it.next();
if (attr.getType() == type)
it.remove();
}
attrCount[type.ordinal()] = 0;
}
}
public void remove(IAttribute attr) {
AttributeType type = attr.getType();
if (type.isUniq()) {
IAttribute a = uniqAttr.get(type);
if (a == attr)
uniqAttr.remove(type);
} else {
if (getMultiCountInternal(type) == 0)
return;
for (Iterator<IAttribute> it = attributes.iterator(); it.hasNext(); ) {
IAttribute a = it.next();
if (a == attr) {
it.remove();
attrCount[type.ordinal()]--;
}
}
}
}
public void clear() {
flags.clear();
uniqAttr.clear();
attributes.clear();
Arrays.fill(attrCount, 0);
}
public List<String> getAttributeStrings() {
int size = flags.size() + uniqAttr.size() + attributes.size();
if (size == 0)
return Collections.emptyList();
List<String> list = new ArrayList<String>(size);
for (AttributeFlag a : flags)
list.add(a.toString());
for (IAttribute a : uniqAttr.values())
list.add(a.toString());
for (IAttribute a : attributes)
list.add(a.toString());
return list;
}
@Override
public String toString() {
List<String> list = getAttributeStrings();
if (list.isEmpty())
return "";
return "A:{" + Utils.listToString(list) + "}";
}
}
@@ -1,55 +0,0 @@
package jadx.core.dex.attributes;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.TypedVar;
import jadx.core.dex.nodes.MethodNode;
public final class BlockRegState {
private final RegisterArg[] regs;
public BlockRegState(MethodNode mth) {
this.regs = new RegisterArg[mth.getRegsCount()];
for (int i = 0; i < regs.length; i++) {
regs[i] = new RegisterArg(i);
}
}
public BlockRegState(BlockRegState state) {
this.regs = new RegisterArg[state.regs.length];
System.arraycopy(state.regs, 0, regs, 0, state.regs.length);
}
public void assignReg(RegisterArg arg) {
regs[arg.getRegNum()] = arg;
arg.getTypedVar().getUseList().add(arg);
}
public void use(RegisterArg arg) {
RegisterArg reg = regs[arg.getRegNum()];
TypedVar regType = reg.getTypedVar();
if (regType == null) {
regType = new TypedVar(arg.getType());
reg.forceSetTypedVar(regType);
}
arg.replaceTypedVar(reg);
reg.getTypedVar().getUseList().add(arg);
}
public RegisterArg getRegister(int r) {
return regs[r];
}
@Override
public String toString() {
StringBuilder str = new StringBuilder();
for (RegisterArg reg : regs) {
if (reg.getTypedVar() != null) {
if (str.length() != 0)
str.append(", ");
str.append(reg.toString());
}
}
return str.toString();
}
}
@@ -1,42 +0,0 @@
package jadx.core.dex.attributes;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.utils.Utils;
import java.util.List;
public class DeclareVariableAttr implements IAttribute {
private final List<RegisterArg> vars;
public DeclareVariableAttr() {
this.vars = null; // for instruction use result
}
public DeclareVariableAttr(List<RegisterArg> vars) {
this.vars = vars; // for regions
}
public List<RegisterArg> getVars() {
return vars;
}
public void addVar(RegisterArg arg) {
int i;
if ((i = vars.indexOf(arg)) != -1) {
if (vars.get(i).getType().equals(arg.getType()))
return;
}
vars.add(arg);
}
@Override
public AttributeType getType() {
return AttributeType.DECLARE_VARIABLE;
}
@Override
public String toString() {
return "DECL_VAR: " + Utils.listToString(vars);
}
}
@@ -0,0 +1,60 @@
package jadx.core.dex.attributes;
import jadx.core.dex.attributes.annotations.Annotation;
import java.util.Collections;
import java.util.List;
public final class EmptyAttrStorage extends AttributeStorage {
@Override
public boolean contains(AFlag flag) {
return false;
}
@Override
public <T extends IAttribute> boolean contains(AType<T> type) {
return false;
}
@Override
public <T extends IAttribute> T get(AType<T> type) {
return null;
}
@Override
public Annotation getAnnotation(String cls) {
return null;
}
@Override
public <T> List<T> getAll(AType<AttrList<T>> type) {
return Collections.emptyList();
}
@Override
public void clear() {
}
@Override
public void remove(AFlag flag) {
}
@Override
public <T extends IAttribute> void remove(AType<T> type) {
}
@Override
public void remove(IAttribute attr) {
}
@Override
public List<String> getAttributeStrings() {
return Collections.emptyList();
}
@Override
public String toString() {
return "";
}
}
@@ -2,6 +2,6 @@ package jadx.core.dex.attributes;
public interface IAttribute {
AttributeType getType();
AType<?> getType();
}
@@ -1,7 +1,38 @@
package jadx.core.dex.attributes;
import jadx.core.dex.attributes.annotations.Annotation;
import java.util.List;
public interface IAttributeNode {
AttributesList getAttributes();
void add(AFlag flag);
void addAttr(IAttribute attr);
<T> void addAttr(AType<AttrList<T>> type, T obj);
void copyAttributesFrom(AttrNode attrNode);
boolean contains(AFlag flag);
<T extends IAttribute> boolean contains(AType<T> type);
<T extends IAttribute> T get(AType<T> type);
Annotation getAnnotation(String cls);
<T> List<T> getAll(AType<AttrList<T>> type);
void remove(AFlag flag);
<T extends IAttribute> void remove(AType<T> type);
void removeAttr(IAttribute attr);
void clearAttributes();
List<String> getAttributesStringsList();
String getAttributesString();
}
@@ -1,50 +0,0 @@
package jadx.core.dex.attributes;
import jadx.core.utils.InsnUtils;
public class JumpAttribute implements IAttribute {
private final int src;
private final int dest;
public JumpAttribute(int src, int dest) {
this.src = src;
this.dest = dest;
}
@Override
public AttributeType getType() {
return AttributeType.JUMP;
}
public int getSrc() {
return src;
}
public int getDest() {
return dest;
}
@Override
public String toString() {
return "JUMP: " + InsnUtils.formatOffset(src) + " -> " + InsnUtils.formatOffset(dest);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + dest;
result = prime * result + src;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
JumpAttribute other = (JumpAttribute) obj;
return dest == other.dest && src == other.src;
}
}
@@ -1,59 +0,0 @@
package jadx.core.dex.attributes;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.utils.BlockUtils;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
public class LoopAttr implements IAttribute {
private final BlockNode start;
private final BlockNode end;
private final Set<BlockNode> loopBlocks;
public LoopAttr(BlockNode start, BlockNode end) {
this.start = start;
this.end = end;
this.loopBlocks = Collections.unmodifiableSet(BlockUtils.getAllPathsBlocks(start, end));
}
public BlockNode getStart() {
return start;
}
public BlockNode getEnd() {
return end;
}
@Override
public AttributeType getType() {
return AttributeType.LOOP;
}
public Set<BlockNode> getLoopBlocks() {
return loopBlocks;
}
/**
* Return block nodes with exit edges from loop <br>
* Exit nodes belongs to loop (contains in {@code loopBlocks})
*/
public Set<BlockNode> getExitNodes() {
Set<BlockNode> nodes = new HashSet<BlockNode>();
Set<BlockNode> inloop = getLoopBlocks();
for (BlockNode block : inloop) {
// exit: successor node not from this loop, (don't change to getCleanSuccessors)
for (BlockNode s : block.getSuccessors())
if (!inloop.contains(s) && !s.getAttributes().contains(AttributeType.EXC_HANDLER))
nodes.add(block);
}
return nodes;
}
@Override
public String toString() {
return "LOOP: " + start + "->" + end;
}
}
@@ -6,7 +6,7 @@ import java.util.Map;
public class Annotation {
public static enum Visibility {
public enum Visibility {
BUILD, RUNTIME, SYSTEM
}
@@ -1,6 +1,6 @@
package jadx.core.dex.attributes.annotations;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.utils.Utils;
@@ -32,9 +32,13 @@ public class AnnotationsList implements IAttribute {
return map.size();
}
public boolean isEmpty() {
return map.isEmpty();
}
@Override
public AttributeType getType() {
return AttributeType.ANNOTATION_LIST;
public AType<AnnotationsList> getType() {
return AType.ANNOTATION_LIST;
}
@Override
@@ -1,6 +1,6 @@
package jadx.core.dex.attributes.annotations;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.utils.Utils;
@@ -20,8 +20,8 @@ public class MethodParameters implements IAttribute {
}
@Override
public AttributeType getType() {
return AttributeType.ANNOTATION_MTH_PARAMETERS;
public AType<MethodParameters> getType() {
return AType.ANNOTATION_MTH_PARAMETERS;
}
@Override
@@ -0,0 +1,35 @@
package jadx.core.dex.attributes.nodes;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.utils.Utils;
import java.util.LinkedList;
import java.util.List;
/**
* List of variables to be declared at region start.
*/
public class DeclareVariablesAttr implements IAttribute {
private final List<RegisterArg> vars = new LinkedList<RegisterArg>();
public Iterable<RegisterArg> getVars() {
return vars;
}
public void addVar(RegisterArg arg) {
vars.add(arg);
}
@Override
public AType<DeclareVariablesAttr> getType() {
return AType.DECLARE_VARIABLES;
}
@Override
public String toString() {
return "DECL_VAR: " + Utils.listToString(vars);
}
}
@@ -1,5 +1,7 @@
package jadx.core.dex.attributes;
package jadx.core.dex.attributes.nodes;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.MethodNode;
@@ -18,10 +20,11 @@ public class EnumClassAttr implements IAttribute {
public EnumField(String name, int argsCount) {
this.name = name;
if (argsCount != 0)
if (argsCount != 0) {
this.args = new ArrayList<InsnArg>(argsCount);
else
} else {
this.args = Collections.emptyList();
}
}
public String getName() {
@@ -50,7 +53,7 @@ public class EnumClassAttr implements IAttribute {
private MethodNode staticMethod;
public EnumClassAttr(int fieldsCount) {
this.fields = new ArrayList<EnumClassAttr.EnumField>(fieldsCount);
this.fields = new ArrayList<EnumField>(fieldsCount);
}
public List<EnumField> getFields() {
@@ -66,8 +69,8 @@ public class EnumClassAttr implements IAttribute {
}
@Override
public AttributeType getType() {
return AttributeType.ENUM_CLASS;
public AType<EnumClassAttr> getType() {
return AType.ENUM_CLASS;
}
@Override
@@ -0,0 +1,49 @@
package jadx.core.dex.attributes.nodes;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.nodes.FieldNode;
import java.util.HashMap;
import java.util.Map;
public class EnumMapAttr implements IAttribute {
public static class KeyValueMap {
private Map<Object, Object> map = new HashMap<Object, Object>();
public Object get(Object key) {
return map.get(key);
}
void put(Object key, Object value) {
map.put(key, value);
}
}
private Map<FieldNode, KeyValueMap> fieldsMap = new HashMap<FieldNode, KeyValueMap>();
public KeyValueMap getMap(FieldNode field) {
return fieldsMap.get(field);
}
public void add(FieldNode field, Object key, Object value) {
KeyValueMap map = getMap(field);
if (map == null) {
map = new KeyValueMap();
fieldsMap.put(field, map);
}
map.put(key, value);
}
@Override
public AType<EnumMapAttr> getType() {
return AType.ENUM_MAP;
}
@Override
public String toString() {
return "Enum fields map: " + fieldsMap;
}
}
@@ -0,0 +1,34 @@
package jadx.core.dex.attributes.nodes;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.info.FieldInfo;
public class FieldReplaceAttr implements IAttribute {
private final FieldInfo fieldInfo;
private final boolean isOuterClass;
public FieldReplaceAttr(FieldInfo fieldInfo, boolean isOuterClass) {
this.fieldInfo = fieldInfo;
this.isOuterClass = isOuterClass;
}
public FieldInfo getFieldInfo() {
return fieldInfo;
}
public boolean isOuterClass() {
return isOuterClass;
}
@Override
public AType<FieldReplaceAttr> getType() {
return AType.FIELD_REPLACE;
}
@Override
public String toString() {
return "REPLACE: " + fieldInfo;
}
}
@@ -1,5 +1,7 @@
package jadx.core.dex.attributes;
package jadx.core.dex.attributes.nodes;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.Utils;
@@ -16,8 +18,8 @@ public class ForceReturnAttr implements IAttribute {
}
@Override
public AttributeType getType() {
return AttributeType.FORCE_RETURN;
public AType<ForceReturnAttr> getType() {
return AType.FORCE_RETURN;
}
@Override
@@ -1,5 +1,7 @@
package jadx.core.dex.attributes;
package jadx.core.dex.attributes.nodes;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.utils.Utils;
public class JadxErrorAttr implements IAttribute {
@@ -15,8 +17,8 @@ public class JadxErrorAttr implements IAttribute {
}
@Override
public AttributeType getType() {
return AttributeType.JADX_ERROR;
public AType<JadxErrorAttr> getType() {
return AType.JADX_ERROR;
}
@Override
@@ -26,7 +28,7 @@ public class JadxErrorAttr implements IAttribute {
if (cause == null) {
str.append("null");
} else {
str.append(cause.getClass().toString());
str.append(cause.getClass());
str.append(":");
str.append(cause.getMessage());
str.append("\n");
@@ -0,0 +1,47 @@
package jadx.core.dex.attributes.nodes;
import jadx.core.utils.InsnUtils;
public class JumpInfo {
private final int src;
private final int dest;
public JumpInfo(int src, int dest) {
this.src = src;
this.dest = dest;
}
public int getSrc() {
return src;
}
public int getDest() {
return dest;
}
@Override
public int hashCode() {
return 31 * dest + src;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
JumpInfo other = (JumpInfo) obj;
return dest == other.dest && src == other.src;
}
@Override
public String toString() {
return "JUMP: " + InsnUtils.formatOffset(src) + " -> " + InsnUtils.formatOffset(dest);
}
}
@@ -1,4 +1,6 @@
package jadx.core.dex.attributes;
package jadx.core.dex.attributes.nodes;
import jadx.core.dex.attributes.AttrNode;
public abstract class LineAttrNode extends AttrNode {
@@ -0,0 +1,95 @@
package jadx.core.dex.attributes.nodes;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.Edge;
import jadx.core.utils.BlockUtils;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
public class LoopInfo {
private final BlockNode start;
private final BlockNode end;
private final Set<BlockNode> loopBlocks;
private int id;
private LoopInfo parentLoop;
public LoopInfo(BlockNode start, BlockNode end) {
this.start = start;
this.end = end;
this.loopBlocks = Collections.unmodifiableSet(BlockUtils.getAllPathsBlocks(start, end));
}
public BlockNode getStart() {
return start;
}
public BlockNode getEnd() {
return end;
}
public Set<BlockNode> getLoopBlocks() {
return loopBlocks;
}
/**
* Return source blocks of exit edges. <br>
* Exit nodes belongs to loop (contains in {@code loopBlocks})
*/
public Set<BlockNode> getExitNodes() {
Set<BlockNode> nodes = new HashSet<BlockNode>();
Set<BlockNode> blocks = getLoopBlocks();
for (BlockNode block : blocks) {
// exit: successor node not from this loop, (don't change to getCleanSuccessors)
for (BlockNode s : block.getSuccessors()) {
if (!blocks.contains(s) && !s.contains(AType.EXC_HANDLER)) {
nodes.add(block);
}
}
}
return nodes;
}
/**
* Return loop exit edges.
*/
public List<Edge> getExitEdges() {
List<Edge> edges = new LinkedList<Edge>();
Set<BlockNode> blocks = getLoopBlocks();
for (BlockNode block : blocks) {
for (BlockNode s : block.getSuccessors()) {
if (!blocks.contains(s) && !s.contains(AType.EXC_HANDLER)) {
edges.add(new Edge(block, s));
}
}
}
return edges;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public LoopInfo getParentLoop() {
return parentLoop;
}
public void setParentLoop(LoopInfo parentLoop) {
this.parentLoop = parentLoop;
}
@Override
public String toString() {
return "LOOP:" + id + ": " + start + "->" + end;
}
}
@@ -0,0 +1,27 @@
package jadx.core.dex.attributes.nodes;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
public class LoopLabelAttr implements IAttribute {
private final LoopInfo loop;
public LoopLabelAttr(LoopInfo loop) {
this.loop = loop;
}
public LoopInfo getLoop() {
return loop;
}
@Override
public AType<LoopLabelAttr> getType() {
return AType.LOOP_LABEL;
}
@Override
public String toString() {
return "LOOP_LABEL: " + loop;
}
}
@@ -1,5 +1,7 @@
package jadx.core.dex.attributes;
package jadx.core.dex.attributes.nodes;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.nodes.InsnNode;
public class MethodInlineAttr implements IAttribute {
@@ -15,8 +17,8 @@ public class MethodInlineAttr implements IAttribute {
}
@Override
public AttributeType getType() {
return AttributeType.METHOD_INLINE;
public AType<MethodInlineAttr> getType() {
return AType.METHOD_INLINE;
}
@Override
@@ -0,0 +1,32 @@
package jadx.core.dex.attributes.nodes;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.instructions.PhiInsn;
import java.util.LinkedList;
import java.util.List;
public class PhiListAttr implements IAttribute {
private final List<PhiInsn> list = new LinkedList<PhiInsn>();
@Override
public AType<PhiListAttr> getType() {
return AType.PHI_LIST;
}
public List<PhiInsn> getList() {
return list;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("PHI: ");
for (PhiInsn phiInsn : list) {
sb.append('r').append(phiInsn.getResult().getRegNum()).append(" ");
}
return sb.toString();
}
}
@@ -1,4 +1,7 @@
package jadx.core.dex.attributes;
package jadx.core.dex.attributes.nodes;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
public class SourceFileAttr implements IAttribute {
@@ -13,8 +16,8 @@ public class SourceFileAttr implements IAttribute {
}
@Override
public AttributeType getType() {
return AttributeType.SOURCE_FILE;
public AType<SourceFileAttr> getType() {
return AType.SOURCE_FILE;
}
@Override
@@ -8,7 +8,7 @@ public class AccessInfo {
private final int accFlags;
public static enum AFType {
public enum AFType {
CLASS, FIELD, METHOD
}
@@ -24,10 +24,11 @@ public class AccessInfo {
}
public AccessInfo remove(int flag) {
if (containsFlag(flag))
if (containsFlag(flag)) {
return new AccessInfo(accFlags - flag, type);
else
} else {
return this;
}
}
public AccessInfo getVisibility() {
@@ -111,71 +112,73 @@ public class AccessInfo {
public String makeString() {
StringBuilder code = new StringBuilder();
if (isPublic())
if (isPublic()) {
code.append("public ");
if (isPrivate())
}
if (isPrivate()) {
code.append("private ");
if (isProtected())
}
if (isProtected()) {
code.append("protected ");
if (isStatic())
}
if (isStatic()) {
code.append("static ");
if (isFinal())
}
if (isFinal()) {
code.append("final ");
if (isAbstract())
}
if (isAbstract()) {
code.append("abstract ");
if (isNative())
}
if (isNative()) {
code.append("native ");
}
switch (type) {
case METHOD:
if (isSynchronized())
if (isSynchronized()) {
code.append("synchronized ");
if (isBridge())
}
if (isBridge()) {
code.append("/* bridge */ ");
}
if (Consts.DEBUG) {
if (isVarArgs())
if (isVarArgs()) {
code.append("/* varargs */ ");
}
}
break;
case FIELD:
if (isVolatile())
if (isVolatile()) {
code.append("volatile ");
if (isTransient())
}
if (isTransient()) {
code.append("transient ");
}
break;
case CLASS:
if ((accFlags & AccessFlags.ACC_STRICT) != 0)
if ((accFlags & AccessFlags.ACC_STRICT) != 0) {
code.append("strict ");
}
if (Consts.DEBUG) {
if ((accFlags & AccessFlags.ACC_SUPER) != 0)
if ((accFlags & AccessFlags.ACC_SUPER) != 0) {
code.append("/* super */ ");
if ((accFlags & AccessFlags.ACC_ENUM) != 0)
}
if ((accFlags & AccessFlags.ACC_ENUM) != 0) {
code.append("/* enum */ ");
}
}
break;
}
if (isSynthetic())
if (isSynthetic()) {
code.append("/* synthetic */ ");
}
return code.toString();
}
public String rawString() {
switch (type){
switch (type) {
case CLASS:
return AccessFlags.classString(accFlags);
case FIELD:
@@ -13,14 +13,28 @@ public final class ClassInfo {
private static final Map<ArgType, ClassInfo> CLASSINFO_CACHE = new WeakHashMap<ArgType, ClassInfo>();
private final ArgType type;
private String pkg;
private String name;
private String fullName;
// for inner class not equals null
private ClassInfo parentClass;
private ClassInfo(ArgType type) {
assert type.isObject() : "Not class type: " + type;
this.type = type;
splitNames(true);
}
public static ClassInfo fromDex(DexNode dex, int clsIndex) {
if (clsIndex == DexNode.NO_INDEX)
if (clsIndex == DexNode.NO_INDEX) {
return null;
}
ArgType type = dex.getType(clsIndex);
if (type.isArray())
if (type.isArray()) {
type = ArgType.OBJECT;
}
return fromType(type);
}
@@ -41,54 +55,41 @@ public final class ClassInfo {
CLASSINFO_CACHE.clear();
}
private final ArgType type;
private String pkg;
private String name;
private String fullName;
private ClassInfo parentClass; // not equals null if this is inner class
private ClassInfo(ArgType type) {
assert type.isObject() : "Not class type: " + type;
this.type = type;
splitNames(true);
}
private void splitNames(boolean canBeInner) {
String fullObjectName = type.getObject();
assert fullObjectName.indexOf('/') == -1 : "Raw type: " + type;
String name;
String clsName;
int dot = fullObjectName.lastIndexOf('.');
if (dot == -1) {
// rename default package if it used from class with package (often for obfuscated apps),
pkg = Consts.DEFAULT_PACKAGE_NAME;
name = fullObjectName;
clsName = fullObjectName;
} else {
pkg = fullObjectName.substring(0, dot);
name = fullObjectName.substring(dot + 1);
clsName = fullObjectName.substring(dot + 1);
}
int sep = name.lastIndexOf('$');
if (canBeInner && sep > 0 && sep != name.length() - 1) {
String parClsName = pkg + '.' + name.substring(0, sep);
int sep = clsName.lastIndexOf('$');
if (canBeInner && sep > 0 && sep != clsName.length() - 1) {
String parClsName = pkg + "." + clsName.substring(0, sep);
parentClass = fromName(parClsName);
name = name.substring(sep + 1);
clsName = clsName.substring(sep + 1);
} else {
parentClass = null;
}
char firstChar = name.charAt(0);
char firstChar = clsName.charAt(0);
if (Character.isDigit(firstChar)) {
name = Consts.ANONYMOUS_CLASS_PREFIX + name;
clsName = Consts.ANONYMOUS_CLASS_PREFIX + clsName;
} else if (firstChar == '$') {
name = "_" + name;
clsName = "_" + clsName;
}
if (NameMapper.isReserved(name)) {
name += "_";
if (NameMapper.isReserved(clsName)) {
clsName += "_";
}
this.fullName = (parentClass != null ? parentClass.getFullName() : pkg) + "." + name;
this.name = name;
this.fullName = (parentClass != null ? parentClass.getFullName() : pkg) + "." + clsName;
this.name = clsName;
}
public String getFullPath() {
@@ -153,7 +154,9 @@ public final class ClassInfo {
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (this == obj) {
return true;
}
if (obj instanceof ClassInfo) {
ClassInfo other = (ClassInfo) obj;
return this.getFullName().equals(other.getFullName());
@@ -3,24 +3,26 @@ package jadx.core.dex.info;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.DexNode;
import com.android.dx.io.FieldId;
import com.android.dex.FieldId;
public class FieldInfo {
private final ClassInfo declClass;
private final String name;
private final ArgType type;
private final ClassInfo declClass;
public static FieldInfo fromDex(DexNode dex, int index) {
return new FieldInfo(dex, index);
public FieldInfo(ClassInfo declClass, String name, ArgType type) {
this.declClass = declClass;
this.name = name;
this.type = type;
}
private FieldInfo(DexNode dex, int ind) {
FieldId field = dex.getFieldId(ind);
this.name = dex.getString(field.getNameIndex());
this.type = dex.getType(field.getTypeIndex());
this.declClass = ClassInfo.fromDex(dex, field.getDeclaringClassIndex());
public static FieldInfo fromDex(DexNode dex, int index) {
FieldId field = dex.getFieldId(index);
return new FieldInfo(
ClassInfo.fromDex(dex, field.getDeclaringClassIndex()),
dex.getString(field.getNameIndex()),
dex.getType(field.getTypeIndex()));
}
public static String getNameById(DexNode dex, int ind) {
@@ -41,13 +43,22 @@ public class FieldInfo {
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
FieldInfo fieldInfo = (FieldInfo) o;
if (!name.equals(fieldInfo.name)) return false;
if (!type.equals(fieldInfo.type)) return false;
if (!declClass.equals(fieldInfo.declClass)) return false;
if (!name.equals(fieldInfo.name)) {
return false;
}
if (!type.equals(fieldInfo.type)) {
return false;
}
if (!declClass.equals(fieldInfo.declClass)) {
return false;
}
return true;
}
@@ -7,8 +7,8 @@ import jadx.core.utils.Utils;
import java.util.List;
import com.android.dx.io.MethodId;
import com.android.dx.io.ProtoId;
import com.android.dex.MethodId;
import com.android.dex.ProtoId;
public final class MethodInfo {
@@ -18,10 +18,6 @@ public final class MethodInfo {
private final ClassInfo declClass;
private final String shortId;
public static MethodInfo fromDex(DexNode dex, int mthIndex) {
return new MethodInfo(dex, mthIndex);
}
private MethodInfo(DexNode dex, int mthIndex) {
MethodId mthId = dex.getMethodId(mthIndex);
name = dex.getString(mthId.getNameIndex());
@@ -31,14 +27,20 @@ public final class MethodInfo {
retType = dex.getType(proto.getReturnTypeIndex());
args = dex.readParamList(proto.getParametersOffset());
StringBuilder strArg = new StringBuilder();
strArg.append('(');
for (ArgType arg : args)
strArg.append(TypeGen.signature(arg));
strArg.append(')');
// strArg.append(TypeGen.signature(retType));
StringBuilder signature = new StringBuilder();
signature.append(name);
signature.append('(');
for (ArgType arg : args) {
signature.append(TypeGen.signature(arg));
}
signature.append(')');
signature.append(TypeGen.signature(retType));
shortId = name + strArg;
shortId = signature.toString();
}
public static MethodInfo fromDex(DexNode dex, int mthIndex) {
return new MethodInfo(dex, mthIndex);
}
public String getName() {
@@ -86,30 +88,40 @@ public final class MethodInfo {
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + declClass.hashCode();
result = prime * result + retType.hashCode();
result = prime * result + shortId.hashCode();
int result = declClass.hashCode();
result = 31 * result + retType.hashCode();
result = 31 * result + shortId.hashCode();
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
MethodInfo other = (MethodInfo) obj;
if (!shortId.equals(other.shortId)) return false;
if (!retType.equals(other.retType)) return false;
if (!declClass.equals(other.declClass)) return false;
if (!shortId.equals(other.shortId)) {
return false;
}
if (!retType.equals(other.retType)) {
return false;
}
if (!declClass.equals(other.declClass)) {
return false;
}
return true;
}
@Override
public String toString() {
return retType + " " + declClass.getFullName() + "." + name
+ "(" + Utils.listToString(args) + ")";
return declClass.getFullName() + "." + name
+ "(" + Utils.listToString(args) + "):" + retType;
}
}
@@ -1,5 +1,6 @@
package jadx.core.dex.instructions;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.RegisterArg;
@@ -51,16 +52,31 @@ public class ArithNode extends InsnNode {
}
public ArithNode(ArithOp op, RegisterArg res, InsnArg a) {
super(InsnType.ARITH, 1);
this.op = op;
setResult(res);
addArg(a);
this(op, res, res, a);
add(AFlag.ARITH_ONEARG);
}
public ArithOp getOp() {
return op;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof ArithNode) || !super.equals(obj)) {
return false;
}
ArithNode that = (ArithNode) obj;
return op == that.op;
}
@Override
public int hashCode() {
return 31 * super.hashCode() + op.hashCode();
}
@Override
public String toString() {
return InsnUtils.formatOffset(offset) + ": "
@@ -68,7 +84,7 @@ public class ArithNode extends InsnNode {
+ getResult() + " = "
+ getArg(0) + " "
+ op.getSymbol() + " "
+ (getArgsCount() == 2 ? getArg(1) : "");
+ getArg(1);
}
}
@@ -15,12 +15,12 @@ public enum ArithOp {
SHR(">>"),
USHR(">>>");
private ArithOp(String symbol) {
private final String symbol;
ArithOp(String symbol) {
this.symbol = symbol;
}
private final String symbol;
public String getSymbol() {
return this.symbol;
}
@@ -3,7 +3,7 @@ package jadx.core.dex.instructions;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.InsnNode;
public class ConstClassNode extends InsnNode {
public final class ConstClassNode extends InsnNode {
private final ArgType clsType;
@@ -16,6 +16,23 @@ public class ConstClassNode extends InsnNode {
return clsType;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof ConstClassNode) || !super.equals(obj)) {
return false;
}
ConstClassNode that = (ConstClassNode) obj;
return clsType.equals(that.clsType);
}
@Override
public int hashCode() {
return 31 * super.hashCode() + clsType.hashCode();
}
@Override
public String toString() {
return super.toString() + " " + clsType;
@@ -2,7 +2,7 @@ package jadx.core.dex.instructions;
import jadx.core.dex.nodes.InsnNode;
public class ConstStringNode extends InsnNode {
public final class ConstStringNode extends InsnNode {
private final String str;
@@ -15,6 +15,23 @@ public class ConstStringNode extends InsnNode {
return str;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof ConstStringNode) || !super.equals(obj)) {
return false;
}
ConstStringNode that = (ConstStringNode) obj;
return str.equals(that.str);
}
@Override
public int hashCode() {
return 31 * super.hashCode() + str.hashCode();
}
@Override
public String toString() {
return super.toString() + " \"" + str + "\"";
@@ -8,7 +8,7 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
import com.android.dx.io.instructions.FillArrayDataPayloadDecodedInstruction;
public class FillArrayNode extends InsnNode {
public final class FillArrayNode extends InsnNode {
private final Object data;
private ArgType elemType;
@@ -53,4 +53,21 @@ public class FillArrayNode extends InsnNode {
elemType = r;
}
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof FillArrayNode) || !super.equals(obj)) {
return false;
}
FillArrayNode that = (FillArrayNode) obj;
return elemType.equals(that.elemType) && data == that.data;
}
@Override
public int hashCode() {
return 31 * super.hashCode() + elemType.hashCode() + data.hashCode();
}
}
@@ -8,11 +8,11 @@ public class GotoNode extends InsnNode {
protected int target;
public GotoNode(int target) {
this(InsnType.GOTO, target);
this(InsnType.GOTO, target, 0);
}
protected GotoNode(InsnType type, int target) {
super(type);
protected GotoNode(InsnType type, int target, int argsCount) {
super(type, argsCount);
this.target = target;
}
@@ -20,6 +20,23 @@ public class GotoNode extends InsnNode {
return target;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof GotoNode) || !super.equals(obj)) {
return false;
}
GotoNode gotoNode = (GotoNode) obj;
return target == gotoNode.target;
}
@Override
public int hashCode() {
return 31 * super.hashCode() + target;
}
@Override
public String toString() {
return super.toString() + "-> " + InsnUtils.formatOffset(target);
@@ -2,7 +2,6 @@ package jadx.core.dex.instructions;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.PrimitiveType;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.utils.InsnUtils;
@@ -14,48 +13,32 @@ import static jadx.core.utils.BlockUtils.selectOther;
public class IfNode extends GotoNode {
protected boolean zeroCmp;
private static final ArgType ARG_TYPE = ArgType.unknown(
PrimitiveType.INT, PrimitiveType.OBJECT, PrimitiveType.ARRAY,
PrimitiveType.BOOLEAN, PrimitiveType.SHORT, PrimitiveType.CHAR);
protected IfOp op;
private BlockNode thenBlock;
private BlockNode elseBlock;
public IfNode(int targ, InsnArg then, InsnArg els) {
super(InsnType.IF, targ);
addArg(then);
if (els == null) {
zeroCmp = true;
} else {
zeroCmp = false;
addArg(els);
}
public IfNode(DecodedInstruction insn, IfOp op) {
this(op, insn.getTarget(),
InsnArg.reg(insn, 0, ARG_TYPE),
insn.getRegisterCount() == 1 ? InsnArg.lit(0, ARG_TYPE) : InsnArg.reg(insn, 1, ARG_TYPE));
}
public IfNode(DecodedInstruction insn, IfOp op) {
super(InsnType.IF, insn.getTarget());
public IfNode(IfOp op, int targetOffset, InsnArg arg1, InsnArg arg2) {
super(InsnType.IF, targetOffset, 2);
this.op = op;
ArgType type = ArgType.unknown(
PrimitiveType.INT, PrimitiveType.OBJECT, PrimitiveType.ARRAY,
PrimitiveType.BOOLEAN, PrimitiveType.SHORT, PrimitiveType.CHAR);
addReg(insn, 0, type);
if (insn.getRegisterCount() == 1) {
zeroCmp = true;
} else {
zeroCmp = false;
addReg(insn, 1, type);
}
addArg(arg1);
addArg(arg2);
}
public IfOp getOp() {
return op;
}
public boolean isZeroCmp() {
return zeroCmp;
}
public void invertCondition() {
op = op.invert();
BlockNode tmp = thenBlock;
@@ -64,17 +47,10 @@ public class IfNode extends GotoNode {
target = thenBlock.getStartOffset();
}
public void changeCondition(InsnArg arg1, InsnArg arg2, IfOp op) {
public void changeCondition(IfOp op, InsnArg arg1, InsnArg arg2) {
this.op = op;
this.zeroCmp = arg2.isLiteral() && ((LiteralArg) arg2).getLiteral() == 0;
setArg(0, arg1);
if (!zeroCmp) {
if (getArgsCount() == 2) {
setArg(1, arg2);
} else {
addArg(arg2);
}
}
setArg(1, arg2);
}
public void initBlocks(BlockNode curBlock) {
@@ -84,7 +60,6 @@ public class IfNode extends GotoNode {
} else {
elseBlock = selectOther(thenBlock, curBlock.getSuccessors());
}
target = thenBlock.getStartOffset();
}
public BlockNode getThenBlock() {
@@ -95,12 +70,28 @@ public class IfNode extends GotoNode {
return elseBlock;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof IfNode) || !super.equals(obj)) {
return false;
}
IfNode ifNode = (IfNode) obj;
return op == ifNode.op;
}
@Override
public int hashCode() {
return 31 * super.hashCode() + op.hashCode();
}
@Override
public String toString() {
return InsnUtils.formatOffset(offset) + ": "
+ InsnUtils.insnTypeToString(insnType)
+ getArg(0) + " " + op.getSymbol()
+ " " + (zeroCmp ? "0" : getArg(1))
+ getArg(0) + " " + op.getSymbol() + " " + getArg(1)
+ " -> " + (thenBlock != null ? thenBlock : InsnUtils.formatOffset(target));
}
}
@@ -12,7 +12,7 @@ public enum IfOp {
private final String symbol;
private IfOp(String symbol) {
IfOp(String symbol) {
this.symbol = symbol;
}
@@ -23,19 +23,19 @@ public enum IfOp {
public IfOp invert() {
switch (this) {
case EQ:
return IfOp.NE;
return NE;
case NE:
return IfOp.EQ;
return EQ;
case LT:
return IfOp.GE;
return GE;
case LE:
return IfOp.GT;
return GT;
case GT:
return IfOp.LE;
return LE;
case GE:
return IfOp.LT;
return LT;
default:
throw new JadxRuntimeException("Unknown if operations type: " + this);
@@ -16,6 +16,23 @@ public class IndexInsnNode extends InsnNode {
return index;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof IndexInsnNode) || !super.equals(obj)) {
return false;
}
IndexInsnNode that = (IndexInsnNode) obj;
return index == null ? that.index == null : index.equals(that.index);
}
@Override
public int hashCode() {
return 31 * super.hashCode() + (index != null ? index.hashCode() : 0);
}
@Override
public String toString() {
return super.toString() + " " + InsnUtils.indexToString(index);
@@ -4,37 +4,52 @@ import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.NamedArg;
import jadx.core.dex.instructions.args.PrimitiveType;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.exceptions.DecodeException;
import com.android.dx.io.Code;
import com.android.dex.Code;
import com.android.dx.io.OpcodeInfo;
import com.android.dx.io.Opcodes;
import com.android.dx.io.instructions.DecodedInstruction;
import com.android.dx.io.instructions.FillArrayDataPayloadDecodedInstruction;
import com.android.dx.io.instructions.PackedSwitchPayloadDecodedInstruction;
import com.android.dx.io.instructions.ShortArrayCodeInput;
import com.android.dx.io.instructions.SparseSwitchPayloadDecodedInstruction;
public class InsnDecoder {
private final MethodNode method;
private final DecodedInstruction[] insnArr;
private final DexNode dex;
private DecodedInstruction[] insnArr;
public InsnDecoder(MethodNode mthNode, Code mthCode) {
public InsnDecoder(MethodNode mthNode) {
this.method = mthNode;
this.dex = method.dex();
this.insnArr = DecodedInstruction.decodeAll(mthCode.getInstructions());
}
public InsnNode[] run() throws DecodeException {
InsnNode[] instructions = new InsnNode[insnArr.length];
public void decodeInsns(Code mthCode) throws DecodeException {
short[] encodedInstructions = mthCode.getInstructions();
int size = encodedInstructions.length;
DecodedInstruction[] decoded = new DecodedInstruction[size];
ShortArrayCodeInput in = new ShortArrayCodeInput(encodedInstructions);
try {
while (in.hasMore()) {
decoded[in.cursor()] = DecodedInstruction.decode(in);
}
} catch (Exception e) {
throw new DecodeException(method, "", e);
}
insnArr = decoded;
}
public InsnNode[] process() throws DecodeException {
InsnNode[] instructions = new InsnNode[insnArr.length];
for (int i = 0; i < insnArr.length; i++) {
DecodedInstruction rawInsn = insnArr[i];
if (rawInsn != null) {
@@ -48,6 +63,7 @@ public class InsnDecoder {
instructions[i] = null;
}
}
insnArr = null;
return instructions;
}
@@ -388,8 +404,7 @@ public class InsnDecoder {
case Opcodes.MOVE_EXCEPTION:
return insn(InsnType.MOVE_EXCEPTION,
InsnArg.reg(insn, 0, ArgType.unknown(PrimitiveType.OBJECT)),
new NamedArg("e", ArgType.unknown(PrimitiveType.OBJECT)));
InsnArg.reg(insn, 0, ArgType.unknown(PrimitiveType.OBJECT)));
case Opcodes.RETURN_VOID:
return new InsnNode(InsnType.RETURN, 0);
@@ -473,7 +488,7 @@ public class InsnDecoder {
case Opcodes.ARRAY_LENGTH: {
InsnNode node = new InsnNode(InsnType.ARRAY_LENGTH, 1);
node.setResult(InsnArg.reg(insn, 0, ArgType.INT));
node.addArg(InsnArg.reg(insn, 1, ArgType.unknown(PrimitiveType.ARRAY)));
node.addArg(InsnArg.reg(insn, 1, ArgType.array(ArgType.UNKNOWN)));
return node;
}
@@ -578,14 +593,16 @@ public class InsnDecoder {
targets = ps.getTargets();
keys = new Object[targets.length];
int k = ps.getFirstKey();
for (int i = 0; i < keys.length; i++)
for (int i = 0; i < keys.length; i++) {
keys[i] = k++;
}
} else {
SparseSwitchPayloadDecodedInstruction ss = (SparseSwitchPayloadDecodedInstruction) payload;
targets = ss.getTargets();
keys = new Object[targets.length];
for (int i = 0; i < keys.length; i++)
for (int i = 0; i < keys.length; i++) {
keys[i] = ss.getKeys()[i];
}
}
// convert from relative to absolute offsets
for (int i = 0; i < targets.length; i++) {
@@ -604,16 +621,20 @@ public class InsnDecoder {
int resReg = getMoveResultRegister(insnArr, offset);
ArgType arrType = dex.getType(insn.getIndex());
ArgType elType = arrType.getArrayElement();
InsnArg[] regs = new InsnArg[insn.getRegisterCount()];
boolean typeImmutable = elType.isPrimitive();
int regsCount = insn.getRegisterCount();
InsnArg[] regs = new InsnArg[regsCount];
if (isRange) {
int r = insn.getA();
for (int i = 0; i < insn.getRegisterCount(); i++) {
regs[i] = InsnArg.reg(r, elType);
for (int i = 0; i < regsCount; i++) {
regs[i] = InsnArg.reg(r, elType, typeImmutable);
r++;
}
} else {
for (int i = 0; i < insn.getRegisterCount(); i++)
regs[i] = InsnArg.reg(insn, i, elType);
for (int i = 0; i < regsCount; i++) {
int regNum = InsnUtils.getArg(insn, i);
regs[i] = InsnArg.reg(regNum, elType, typeImmutable);
}
}
return insn(InsnType.FILLED_NEW_ARRAY,
resReg == -1 ? null : InsnArg.reg(resReg, arrType),
@@ -689,8 +710,9 @@ public class InsnDecoder {
InsnNode node = new InsnNode(type, args == null ? 0 : args.length);
node.setResult(res);
if (args != null) {
for (InsnArg arg : args)
for (InsnArg arg : args) {
node.addArg(arg);
}
}
return node;
}
@@ -711,19 +733,23 @@ public class InsnDecoder {
public static int getPrevInsnOffset(Object[] insnArr, int offset) {
int i = offset - 1;
while (i >= 0 && insnArr[i] == null)
while (i >= 0 && insnArr[i] == null) {
i--;
if (i < 0)
}
if (i < 0) {
return -1;
}
return i;
}
public static int getNextInsnOffset(Object[] insnArr, int offset) {
int i = offset + 1;
while (i < insnArr.length && insnArr[i] == null)
while (i < insnArr.length && insnArr[i] == null) {
i++;
if (i >= insnArr.length)
}
if (i >= insnArr.length) {
return -1;
}
return i;
}
}
@@ -1,7 +1,6 @@
package jadx.core.dex.instructions;
public enum InsnType {
NOP, // replacement for removed instructions
CONST,
CONST_STR,
@@ -48,15 +47,24 @@ public enum InsnType {
INVOKE,
// additional instructions
// *** Additional instructions ***
// replacement for removed instructions
NOP,
TERNARY,
CONSTRUCTOR,
BREAK,
CONTINUE,
STR_CONCAT, // strings concatenation
// strings concatenation
STR_CONCAT,
TERNARY,
ARGS, // just generate arguments
// just generate one argument
ONE_ARG,
PHI,
NEW_MULTIDIM_ARRAY // TODO: now multidimensional arrays created using Array.newInstance function
// TODO: now multidimensional arrays created using Array.newInstance function
NEW_MULTIDIM_ARRAY
}
@@ -19,8 +19,9 @@ public class InvokeNode extends InsnNode {
this.mth = mth;
this.type = type;
if (resReg >= 0)
if (resReg >= 0) {
setResult(InsnArg.reg(resReg, mth.getReturnType()));
}
int k = isRange ? insn.getA() : 0;
if (type != InvokeType.STATIC) {
@@ -43,6 +44,26 @@ public class InvokeNode extends InsnNode {
return mth;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof InvokeNode) || !super.equals(obj)) {
return false;
}
InvokeNode that = (InvokeNode) obj;
return type == that.type && mth.equals(that.mth);
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + type.hashCode();
result = 31 * result + mth.hashCode();
return result;
}
@Override
public String toString() {
return InsnUtils.formatOffset(offset) + ": "
@@ -0,0 +1,38 @@
package jadx.core.dex.instructions;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.Utils;
public class PhiInsn extends InsnNode {
public PhiInsn(int regNum, int predecessors) {
super(InsnType.PHI, predecessors);
setResult(InsnArg.reg(regNum, ArgType.UNKNOWN));
for (int i = 0; i < predecessors; i++) {
addReg(regNum, ArgType.UNKNOWN);
}
add(AFlag.DONT_INLINE);
}
@Override
public RegisterArg getArg(int n) {
return (RegisterArg) super.getArg(n);
}
public boolean removeArg(RegisterArg arg) {
boolean isRemoved = super.removeArg(arg);
if (isRemoved) {
arg.getSVar().setUsedInPhi(null);
}
return isRemoved;
}
@Override
public String toString() {
return "PHI: " + getResult() + " = " + Utils.listToString(getArguments());
}
}
@@ -36,14 +36,38 @@ public class SwitchNode extends InsnNode {
return def;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof SwitchNode) || !super.equals(obj)) {
return false;
}
SwitchNode that = (SwitchNode) obj;
return def == that.def
&& Arrays.equals(keys, that.keys)
&& Arrays.equals(targets, that.targets);
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + Arrays.hashCode(keys);
result = 31 * result + Arrays.hashCode(targets);
result = 31 * result + def;
return result;
}
@Override
public String toString() {
StringBuilder targ = new StringBuilder();
targ.append('[');
for (int i = 0; i < targets.length; i++) {
targ.append(InsnUtils.formatOffset(targets[i]));
if (i < targets.length - 1)
if (i < targets.length - 1) {
targ.append(", ");
}
}
targ.append(']');
return super.toString() + " k:" + Arrays.toString(keys) + " t:" + targ;
@@ -2,20 +2,16 @@ package jadx.core.dex.instructions.args;
import jadx.core.Consts;
import jadx.core.clsp.ClspGraph;
import jadx.core.dex.nodes.parser.SignatureParser;
import jadx.core.utils.Utils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.jetbrains.annotations.Nullable;
public abstract class ArgType {
private static final Logger LOG = LoggerFactory.getLogger(ArgType.class);
public static final ArgType INT = primitive(PrimitiveType.INT);
public static final ArgType BOOLEAN = primitive(PrimitiveType.BOOLEAN);
@@ -54,24 +50,40 @@ public abstract class ArgType {
ArgType.clsp = clsp;
}
public static boolean isClspSet() {
return ArgType.clsp != null;
}
private static ArgType primitive(PrimitiveType stype) {
return new PrimitiveArg(stype);
}
public static ArgType object(String obj) {
return new ObjectArg(obj);
return new ObjectType(obj);
}
public static ArgType genericType(String type) {
return new GenericTypeArg(type);
return new GenericType(type);
}
public static ArgType wildcard() {
return new WildcardType(OBJECT, 0);
}
public static ArgType wildcard(ArgType obj, int bound) {
return new WildcardType(obj, bound);
}
public static ArgType generic(String sign) {
return parseSignature(sign);
return new SignatureParser(sign).consumeType();
}
public static ArgType generic(String obj, ArgType[] generics) {
return new GenericObjectArg(obj, generics);
return new GenericObject(obj, generics);
}
public static ArgType genericInner(ArgType genericType, String innerName, ArgType[] generics) {
return new GenericObject((GenericObject) genericType, innerName, generics);
}
public static ArgType array(ArgType vtype) {
@@ -82,14 +94,32 @@ public abstract class ArgType {
return new UnknownArg(types);
}
private abstract static class KnownTypeArg extends ArgType {
private abstract static class KnownType extends ArgType {
private static final PrimitiveType[] EMPTY_POSSIBLES = new PrimitiveType[0];
@Override
public boolean isTypeKnown() {
return true;
}
@Override
public boolean contains(PrimitiveType type) {
return getPrimitiveType() == type;
}
@Override
public ArgType selectFirst() {
return null;
}
@Override
public PrimitiveType[] getPossibleTypes() {
return EMPTY_POSSIBLES;
}
}
private static final class PrimitiveArg extends KnownTypeArg {
private static final class PrimitiveArg extends KnownType {
private final PrimitiveType type;
public PrimitiveArg(PrimitiveType type) {
@@ -118,10 +148,10 @@ public abstract class ArgType {
}
}
private static class ObjectArg extends KnownTypeArg {
private static class ObjectType extends KnownType {
private final String object;
public ObjectArg(String obj) {
public ObjectType(String obj) {
this.object = Utils.cleanObjectName(obj);
this.hash = object.hashCode();
}
@@ -143,7 +173,7 @@ public abstract class ArgType {
@Override
boolean internalEquals(Object obj) {
return object.equals(((ObjectArg) obj).object);
return object.equals(((ObjectType) obj).object);
}
@Override
@@ -152,8 +182,8 @@ public abstract class ArgType {
}
}
private static final class GenericTypeArg extends ObjectArg {
public GenericTypeArg(String obj) {
private static final class GenericType extends ObjectType {
public GenericType(String obj) {
super(obj);
}
@@ -163,24 +193,93 @@ public abstract class ArgType {
}
}
private static final class GenericObjectArg extends ObjectArg {
private final ArgType[] generics;
private static final class WildcardType extends ObjectType {
private final ArgType type;
private final int bounds;
public GenericObjectArg(String obj, ArgType[] generics) {
public WildcardType(ArgType obj, int bound) {
super(OBJECT.getObject());
this.type = obj;
this.bounds = bound;
}
@Override
public boolean isGeneric() {
return true;
}
@Override
public ArgType getWildcardType() {
return type;
}
/**
* Return wildcard bounds:
* <ul>
* <li> 1 for upper bound (? extends A) </li>
* <li> 0 no bounds (?) </li>
* <li>-1 for lower bound (? super A) </li>
* </ul>
*/
@Override
public int getWildcardBounds() {
return bounds;
}
@Override
boolean internalEquals(Object obj) {
return super.internalEquals(obj)
&& bounds == ((WildcardType) obj).bounds
&& type.equals(((WildcardType) obj).type);
}
@Override
public String toString() {
if (bounds == 0) {
return "?";
}
return "? " + (bounds == -1 ? "super" : "extends") + " " + type;
}
}
private static class GenericObject extends ObjectType {
private final ArgType[] generics;
private final GenericObject outerType;
public GenericObject(String obj, ArgType[] generics) {
super(obj);
this.outerType = null;
this.generics = generics;
this.hash = obj.hashCode() + 31 * Arrays.hashCode(generics);
}
public GenericObject(GenericObject outerType, String innerName, ArgType[] generics) {
super(outerType.getObject() + "$" + innerName);
this.outerType = outerType;
this.generics = generics;
this.hash = outerType.hashCode() + 31 * innerName.hashCode()
+ 31 * 31 * Arrays.hashCode(generics);
}
@Override
public boolean isGeneric() {
return true;
}
@Override
public ArgType[] getGenericTypes() {
return generics;
}
@Override
public ArgType getOuterType() {
return outerType;
}
@Override
boolean internalEquals(Object obj) {
return super.internalEquals(obj)
&& Arrays.equals(generics, ((GenericObjectArg) obj).generics);
&& Arrays.equals(generics, ((GenericObject) obj).generics);
}
@Override
@@ -189,7 +288,8 @@ public abstract class ArgType {
}
}
private static final class ArrayArg extends KnownTypeArg {
private static final class ArrayArg extends KnownType {
public static final PrimitiveType[] ARRAY_POSSIBLES = new PrimitiveType[]{PrimitiveType.ARRAY};
private final ArgType arrayElement;
public ArrayArg(ArgType arrayElement) {
@@ -212,6 +312,21 @@ public abstract class ArgType {
return PrimitiveType.ARRAY;
}
@Override
public boolean isTypeKnown() {
return arrayElement.isTypeKnown();
}
@Override
public ArgType selectFirst() {
return array(arrayElement.selectFirst());
}
@Override
public PrimitiveType[] getPossibleTypes() {
return ARRAY_POSSIBLES;
}
@Override
public int getArrayDimension() {
return 1 + arrayElement.getArrayDimension();
@@ -229,7 +344,7 @@ public abstract class ArgType {
@Override
public String toString() {
return arrayElement.toString() + "[]";
return arrayElement + "[]";
}
}
@@ -264,8 +379,10 @@ public abstract class ArgType {
@Override
public ArgType selectFirst() {
PrimitiveType f = possibleTypes[0];
if (f == PrimitiveType.OBJECT || f == PrimitiveType.ARRAY) {
return object(Consts.CLASS_OBJECT);
if (contains(PrimitiveType.OBJECT)) {
return OBJECT;
} else if (contains(PrimitiveType.ARRAY)) {
return array(OBJECT);
} else {
return primitive(f);
}
@@ -299,13 +416,17 @@ public abstract class ArgType {
}
public String getObject() {
throw new UnsupportedOperationException();
throw new UnsupportedOperationException("ArgType.getObject(), call class: " + this.getClass());
}
public boolean isObject() {
return false;
}
public boolean isGeneric() {
return false;
}
public boolean isGenericType() {
return false;
}
@@ -314,6 +435,21 @@ public abstract class ArgType {
return null;
}
public ArgType getWildcardType() {
return null;
}
/**
* @see WildcardType#getWildcardBounds()
*/
public int getWildcardBounds() {
return 0;
}
public ArgType getOuterType() {
return null;
}
public boolean isArray() {
return false;
}
@@ -330,20 +466,15 @@ public abstract class ArgType {
return this;
}
public boolean contains(PrimitiveType type) {
throw new UnsupportedOperationException();
}
public abstract boolean contains(PrimitiveType type);
public ArgType selectFirst() {
throw new UnsupportedOperationException();
}
public abstract ArgType selectFirst();
public PrimitiveType[] getPossibleTypes() {
return null;
}
public abstract PrimitiveType[] getPossibleTypes();
@Nullable
public static ArgType merge(ArgType a, ArgType b) {
if (b == null || a == null) {
if (a == null || b == null) {
return null;
}
if (a.equals(b)) {
@@ -360,13 +491,18 @@ public abstract class ArgType {
if (a == UNKNOWN) {
return b;
}
if (a.isArray()) {
return mergeArrays((ArrayArg) a, b);
} else if (b.isArray()) {
return mergeArrays((ArrayArg) b, a);
}
if (!a.isTypeKnown()) {
if (b.isTypeKnown()) {
if (a.contains(b.getPrimitiveType())) {
return b;
} else {
return null;
}
return null;
} else {
// both types unknown
List<PrimitiveType> types = new ArrayList<PrimitiveType>();
@@ -375,9 +511,10 @@ public abstract class ArgType {
types.add(type);
}
}
if (types.size() == 0) {
if (types.isEmpty()) {
return null;
} else if (types.size() == 1) {
}
if (types.size() == 1) {
PrimitiveType nt = types.get(0);
if (nt == PrimitiveType.OBJECT || nt == PrimitiveType.ARRAY) {
return unknown(nt);
@@ -400,32 +537,16 @@ public abstract class ArgType {
String aObj = a.getObject();
String bObj = b.getObject();
if (aObj.equals(bObj)) {
return (a.getGenericTypes() != null ? a : b);
} else if (aObj.equals(Consts.CLASS_OBJECT)) {
return a.getGenericTypes() != null ? a : b;
}
if (aObj.equals(Consts.CLASS_OBJECT)) {
return b;
} else if (bObj.equals(Consts.CLASS_OBJECT)) {
}
if (bObj.equals(Consts.CLASS_OBJECT)) {
return a;
} else {
// different objects
String obj = clsp.getCommonAncestor(aObj, bObj);
return (obj == null ? null : object(obj));
}
}
if (a.isArray()) {
if (b.isArray()) {
ArgType ea = a.getArrayElement();
ArgType eb = b.getArrayElement();
if (ea.isPrimitive() && eb.isPrimitive()) {
return OBJECT;
} else {
ArgType res = merge(ea, eb);
return (res == null ? null : ArgType.array(res));
}
} else if (b.equals(OBJECT)) {
return OBJECT;
} else {
return null;
}
String obj = clsp.getCommonAncestor(aObj, bObj);
return obj == null ? null : object(obj);
}
if (a.isPrimitive() && b.isPrimitive() && a.getRegCount() == b.getRegCount()) {
return primitive(PrimitiveType.getSmaller(a.getPrimitiveType(), b.getPrimitiveType()));
@@ -434,6 +555,25 @@ public abstract class ArgType {
return null;
}
private static ArgType mergeArrays(ArrayArg array, ArgType b) {
if (b.isArray()) {
ArgType ea = array.getArrayElement();
ArgType eb = b.getArrayElement();
if (ea.isPrimitive() && eb.isPrimitive()) {
return OBJECT;
}
ArgType res = merge(ea, eb);
return res == null ? null : array(res);
}
if (b.contains(PrimitiveType.ARRAY)) {
return array;
}
if (b.equals(OBJECT)) {
return OBJECT;
}
return null;
}
public static boolean isCastNeeded(ArgType from, ArgType to) {
if (from.equals(to)) {
return false;
@@ -445,6 +585,16 @@ public abstract class ArgType {
return true;
}
public static boolean isInstanceOf(ArgType type, ArgType of) {
if (type.equals(of)) {
return true;
}
if (!type.isObject() || !of.isObject()) {
return false;
}
return clsp.isImplements(type.getObject(), of.getObject());
}
public static ArgType parse(String type) {
char f = type.charAt(0);
switch (f) {
@@ -459,148 +609,7 @@ public abstract class ArgType {
}
}
public static ArgType parseSignature(String sign) {
int b = sign.indexOf('<');
if (b == -1) {
return parse(sign);
}
if (sign.charAt(0) == '[') {
return array(parseSignature(sign.substring(1)));
}
String obj = sign.substring(0, b) + ";";
String genericsStr = sign.substring(b + 1, sign.length() - 2);
List<ArgType> generics = parseSignatureList(genericsStr);
if (generics != null) {
return generic(obj, generics.toArray(new ArgType[generics.size()]));
} else {
return object(obj);
}
}
public static List<ArgType> parseSignatureList(String str) {
try {
return parseSignatureListInner(str, true);
} catch (Throwable e) {
LOG.warn("Signature parse exception: {}", str, e);
return null;
}
}
private static List<ArgType> parseSignatureListInner(String str, boolean parsePrimitives) {
if (str.isEmpty()) {
return Collections.emptyList();
}
if (str.equals("*")) {
return Arrays.asList(UNKNOWN);
}
List<ArgType> signs = new ArrayList<ArgType>(3);
int obj = 0;
int objStart = 0;
int gen = 0;
int arr = 0;
int pos = 0;
ArgType type = null;
while (pos < str.length()) {
char c = str.charAt(pos);
switch (c) {
case 'L':
case 'T':
if (obj == 0 && gen == 0) {
obj++;
objStart = pos;
}
break;
case ';':
if (obj == 1 && gen == 0) {
obj--;
String o = str.substring(objStart, pos + 1);
type = parseSignature(o);
}
break;
case ':': // generic types map separator
if (gen == 0) {
obj = 0;
String o = str.substring(objStart, pos);
if (o.length() > 0) {
type = genericType(o);
}
}
break;
case '<':
gen++;
break;
case '>':
gen--;
break;
case '[':
if (obj == 0 && gen == 0) {
arr++;
}
break;
default:
if (parsePrimitives && obj == 0 && gen == 0) {
type = parse(c);
}
break;
}
if (type != null) {
if (arr == 0) {
signs.add(type);
} else {
for (int i = 0; i < arr; i++) {
type = array(type);
}
signs.add(type);
arr = 0;
}
type = null;
objStart = pos + 1;
}
pos++;
}
return signs;
}
public static Map<ArgType, List<ArgType>> parseGenericMap(String gen) {
try {
Map<ArgType, List<ArgType>> genericMap = null;
List<ArgType> genTypes = parseSignatureListInner(gen, false);
if (genTypes != null) {
genericMap = new LinkedHashMap<ArgType, List<ArgType>>(2);
ArgType prev = null;
List<ArgType> genList = new ArrayList<ArgType>(2);
for (ArgType arg : genTypes) {
if (arg.isGenericType()) {
if (prev != null) {
genericMap.put(prev, genList);
genList = new ArrayList<ArgType>();
}
prev = arg;
} else {
if (!arg.getObject().equals(Consts.CLASS_OBJECT)) {
genList.add(arg);
}
}
}
if (prev != null) {
genericMap.put(prev, genList);
}
}
return genericMap;
} catch (Throwable e) {
LOG.warn("Generic map parse exception: {}", gen, e);
return null;
}
}
private static ArgType parse(char f) {
public static ArgType parse(char f) {
switch (f) {
case 'Z':
return BOOLEAN;
@@ -2,12 +2,16 @@ package jadx.core.dex.instructions.args;
import jadx.core.dex.info.FieldInfo;
// TODO: don't extend RegisterArg (now used as a result of instruction)
public final class FieldArg extends RegisterArg {
private final FieldInfo field;
// instArg equal 'null' for static fields
private final InsnArg instArg;
public FieldArg(FieldInfo field, int regNum) {
super(regNum, field.getType());
public FieldArg(FieldInfo field, InsnArg reg) {
super(-1);
this.instArg = reg;
this.field = field;
}
@@ -15,8 +19,12 @@ public final class FieldArg extends RegisterArg {
return field;
}
public InsnArg getInstanceArg() {
return instArg;
}
public boolean isStatic() {
return regNum == -1;
return instArg == null;
}
@Override
@@ -29,6 +37,37 @@ public final class FieldArg extends RegisterArg {
return false;
}
@Override
public void setType(ArgType type) {
this.type = type;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof FieldArg) || !super.equals(obj)) {
return false;
}
FieldArg fieldArg = (FieldArg) obj;
if (!field.equals(fieldArg.field)) {
return false;
}
if (instArg != null ? !instArg.equals(fieldArg.instArg) : fieldArg.instArg != null) {
return false;
}
return true;
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + field.hashCode();
result = 31 * result + (instArg != null ? instArg.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "(" + field + ")";
@@ -1,27 +0,0 @@
package jadx.core.dex.instructions.args;
public class ImmutableTypedVar extends TypedVar {
public ImmutableTypedVar(ArgType type) {
super(type);
}
@Override
public boolean isImmutable() {
return true;
}
@Override
public void forceSetType(ArgType newType) {
}
@Override
public boolean merge(TypedVar typedVar) {
return false;
}
@Override
public boolean merge(ArgType type) {
return false;
}
}
@@ -1,8 +1,15 @@
package jadx.core.dex.instructions.args;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.InsnUtils;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.android.dx.io.instructions.DecodedInstruction;
/**
@@ -11,6 +18,8 @@ import com.android.dx.io.instructions.DecodedInstruction;
*/
public abstract class InsnArg extends Typed {
private static final Logger LOG = LoggerFactory.getLogger(InsnArg.class);
protected InsnNode parentInsn;
public static RegisterArg reg(int regNum, ArgType type) {
@@ -21,10 +30,12 @@ public abstract class InsnArg extends Typed {
return reg(InsnUtils.getArg(insn, argNum), type);
}
public static RegisterArg immutableReg(int regNum, ArgType type) {
RegisterArg r = new RegisterArg(regNum);
r.forceSetTypedVar(new ImmutableTypedVar(type));
return r;
public static TypeImmutableArg typeImmutableReg(int regNum, ArgType type) {
return new TypeImmutableArg(regNum, type);
}
public static RegisterArg reg(int regNum, ArgType type, boolean typeImmutable) {
return typeImmutable ? new TypeImmutableArg(regNum, type) : new RegisterArg(regNum, type);
}
public static LiteralArg lit(long literal, ArgType type) {
@@ -69,16 +80,40 @@ public abstract class InsnArg extends Typed {
public InsnArg wrapInstruction(InsnNode insn) {
InsnNode parent = parentInsn;
assert parent != insn : "Can't wrap instruction info itself";
if (parent == null) {
return null;
}
if (parent == insn) {
LOG.debug("Can't wrap instruction info itself: {}", insn);
Thread.dumpStack();
return null;
}
int i = getArgIndex(parent, this);
if (i == -1) {
return null;
}
insn.add(AFlag.WRAPPED);
InsnArg arg = wrapArg(insn);
parent.setArg(i, arg);
return arg;
}
public static void updateParentInsn(InsnNode fromInsn, InsnNode toInsn) {
List<RegisterArg> args = new ArrayList<RegisterArg>();
fromInsn.getRegisterArgs(args);
for (RegisterArg reg : args) {
reg.setParentInsn(toInsn);
}
}
private static int getArgIndex(InsnNode parent, InsnArg arg) {
int count = parent.getArgsCount();
for (int i = 0; i < count; i++) {
if (parent.getArg(i) == this) {
InsnArg arg = wrapArg(insn);
parent.setArg(i, arg);
return arg;
if (parent.getArg(i) == arg) {
return i;
}
}
return null;
return -1;
}
public static InsnArg wrapArg(InsnNode insn) {
@@ -90,11 +125,11 @@ public abstract class InsnArg extends Typed {
break;
case CONST_STR:
arg = wrap(insn);
arg.getTypedVar().forceSetType(ArgType.STRING);
arg.setType(ArgType.STRING);
break;
case CONST_CLASS:
arg = wrap(insn);
arg.getTypedVar().forceSetType(ArgType.CLASS);
arg.setType(ArgType.CLASS);
break;
default:
arg = wrap(insn);
@@ -104,7 +139,7 @@ public abstract class InsnArg extends Typed {
}
public boolean isThis() {
// must be implemented in RegisterArg
// must be implemented in RegisterArg and MthParameterArg
return false;
}
}
@@ -7,8 +7,8 @@ public final class InsnWrapArg extends InsnArg {
private final InsnNode wrappedInsn;
public InsnWrapArg(InsnNode insn) {
ArgType type = (insn.getResult() == null ? ArgType.VOID : insn.getResult().getType());
this.typedVar = new TypedVar(type);
RegisterArg result = insn.getResult();
this.type = result != null ? result.getType() : ArgType.VOID;
this.wrappedInsn = insn;
}
@@ -29,6 +29,6 @@ public final class InsnWrapArg extends InsnArg {
@Override
public String toString() {
return "(wrap: " + typedVar + "\n " + wrappedInsn + ")";
return "(wrap: " + type + "\n " + wrappedInsn + ")";
}
}
@@ -5,6 +5,9 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
public final class LiteralArg extends InsnArg {
public static final LiteralArg TRUE = new LiteralArg(1, ArgType.BOOLEAN);
public static final LiteralArg FALSE = new LiteralArg(0, ArgType.BOOLEAN);
private final long literal;
public LiteralArg(long value, ArgType type) {
@@ -21,7 +24,7 @@ public final class LiteralArg extends InsnArg {
}
}
this.literal = value;
this.typedVar = new TypedVar(type);
this.type = type;
}
public long getLiteral() {
@@ -34,21 +37,42 @@ public final class LiteralArg extends InsnArg {
}
public boolean isInteger() {
PrimitiveType type = typedVar.getType().getPrimitiveType();
return (type == PrimitiveType.INT
PrimitiveType type = this.type.getPrimitiveType();
return type == PrimitiveType.INT
|| type == PrimitiveType.BYTE
|| type == PrimitiveType.CHAR
|| type == PrimitiveType.SHORT
|| type == PrimitiveType.LONG);
|| type == PrimitiveType.LONG;
}
@Override
public int hashCode() {
return (int) (literal ^ (literal >>> 32)) + 31 * getType().hashCode();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
LiteralArg that = (LiteralArg) o;
return literal == that.literal && getType().equals(that.getType());
}
@Override
public String toString() {
try {
return "(" + TypeGen.literalToString(literal, getType()) + " " + typedVar + ")";
String value = TypeGen.literalToString(literal, getType());
if (getType().equals(ArgType.BOOLEAN) && (value.equals("true") || value.equals("false"))) {
return value;
}
return "(" + value + " " + type + ")";
} catch (JadxRuntimeException ex) {
// can't convert literal to string
return "(" + literal + " " + typedVar + ")";
return "(" + literal + " " + type + ")";
}
}
}
@@ -0,0 +1,8 @@
package jadx.core.dex.instructions.args;
public interface Named {
String getName();
void setName(String name);
}
@@ -1,12 +1,12 @@
package jadx.core.dex.instructions.args;
public final class NamedArg extends InsnArg {
public final class NamedArg extends InsnArg implements Named {
private String name;
public NamedArg(String name, ArgType type) {
this.name = name;
this.typedVar = new TypedVar(type);
this.type = type;
}
public String getName() {
@@ -24,6 +24,6 @@ public final class NamedArg extends InsnArg {
@Override
public String toString() {
return "(" + name + " " + typedVar + ")";
return "(" + name + " " + type + ")";
}
}
@@ -16,7 +16,7 @@ public enum PrimitiveType {
private final String shortName;
private final String longName;
private PrimitiveType(String shortName, String longName) {
PrimitiveType(String shortName, String longName) {
this.shortName = shortName;
this.longName = longName;
}
@@ -30,17 +30,19 @@ public enum PrimitiveType {
}
public static PrimitiveType getWidest(PrimitiveType a, PrimitiveType b) {
if (a.ordinal() > b.ordinal())
if (a.ordinal() > b.ordinal()) {
return a;
else
} else {
return b;
}
}
public static PrimitiveType getSmaller(PrimitiveType a, PrimitiveType b) {
if (a.ordinal() < b.ordinal())
if (a.ordinal() < b.ordinal()) {
return a;
else
} else {
return b;
}
}
@Override
@@ -1,11 +1,12 @@
package jadx.core.dex.instructions.args;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.ConstClassNode;
import jadx.core.dex.instructions.ConstStringNode;
import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.PhiInsn;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode;
@@ -14,17 +15,19 @@ import jadx.core.dex.nodes.parser.FieldValueAttr;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RegisterArg extends InsnArg {
public class RegisterArg extends InsnArg implements Named {
private static final Logger LOG = LoggerFactory.getLogger(RegisterArg.class);
protected final int regNum;
// not null after SSATransform pass
private SSAVar sVar;
public RegisterArg(int rn) {
this.regNum = rn;
}
public RegisterArg(int rn, ArgType type) {
this.typedVar = new TypedVar(type);
this.type = type;
this.regNum = rn;
}
@@ -37,17 +40,51 @@ public class RegisterArg extends InsnArg {
return true;
}
public InsnNode getAssignInsn() {
for (InsnArg arg : getTypedVar().getUseList()) {
InsnNode assignInsn = arg.getParentInsn();
if (assignInsn == null)
// assign as function argument
return null;
else if (assignInsn.getResult() != null
&& assignInsn.getResult().getRegNum() == regNum)
return assignInsn;
public SSAVar getSVar() {
return sVar;
}
void setSVar(SSAVar sVar) {
this.sVar = sVar;
}
public String getName() {
if (sVar == null) {
return null;
}
return null;
return sVar.getName();
}
public void setName(String name) {
if (sVar != null) {
sVar.setName(name);
}
}
public boolean isNameEquals(InsnArg arg) {
String n = getName();
if (n == null || !(arg instanceof Named)) {
return false;
}
return n.equals(((Named) arg).getName());
}
@Override
public void setType(ArgType type) {
if (sVar != null) {
sVar.setType(type);
}
}
public void mergeDebugInfo(ArgType type, String name) {
setType(type);
setName(name);
}
public RegisterArg duplicate() {
RegisterArg dup = new RegisterArg(getRegNum(), getType());
dup.setSVar(sVar);
return dup;
}
/**
@@ -57,69 +94,115 @@ public class RegisterArg extends InsnArg {
*/
public Object getConstValue(DexNode dex) {
InsnNode parInsn = getAssignInsn();
if (parInsn != null) {
InsnType insnType = parInsn.getType();
switch (insnType) {
case CONST:
return parInsn.getArg(0);
case CONST_STR:
return ((ConstStringNode) parInsn).getString();
case CONST_CLASS:
return ((ConstClassNode) parInsn).getClsType();
case SGET:
FieldInfo f = (FieldInfo) ((IndexInsnNode) parInsn).getIndex();
FieldNode fieldNode = dex.resolveField(f);
if (fieldNode != null) {
FieldValueAttr attr = (FieldValueAttr) fieldNode.getAttributes().get(AttributeType.FIELD_VALUE);
if (attr != null) {
return attr.getValue();
}
} else {
LOG.warn("Field {} not found in dex {}", f, dex);
if (parInsn == null) {
return null;
}
InsnType insnType = parInsn.getType();
switch (insnType) {
case CONST:
return parInsn.getArg(0);
case CONST_STR:
return ((ConstStringNode) parInsn).getString();
case CONST_CLASS:
return ((ConstClassNode) parInsn).getClsType();
case SGET:
FieldInfo f = (FieldInfo) ((IndexInsnNode) parInsn).getIndex();
FieldNode fieldNode = dex.resolveField(f);
if (fieldNode != null) {
FieldValueAttr attr = fieldNode.get(AType.FIELD_VALUE);
if (attr != null) {
return attr.getValue();
}
break;
}
} else {
LOG.warn("Field {} not found in dex {}", f, dex);
}
break;
}
return null;
}
@Override
public boolean isThis() {
if (isRegister()) {
String name = getTypedVar().getName();
if (name != null && name.equals("this")) {
return true;
}
// maybe it was moved from 'this' register
InsnNode ai = getAssignInsn();
if (ai != null && ai.getType() == InsnType.MOVE) {
InsnArg arg = ai.getArg(0);
if (arg != this && arg.isThis()) {
return true;
}
if ("this".equals(getName())) {
return true;
}
// maybe it was moved from 'this' register
InsnNode ai = getAssignInsn();
if (ai != null && ai.getType() == InsnType.MOVE) {
InsnArg arg = ai.getArg(0);
if (arg != this) {
return arg.isThis();
}
}
return false;
}
public InsnNode getAssignInsn() {
if (sVar == null) {
return null;
}
return sVar.getAssign().getParentInsn();
}
public InsnNode getPhiAssignInsn() {
PhiInsn usePhi = sVar.getUsedInPhi();
if (usePhi != null) {
return usePhi;
}
InsnNode parent = sVar.getAssign().getParentInsn();
if (parent != null && parent.getType() == InsnType.PHI) {
return parent;
}
return null;
}
public boolean equalRegisterAndType(RegisterArg arg) {
return regNum == arg.regNum && type.equals(arg.type);
}
@Override
public int hashCode() {
return regNum * 31 + typedVar.hashCode();
return (regNum * 31 + type.hashCode()) * 31 + (sVar != null ? sVar.hashCode() : 0);
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof RegisterArg)) {
return false;
}
RegisterArg other = (RegisterArg) obj;
if (regNum != other.regNum) return false;
if (!typedVar.equals(other.typedVar)) return false;
if (regNum != other.regNum) {
return false;
}
if (!type.equals(other.type)) {
return false;
}
if (sVar != null && !sVar.equals(other.getSVar())) {
return false;
}
return true;
}
@Override
public String toString() {
return "(r" + regNum + " " + typedVar + ")";
StringBuilder sb = new StringBuilder();
sb.append("(r");
sb.append(regNum);
if (sVar != null) {
sb.append("_").append(sVar.getVersion());
}
if (getName() != null) {
sb.append(" '").append(getName()).append("'");
}
sb.append(" ");
sb.append(type);
sb.append(")");
return sb.toString();
}
}
@@ -0,0 +1,210 @@
package jadx.core.dex.instructions.args;
import jadx.core.dex.instructions.PhiInsn;
import java.util.ArrayList;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class SSAVar {
private final int regNum;
private final int version;
private VarName varName;
private int startUseAddr;
private int endUseAddr;
@NotNull
private RegisterArg assign;
private final List<RegisterArg> useList = new ArrayList<RegisterArg>(2);
@Nullable
private PhiInsn usedInPhi;
private ArgType type;
private boolean typeImmutable;
public SSAVar(int regNum, int v, @NotNull RegisterArg assign) {
this.regNum = regNum;
this.version = v;
this.assign = assign;
assign.setSVar(this);
startUseAddr = -1;
endUseAddr = -1;
}
public int getStartAddr() {
if (startUseAddr == -1) {
calcUsageAddrRange();
}
return startUseAddr;
}
public int getEndAddr() {
if (endUseAddr == -1) {
calcUsageAddrRange();
}
return endUseAddr;
}
private void calcUsageAddrRange() {
int start = Integer.MAX_VALUE;
int end = Integer.MIN_VALUE;
if (assign.getParentInsn() != null) {
int insnAddr = assign.getParentInsn().getOffset();
if (insnAddr >= 0) {
start = Math.min(insnAddr, start);
end = Math.max(insnAddr, end);
}
}
for (RegisterArg arg : useList) {
if (arg.getParentInsn() != null) {
int insnAddr = arg.getParentInsn().getOffset();
if (insnAddr >= 0) {
start = Math.min(insnAddr, start);
end = Math.max(insnAddr, end);
}
}
}
if (start != Integer.MAX_VALUE && end != Integer.MIN_VALUE) {
startUseAddr = start;
endUseAddr = end;
}
}
public int getRegNum() {
return regNum;
}
public int getVersion() {
return version;
}
@NotNull
public RegisterArg getAssign() {
return assign;
}
public void setAssign(@NotNull RegisterArg assign) {
this.assign = assign;
}
public List<RegisterArg> getUseList() {
return useList;
}
public int getUseCount() {
return useList.size();
}
public void use(RegisterArg arg) {
if (arg.getSVar() != null) {
arg.getSVar().removeUse(arg);
}
arg.setSVar(this);
useList.add(arg);
}
public void removeUse(RegisterArg arg) {
for (int i = 0, useListSize = useList.size(); i < useListSize; i++) {
if (useList.get(i) == arg) {
useList.remove(i);
break;
}
}
}
public void setUsedInPhi(@Nullable PhiInsn usedInPhi) {
this.usedInPhi = usedInPhi;
}
@Nullable
public PhiInsn getUsedInPhi() {
return usedInPhi;
}
public boolean isUsedInPhi() {
return usedInPhi != null;
}
public int getVariableUseCount() {
if (usedInPhi == null) {
return useList.size();
}
return useList.size() + usedInPhi.getResult().getSVar().getUseCount();
}
public void setType(ArgType type) {
ArgType acceptedType;
if (typeImmutable) {
// don't change type, just update types in useList
acceptedType = this.type;
} else {
acceptedType = type;
this.type = acceptedType;
}
assign.type = acceptedType;
for (int i = 0, useListSize = useList.size(); i < useListSize; i++) {
useList.get(i).type = acceptedType;
}
}
public void setTypeImmutable(ArgType type) {
setType(type);
this.typeImmutable = true;
}
public boolean isTypeImmutable() {
return typeImmutable;
}
public void setName(String name) {
if (name != null) {
if (varName == null) {
varName = new VarName();
}
varName.setName(name);
}
}
public String getName() {
if (varName == null) {
return null;
}
return varName.getName();
}
public VarName getVarName() {
return varName;
}
public void setVarName(VarName varName) {
this.varName = varName;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof SSAVar)) {
return false;
}
SSAVar ssaVar = (SSAVar) o;
return regNum == ssaVar.regNum && version == ssaVar.version;
}
@Override
public int hashCode() {
return 31 * regNum + version;
}
@Override
public String toString() {
return "r" + regNum + "_" + version;
}
}
@@ -0,0 +1,66 @@
package jadx.core.dex.instructions.args;
public class TypeImmutableArg extends RegisterArg {
private boolean isThis;
public TypeImmutableArg(int rn, ArgType type) {
super(rn, type);
}
@Override
public boolean isTypeImmutable() {
return true;
}
@Override
public void setType(ArgType type) {
// not allowed
}
public void markAsThis() {
this.isThis = true;
}
@Override
public boolean isThis() {
return isThis;
}
@Override
public String getName() {
if (isThis) {
return "this";
}
return super.getName();
}
@Override
void setSVar(SSAVar sVar) {
if (isThis) {
sVar.setName("this");
}
sVar.setTypeImmutable(type);
super.setSVar(sVar);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof TypeImmutableArg)) {
return false;
}
if (!super.equals(obj)) {
return false;
}
TypeImmutableArg that = (TypeImmutableArg) obj;
return isThis == that.isThis;
}
@Override
public int hashCode() {
return 31 * super.hashCode() + (isThis ? 1 : 0);
}
}
@@ -1,71 +1,31 @@
package jadx.core.dex.instructions.args;
import java.util.List;
public abstract class Typed {
TypedVar typedVar;
public TypedVar getTypedVar() {
return typedVar;
}
protected ArgType type;
public ArgType getType() {
return typedVar.getType();
return type;
}
public boolean merge(Typed var) {
return typedVar.merge(var.getTypedVar());
public void setType(ArgType type) {
this.type = type;
}
public boolean merge(ArgType var) {
return typedVar.merge(var);
public boolean isTypeImmutable() {
return false;
}
public void forceSetTypedVar(TypedVar arg) {
this.typedVar = arg;
}
public void mergeDebugInfo(Typed arg) {
merge(arg);
mergeName(arg);
}
protected void mergeName(Typed arg) {
getTypedVar().mergeName(arg.getTypedVar());
}
public boolean replaceTypedVar(Typed var) {
TypedVar curVar = this.typedVar;
TypedVar newVar = var.typedVar;
if (curVar == newVar) {
return false;
public boolean merge(ArgType newType) {
ArgType m = ArgType.merge(type, newType);
if (m != null && !m.equals(type)) {
setType(m);
return true;
}
if (curVar != null) {
if (curVar.isImmutable()) {
moveInternals(newVar, curVar);
} else {
newVar.merge(curVar);
moveInternals(curVar, newVar);
this.typedVar = newVar;
}
} else {
this.typedVar = newVar;
}
return true;
return false;
}
private void moveInternals(TypedVar from, TypedVar to) {
List<InsnArg> curUseList = from.getUseList();
if (curUseList.size() != 0) {
for (InsnArg arg : curUseList) {
if (arg != this) {
arg.forceSetTypedVar(to);
}
}
to.getUseList().addAll(curUseList);
curUseList.clear();
}
to.mergeName(from);
public boolean merge(InsnArg arg) {
return merge(arg.getType());
}
}
@@ -1,94 +0,0 @@
package jadx.core.dex.instructions.args;
import java.util.ArrayList;
import java.util.List;
public class TypedVar {
private ArgType type;
private final List<InsnArg> useList = new ArrayList<InsnArg>(2);
private String name;
public TypedVar(ArgType initType) {
this.type = initType;
}
public ArgType getType() {
return type;
}
/**
* This method must be used very carefully
*/
public void forceSetType(ArgType newType) {
type = newType;
}
public boolean merge(TypedVar typedVar) {
return merge(typedVar.getType());
}
public boolean merge(ArgType mtype) {
ArgType res = ArgType.merge(type, mtype);
if (res != null && !type.equals(res)) {
this.type = res;
return true;
} else {
return false;
}
}
public List<InsnArg> getUseList() {
return useList;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void mergeName(TypedVar arg) {
String name = arg.getName();
if (name != null) {
setName(name);
} else if (getName() != null) {
arg.setName(getName());
}
}
public boolean isImmutable() {
return false;
}
@Override
public int hashCode() {
return type.hashCode() * 31 + (name == null ? 0 : name.hashCode());
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (!(obj instanceof TypedVar)) return false;
TypedVar other = (TypedVar) obj;
if (!type.equals(other.type)) return false;
if (name == null) {
if (other.name != null) return false;
} else if (!name.equals(other.name)) {
return false;
}
return true;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
if (name != null)
sb.append('\'').append(name).append("' ");
sb.append(type);
return sb.toString();
}
}

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