Compare commits

...

95 Commits

Author SHA1 Message Date
Skylot cc29da8e81 build: fix release build 2019-12-07 15:31:24 +00:00
Skylot d1a6841c20 fix: inline assign in complex conditions (#699) 2019-11-30 16:32:29 +00:00
Skylot 600842a1a6 fix: resolve error if input file don't has extension 2019-11-30 16:22:09 +00:00
Skylot 8ba3e935a5 build: update dependencies and gradle 2019-11-24 20:34:36 +00:00
Skylot 87504dd2cc refactor: additional checks for ssa vars and registers 2019-11-24 20:33:19 +00:00
Skylot e4e6f37949 fix: sort inner classes and methods by source lines 2019-11-19 20:05:21 +00:00
Skylot 4b314e9d99 fix: don't eliminate StringBuilder if no String arg present 2019-11-19 18:26:12 +00:00
Skylot a48ce296b8 fix: resolve code generation error for interface methods (#775) 2019-11-05 09:31:12 +00:00
Jan S cf3e17c4b8 feat(gui): support APK signature v3 (PR #773) 2019-11-01 19:31:24 +03:00
Skylot bae36f9720 fix: merge const block before return (#699) 2019-10-31 15:47:29 +00:00
Skylot 11db454b84 fix: duplicate result arg on instruction copy 2019-10-30 20:59:14 +00:00
Skylot 1b60c1d1a8 test: print smali code for debug purpose 2019-10-30 13:42:58 +00:00
Skylot 8321d5e380 fix: preserve arg type on PHI insn inline (#718) 2019-10-28 17:19:52 +00:00
Skylot 64c9ce2ab0 build: update sonarqube 2019-10-27 19:35:43 +00:00
Skylot 08f9a90c95 fix: force cast for null args in method invoke (temp fix for #724) 2019-10-27 16:53:25 +00:00
Skylot 9f06d6744e fix: increase region iterative traversal limit (#767) 2019-10-27 16:19:58 +00:00
Skylot f228a72118 fix(gui): fix search if class contains not generated inner class (#755) 2019-10-21 18:46:55 +01:00
Jan S 3249a5e0bc fix: workaround for IntelliJ bug on import line in build.gradle (PR #766) 2019-10-17 20:23:44 +03:00
Skylot d1ac43de33 fix(gui): add default contructor for classes serialized with GSON (#752) 2019-10-17 17:35:01 +03:00
Skylot 00f5e83506 fix: handle incorrect args count in signature (#763) 2019-10-17 16:51:06 +03:00
Skylot d3ecc1f640 fix: add dummy class if class loading exception occur (#763) 2019-10-17 16:51:03 +03:00
Jan S 902247fcdb fix: don't stop loading classes in case of an error (PR #764)
* fix: don't stop loading classes in case of an error
* style: reformat code
2019-10-15 20:25:18 +03:00
Skylot bd9e1096cc fix: handle methods with all NOPs (#744) 2019-08-30 15:37:38 +01:00
Skylot db892adf34 fix: don't run class process from visitors to avoid deadlock (#743) 2019-08-27 17:24:18 +01:00
Skylot 1cbaad3ec9 fix: make correct class members loading in jadx api (#742) 2019-08-25 19:53:12 +01:00
Skylot 401d08ea49 refactor: move all smali libs usage to one utility class 2019-08-21 14:45:32 +01:00
Skylot ba17f7bc00 refactor: move type with outer generic to different class 2019-08-15 21:43:57 +01:00
Skylot db2b537380 fix: try to resolve generic type variables (#662) 2019-08-15 21:39:43 +01:00
Skylot 06f26ef8f5 refactor: use enum for wildcard bounds instead of int 2019-08-15 14:31:30 +01:00
Skylot a71bb7a532 fix(gui): yet another fix for broken find usage action 2019-08-12 10:32:38 +01:00
Skylot 99934b5100 chore: update dependencies 2019-08-12 10:05:24 +01:00
Skylot ff5f6fca3c fix(gui): fix "Go to declaration" and "Find usage" menu actions 2019-08-11 22:03:47 +03:00
Skylot 3578f7d68f fix(gui): use editor font on tabs 2019-08-11 21:40:46 +03:00
Skylot 7bc01dcfa8 fix(gui): ignore mouse click on empty space in tree (#737) 2019-08-11 19:39:05 +03:00
Skylot bc7a748420 feat(cli): add options for change log level (#735) 2019-08-08 13:14:36 +03:00
Skylot c0194d025d refactor: fix misuse of immutable type flag 2019-08-03 17:31:13 +03:00
Skylot 19ca8a096b chore: resolve minor code issues in debug info parser 2019-08-03 14:19:54 +03:00
Skylot cf5bfc297b test: fix regression for code auto check 2019-08-02 21:05:03 +03:00
Skylot a17f9136dd refactor: enable class unloading after code generation 2019-08-01 23:29:30 +03:00
Skylot 7d07fb0b77 chore: fix issues reported by lgtm.com 2019-08-01 12:14:29 +03:00
Skylot 99935bada6 docs: update readme and contributing rules 2019-07-31 21:40:23 +03:00
Skylot be9dae57b9 fix: add explicit cast for byte literal in method invoke (#719) 2019-07-30 22:46:28 +03:00
Skylot 4629043721 fix: convert inner enums and fix inner classes reference (#719) 2019-07-30 20:49:31 +03:00
Skylot 068234f0ca fix: remove synchronization lock for code generation (#726) 2019-07-29 14:55:50 +03:00
Skylot ccb8ed1394 fix: add assign for inlined getter methods 2019-07-29 12:48:38 +03:00
Skylot 8d68d409eb test: another deboxing issue 2019-07-28 21:09:56 +03:00
Skylot e842e022ba fix: use nice name for 'package-private' in modifiers change message 2019-07-28 20:42:07 +03:00
Skylot 1e6b30343c fix: several improvements for multi-variable type search (#720) 2019-07-28 20:22:28 +03:00
Skylot ddedb8d8a0 fix: don't override type of method parameter in const deboxing (#723) 2019-07-26 16:14:27 +03:00
Skylot 472aa52706 fix: resolve some multi-thread issues 2019-07-25 21:53:37 +03:00
Skylot ab97084058 refactor: move passes list to root node 2019-07-25 17:54:04 +03:00
Skylot 0911b2dc2f test: NYI test for issue #722 2019-07-24 17:00:05 +03:00
Skylot fd7d08cb10 feat: initial deboxing implementation (#717) 2019-07-23 20:37:37 +03:00
Skylot 3ae8359408 fix: improve exception handler remove (#703) 2019-07-22 20:38:16 +03:00
Skylot 6b76a3c787 fix: protect method from second load 2019-07-22 18:43:02 +03:00
Skylot 9fbf9ef667 fix(gui): compare files extension in case insensitive way 2019-07-22 18:43:02 +03:00
Skylot c8de7b97dd fix: instead commenting move constructor call to the top (#704) 2019-07-21 19:45:22 +03:00
Skylot b32dc17dd7 fix: don't change AST before checks in ternary transform (#710) 2019-07-20 21:33:20 +03:00
Skylot 7c53b985cd refactor(gui): remove JCertificate node 2019-07-19 18:19:08 +03:00
Skylot c8df26f227 feat(gui): add class links for AndroidManifest.xml and other minor fixes 2019-07-19 18:03:40 +03:00
Skylot 3bc9671905 perf(gui): speed up line numbers rendering (#714) 2019-07-18 23:19:06 +03:00
Skylot 7fd959e6e3 refactor: improve variables handling in instruction wrapping 2019-07-17 22:53:00 +03:00
Skylot 24dc68652e fix: check that iteration variable in for-each loop not used outside (#708) 2019-07-17 22:42:33 +03:00
Skylot aad2d24c58 fix: unbind unused ssa variable after ternary conversion (#708) 2019-07-16 19:44:48 +03:00
Skylot 15d56abeb6 fix: read correct buffer size for string pool parsing (#712) 2019-07-15 21:19:58 +03:00
Skylot d89ec67888 style: resolve compiler warnings 2019-07-15 17:12:40 +03:00
Skylot f9f840fb9d refactor: remove redundant FieldArg and change arith one arg insn 2019-07-15 17:01:02 +03:00
Skylot 8e8a2faa10 fix(res): skip string if parsing failed (#712) 2019-07-14 17:06:19 +03:00
Skylot 0c2784bb42 refactor: inline fields in arithmetic operations 2019-07-14 15:09:01 +03:00
Skylot c555cd0825 fix: rename packages with reserved names (#711) 2019-07-14 13:13:00 +03:00
Skylot 92e28326a4 misc: don't add same edge insn several times 2019-07-13 13:24:52 +03:00
Skylot 2dbdd1f079 fix: support instructions removing in SimplifyVisitor 2019-07-13 13:19:58 +03:00
Skylot fc58022d56 misc: show shorter exception stacktrace in code 2019-07-13 13:17:22 +03:00
Skylot ed9fe8a573 fix: incorrect init values of inherited fields 2019-07-13 13:10:23 +03:00
Skylot 49e234d9f8 fix: improve finally extraction 2019-07-12 23:26:46 +03:00
Skylot a587ce88ea fix: ignore finally extraction with only one 'if' instruction (#709) 2019-07-12 21:21:14 +03:00
Skylot a530371b6f fix: improve StringBuilder elimination (#704) 2019-07-11 20:07:14 +03:00
Skylot 0c5a83c021 style: fix code style in test 2019-07-10 21:32:11 +03:00
Skylot 12bb632371 fix: always cast null objects in overloaded method (#707) 2019-07-10 21:11:02 +03:00
Skylot e4fc6774b1 fix: make correct hash calculation for GenericObject type (#705) 2019-07-10 16:58:52 +03:00
Skylot f57dfb3f2e test: check method override with generic arguments (#701) 2019-07-09 13:08:32 +03:00
Skylot c3f7a049d8 fix: ignore incorrect dex files in apk (#700) 2019-07-08 12:24:54 +03:00
Skylot 3eee83c2f2 fix: adjust insn reorder check in code shrink visitor (#695) 2019-07-07 14:18:21 +03:00
Skylot ed8c662631 fix: add generic types propagation (#695) 2019-07-06 19:12:31 +03:00
Skylot 850df18d7c refactor: update duplicate methods in InsnArg classes 2019-07-05 20:55:00 +03:00
Skylot 7f4da306c9 refactor: remove cloning library dependency 2019-07-05 20:45:28 +03:00
Skylot 424a8ffaf4 fix: inline constant strings (#685) 2019-07-05 19:10:57 +03:00
Skylot 8410e62531 fix: force one branch ternary in constructors (#685) 2019-07-05 17:14:46 +03:00
Skylot 533b686e0b fix: comment out instructions also before other constructor call (#685) 2019-07-05 17:05:38 +03:00
Skylot c6c54f90dc fix: comment out instructions before super call in constructor (#685) 2019-07-03 14:39:21 +03:00
Kend 0f5fd4e48a fix(gui): update Chinese translation (PR #697) 2019-06-27 12:39:48 +03:00
Skylot a7247e8a88 build: remove unused test-app submodule 2019-06-27 11:59:25 +03:00
Skylot c10a30346b style: reformat gradle files 2019-06-27 11:53:56 +03:00
Skylot 436e86fdf2 build: update gradle and dependencies 2019-06-27 11:16:44 +03:00
Jan S 29a137bde3 fix: jadx-gui.bat and jadx.bat do not work (#692) (PR #694) 2019-06-21 17:44:45 +03:00
288 changed files with 8626 additions and 3102 deletions
@@ -8,10 +8,9 @@ assignees: ''
---
**Checks before report**
- [ ] check [latest unstable build](https://bintray.com/skylot/jadx/unstable/_latestVersion#files) (maybe issue already fixed)
- [ ] search existing issues by exception message
(for example `JadxRuntimeException: Can't find immediate dominator for block`)
- [ ] check [Troubleshooting Q&A](https://github.com/skylot/jadx/wiki/Troubleshooting-Q&A) section on wiki
- check [latest unstable build](https://bintray.com/skylot/jadx/unstable/_latestVersion#files) (maybe issue already fixed)
- check [Troubleshooting Q&A](https://github.com/skylot/jadx/wiki/Troubleshooting-Q&A) section on wiki
- search existing issues by exception message
**Describe error**
- provide full name of method or class with error
+10
View File
@@ -0,0 +1,10 @@
---
name: Feature Request
about: Suggest an idea for jadx
title: "[feature]"
labels: new feature
assignees: ''
---
*Describe your idea:*
+5
View File
@@ -0,0 +1,5 @@
:exclamation: Please review the [guidelines for contributing](../CONTRIBUTING.md#Pull-Request-Process)
### Description
Please describe your pull request.
Reference issue it fix.
+6 -1
View File
@@ -19,13 +19,18 @@ java-11:
image: openjdk:11
script: ./gradlew clean build
java-12:
stage: test
image: gradle:jdk12 # latest gradle and jdk12
script: gradle clean build
check:
stage: check
image: openjdk:8
script:
- export JADX_LAST_TAG="$(git describe --abbrev=0 --tags)"
- export JADX_VERSION="${JADX_LAST_TAG:1}-dev-$(git rev-parse --short HEAD)"
- ./gradlew clean sonarqube -Dsonar.host.url=$SONAR_HOST -Dsonar.organization=$SONAR_ORG -Dsonar.login=$SONAR_TOKEN -Dsonar.branch.name=dev
- ./gradlew clean sonarqube -Dsonar.host.url="${SONAR_HOST}" -Dsonar.projectKey=jadx -Dsonar.organization="${SONAR_ORG}" -Dsonar.login="${SONAR_TOKEN}" -Dsonar.branch.name=dev || echo "Skip sonar build and upload"
- ./gradlew clean dist
artifacts:
paths:
-3
View File
@@ -1,3 +0,0 @@
[submodule "jadx-test-app/test-app"]
path = jadx-test-app/test-app
url = git://github.com/skylot/jadx-test-app.git
+3 -2
View File
@@ -1,5 +1,5 @@
language: java
sudo: false
os: linux
dist: trusty
# don't build on tag push
@@ -22,7 +22,6 @@ env:
jdk:
- openjdk8
- oraclejdk8
- openjdk11
script: ./gradlew clean build
@@ -35,6 +34,8 @@ jobs:
script: bash scripts/travis-master.sh
- stage: deploy-release
language: node_js
jdk: openjdk8
node_js: 11
if: branch = release AND repo = env(MAIN_REPO) AND type = push
script: bash scripts/travis-release.sh
+76
View File
@@ -0,0 +1,76 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at skylot@gmail.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq
+33 -6
View File
@@ -1,7 +1,34 @@
# Contribution
# Contributing
To support this project you can:
- Post thoughts about new features/optimizations that important to you
- Submit bug using one of following patterns:
* Java code examples which decompiles incorrectly
* Error log and link to _public available_ apk file or app page on Google play
Please note we have a [code of conduct](CODE_OF_CONDUCT.md), please follow it in all your interactions with the project.
## Open Issue
1. Before proceed please do:
- check [latest unstable build](https://bintray.com/skylot/jadx/unstable/_latestVersion#files) (maybe issue already fixed)
- check [Troubleshooting Q&A](https://github.com/skylot/jadx/wiki/Troubleshooting-Q&A) section on wiki
- search existing issues by exception message
2. Describe error
- provide full name of method or class with error
- provide full java stacktrace
**Note**: no need to copy method fallback code (commented pseudocode)
- attach or provide link to apk file (double check apk version)
**Note**: GitHub don't allow attach files with `.apk` extension, but you can change extension by adding `.zip` at the end :)
## Pull Request Process
1. Please don't submit any code style fixes, dependencies updates or other changes which are not fixing any issues.
1. Before open a PR please discuss the change you wish to make via issue. PR without corresponding issue will be rejected.
1. Use only features and API from Java 8 or below.
1. If possible don't add additional dependencies especially if they are big.
1. Make sure your code is correctly formatted, see description here: [Code Formatting](https://github.com/skylot/jadx/wiki/Code-Formatting).
1. Make sure your changes is passing build: `./gradlew clean build dist`
+54 -40
View File
@@ -2,6 +2,7 @@
[![Build Status](https://travis-ci.org/skylot/jadx.png?branch=master)](https://travis-ci.org/skylot/jadx)
[![Code Coverage](https://codecov.io/gh/skylot/jadx/branch/master/graph/badge.svg)](https://codecov.io/gh/skylot/jadx)
[![Alerts from lgtm.com](https://img.shields.io/lgtm/alerts/g/skylot/jadx.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/skylot/jadx/alerts/)
[![SonarQube Bugs](https://sonarcloud.io/api/project_badges/measure?project=jadx&metric=bugs)](https://sonarcloud.io/dashboard?id=jadx)
[![License](http://img.shields.io/:license-apache-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0.html)
[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)
@@ -10,52 +11,59 @@
Command line and GUI tools for produce Java source code from Android Dex and Apk files
**Main features:**
- decompile Dalvik bytecode to java classes from APK, dex, aar and zip files
- decode `AndroidManifest.xml` and other resources from `resources.arsc`
- deobfuscator included
**jadx-gui features:**
- view decompiled code with highlighted syntax
- jump to declaration
- find usage
- full text search
See these features in action here: [jadx-gui features overview](https://github.com/skylot/jadx/wiki/jadx-gui-features-overview)
![jadx-gui screenshot](https://i.imgur.com/h917IBZ.png)
### Downloads
### Download
- latest [unstable build: ![Download](https://api.bintray.com/packages/skylot/jadx/unstable/images/download.svg) ](https://bintray.com/skylot/jadx/unstable/_latestVersion#files)
- release from [github: ![Latest release](https://img.shields.io/github/release/skylot/jadx.svg)](https://github.com/skylot/jadx/releases/latest)
- release from [bintray: ![Download](https://api.bintray.com/packages/skylot/jadx/releases/images/download.svg) ](https://bintray.com/skylot/jadx/releases/_latestVersion#files)
After download unpack zip file go to `bin` directory and run:
- `jadx` - command line version
- `jadx-gui` - graphical version
- `jadx-gui` - UI version
On Windows run `.bat` files with double-click\
**Note:** ensure you have installed Java 8 64-bit version
**Note:** ensure you have installed Java 8 or later 64-bit version.
For windows you can download it from [adoptopenjdk.net](https://adoptopenjdk.net/releases.html?variant=openjdk11&jvmVariant=hotspot#x64_win) (select "Install JRE").
### Install
1. Arch linux
```bash
sudo pacman -S jadx
```
2. macOS
```bash
brew install jadx
```
### Related projects:
- [PyJadx](https://github.com/romainthomas/pyjadx) - python binding for jadx by [@romainthomas](https://github.com/romainthomas)
### Building jadx from source
### Build from source
JDK 8 or higher must be installed:
git clone https://github.com/skylot/jadx.git
cd jadx
./gradlew dist
```
git clone https://github.com/skylot/jadx.git
cd jadx
./gradlew dist
```
(on Windows, use `gradlew.bat` instead of `./gradlew`)
Scripts for run jadx will be placed in `build/jadx/bin`
and also packed to `build/jadx-<version>.zip`
### macOS
You can install using brew:
brew install jadx
### Run
Run **jadx** on itself:
cd build/jadx/
bin/jadx -d out lib/jadx-core-*.jar
# or
bin/jadx-gui lib/jadx-core-*.jar
### Usage
```
jadx[-gui] [options] <input file> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc)
@@ -63,12 +71,12 @@ options:
-d, --output-dir - output directory
-ds, --output-dir-src - output directory for sources
-dr, --output-dir-res - output directory for resources
-j, --threads-count - processing threads count
-r, --no-res - do not decode resources
-s, --no-src - do not decompile source code
--single-class - decompile a single class
--output-format - can be 'java' or 'json' (default: java)
--output-format - can be 'java' or 'json', default: java
-e, --export-gradle - save as android gradle project
-j, --threads-count - processing threads count, default: 4
--show-bad-code - show inconsistent code (incorrectly decompiled)
--no-imports - disable use of imports, always write entire package name
--no-debug-info - disable debug info
@@ -77,35 +85,41 @@ options:
--escape-unicode - escape non latin characters in strings (with \u)
--respect-bytecode-access-modifiers - don't change original access modifiers
--deobf - activate deobfuscation
--deobf-min - min length of name, renamed if shorter (default: 3)
--deobf-max - max length of name, renamed if longer (default: 64)
--deobf-min - min length of name, renamed if shorter, default: 3
--deobf-max - max length of name, renamed if longer, default: 64
--deobf-rewrite-cfg - force to save deobfuscation map
--deobf-use-sourcename - use source file name as class name alias
--rename-flags - what to rename, comma-separated, 'case' for system case sensitivity, 'valid' for java identifiers, 'printable' characters, 'none' or 'all'
--rename-flags - what to rename, comma-separated, 'case' for system case sensitivity, 'valid' for java identifiers, 'printable' characters, 'none' or 'all' (default)
--fs-case-sensitive - treat filesystem as case sensitive, false by default
--cfg - save methods control flow graph to dot file
--raw-cfg - save methods control flow graph (use raw instructions)
-f, --fallback - make simple dump (using goto instead of 'if', 'for', etc)
-v, --verbose - verbose output
-v, --verbose - verbose output (set --log-level to DEBUG)
-q, --quiet - turn off output (set --log-level to QUIET)
--log-level - set log level, values: QUIET, PROGRESS, ERROR, WARN, INFO, DEBUG, default: PROGRESS
--version - print jadx version
-h, --help - print this help
Example:
jadx -d out classes.dex
jadx --rename-flags "none" classes.dex
jadx --rename-flags "valid,printable" classes.dex
jadx --log-level error app.apk
```
These options also worked on jadx-gui running from command line and override options from preferences dialog
### Troubleshooting
##### Out of memory error:
- Reduce processing threads count (`-j` option)
- Increase maximum java heap size:
* command line (example for linux):
`JAVA_OPTS="-Xmx4G" jadx -j 1 some.apk`
* edit 'jadx' script (jadx.bat on Windows) and setup bigger heap size:
`DEFAULT_JVM_OPTS="-Xmx2500M"`
Please check wiki page [Troubleshooting Q&A](https://github.com/skylot/jadx/wiki/Troubleshooting-Q&A)
### Contributing
To support this project you can:
- Post thoughts about new features/optimizations that important to you
- Submit decompilation issues, please read before proceed: [Open issue](CONTRIBUTING.md#Open-Issue)
- Open pull request, please follow these rules: [Pull Request Process](CONTRIBUTING.md#Pull-Request-Process)
### Related projects:
- [PyJadx](https://github.com/romainthomas/pyjadx) - python binding for jadx by [@romainthomas](https://github.com/romainthomas)
---------------------------------------
*Licensed under the Apache 2.0 License*
*Copyright 2018 by Skylot*
*Copyright 2019 by Skylot*
+12 -20
View File
@@ -1,9 +1,7 @@
import com.diffplug.spotless.LineEnding
plugins {
id 'org.sonarqube' version '2.7'
id 'com.github.ben-manes.versions' version '0.21.0'
id "com.diffplug.gradle.spotless" version "3.21.1"
id 'org.sonarqube' version '2.8'
id 'com.github.ben-manes.versions' version '0.27.0'
id "com.diffplug.gradle.spotless" version "3.26.0"
}
ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev"
@@ -18,9 +16,6 @@ allprojects {
version = jadxVersion
tasks.withType(JavaCompile) {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
if (!"$it".contains(':jadx-samples:')) {
options.compilerArgs << '-Xlint' << '-Xlint:unchecked' << '-Xlint:deprecation'
}
@@ -38,14 +33,15 @@ allprojects {
}
dependencies {
compile 'org.slf4j:slf4j-api:1.7.26'
compile 'org.slf4j:slf4j-api:1.7.29'
testCompile 'ch.qos.logback:logback-classic:1.2.3'
testCompile 'org.hamcrest:hamcrest-library:2.1'
testCompile 'org.mockito:mockito-core:2.25.1'
testCompile 'org.hamcrest:hamcrest-library:2.2'
testCompile 'org.mockito:mockito-core:3.1.0'
testCompile 'org.assertj:assertj-core:3.14.0'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.4.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.4.1'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.5.2'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.5.2'
testCompile 'org.eclipse.jdt.core.compiler:ecj:4.6.1'
}
@@ -96,16 +92,16 @@ spotless {
eclipse().configFile 'config/code-formatter/eclipse.xml'
removeUnusedImports()
lineEndings(LineEnding.UNIX)
lineEndings(com.diffplug.spotless.LineEnding.UNIX)
encoding("UTF-8")
trimTrailingWhitespace()
endWithNewline()
}
format 'misc', {
target '**/*.gradle', '**/*.md', '**/*.xml', '**/.gitignore', '**/.properties'
targetExclude "jadx-test-app/test-app/**", ".gradle/**", ".idea/**"
targetExclude ".gradle/**", ".idea/**"
lineEndings(LineEnding.UNIX)
lineEndings(com.diffplug.spotless.LineEnding.UNIX)
encoding("UTF-8")
trimTrailingWhitespace()
endWithNewline()
@@ -155,10 +151,6 @@ task samples(dependsOn: 'jadx-samples:samples') {
group 'jadx'
}
task testAppCheck(dependsOn: 'jadx-test-app:testAppCheck') {
group 'jadx'
}
task cleanBuildDir(type: Delete) {
group 'jadx'
delete buildDir
+3 -1
View File
@@ -78,7 +78,9 @@
<!-- annotations -->
<module name="AnnotationLocation"/>
<module name="AnnotationUseStyle"/>
<module name="AnnotationUseStyle">
<property name="elementStyle" value="compact"/>
</module>
<module name="MissingOverride"/>
<!-- <module name="MissingDeprecated"/> -->
Binary file not shown.
+1 -1
View File
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored
+31 -20
View File
@@ -1,5 +1,21 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
@@ -28,7 +44,7 @@ APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
@@ -109,8 +125,8 @@ if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
@@ -138,19 +154,19 @@ if $cygwin ; then
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
i=`expr $i + 1`
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
@@ -159,14 +175,9 @@ save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"
Vendored
+17 -1
View File
@@ -1,3 +1,19 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@@ -14,7 +30,7 @@ set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
+18 -22
View File
@@ -1,28 +1,24 @@
apply plugin: 'application'
mainClassName = 'jadx.cli.JadxCLI'
applicationName = 'jadx'
plugins {
id 'application'
}
dependencies {
compile(project(':jadx-core'))
compile 'com.beust:jcommander:1.74'
compile 'ch.qos.logback:logback-classic:1.2.3'
compile(project(':jadx-core'))
compile 'com.beust:jcommander:1.78'
compile 'ch.qos.logback:logback-classic:1.2.3'
}
application {
applicationName = 'jadx'
mainClassName = 'jadx.cli.JadxCLI'
applicationDefaultJvmArgs = ['-Xms128M', '-Xmx4g', '-XX:+UseG1GC']
}
applicationDistribution.with {
into('') {
from '../.'
include 'README.md'
include 'NOTICE'
include 'LICENSE'
}
}
startScripts {
defaultJvmOpts = ['-Xms128M', '-Xmx4g', '-XX:+UseG1GC']
doLast {
def str = windowsScript.text
str = str.replaceAll('set JAVA_EXE=%JAVA_HOME%/bin/java.exe', 'set JAVA_EXE="%JAVA_HOME%/bin/java.exe"')
windowsScript.text = str
}
into('') {
from '../.'
include 'README.md'
include 'NOTICE'
include 'LICENSE'
}
}
@@ -9,6 +9,8 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.jetbrains.annotations.Nullable;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.ParameterDescription;
import com.beust.jcommander.ParameterException;
@@ -80,7 +82,10 @@ public class JCommanderWrapper<T> {
opt.append(" ").append(p.getNames());
addSpaces(opt, maxNamesLen - opt.length() + 3);
opt.append("- ").append(p.getDescription());
addDefaultValue(args, f, opt);
String defaultValue = getDefaultValue(args, f, opt);
if (defaultValue != null) {
opt.append(", default: ").append(defaultValue);
}
out.println(opt);
}
out.println("Example:");
@@ -102,26 +107,26 @@ public class JCommanderWrapper<T> {
return fieldList;
}
private void addDefaultValue(JadxCLIArgs args, Field f, StringBuilder opt) {
Class<?> fieldType = f.getType();
if (fieldType == int.class) {
try {
int val = f.getInt(args);
opt.append(" (default: ").append(val).append(')');
} catch (Exception e) {
// ignore
@Nullable
private String getDefaultValue(JadxCLIArgs args, Field f, StringBuilder opt) {
try {
Class<?> fieldType = f.getType();
if (fieldType == int.class) {
return Integer.toString(f.getInt(args));
}
}
if (fieldType == String.class) {
try {
String val = (String) f.get(args);
if (fieldType == String.class) {
return (String) f.get(args);
}
if (Enum.class.isAssignableFrom(fieldType)) {
Enum<?> val = (Enum<?>) f.get(args);
if (val != null) {
opt.append(" (default: ").append(val).append(')');
return val.name();
}
} catch (Exception e) {
// ignore
}
} catch (Exception e) {
// ignore
}
return null;
}
private static void addSpaces(StringBuilder str, int count) {
+4 -2
View File
@@ -5,6 +5,7 @@ import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs;
import jadx.api.JadxDecompiler;
import jadx.api.impl.NoOpCodeCache;
import jadx.core.utils.exceptions.JadxArgsValidateException;
public class JadxCLI {
@@ -27,6 +28,7 @@ public class JadxCLI {
static int processAndSave(JadxCLIArgs inputArgs) {
JadxArgs args = inputArgs.toJadxArgs();
args.setCodeCache(new NoOpCodeCache());
JadxDecompiler jadx = new JadxDecompiler(args);
try {
jadx.load();
@@ -38,10 +40,10 @@ public class JadxCLI {
int errorsCount = jadx.getErrorsCount();
if (errorsCount != 0) {
jadx.printErrorsReport();
LOG.error("finished with errors");
LOG.error("finished with errors, count: {}", errorsCount);
} else {
LOG.info("done");
}
return errorsCount;
return 0;
}
}
@@ -8,15 +8,9 @@ import java.util.Locale;
import java.util.Set;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.beust.jcommander.IStringConverter;
import com.beust.jcommander.Parameter;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
import jadx.api.JadxArgs;
import jadx.api.JadxArgs.RenameEnum;
import jadx.api.JadxDecompiler;
@@ -114,9 +108,19 @@ public class JadxCLIArgs {
@Parameter(names = { "-f", "--fallback" }, description = "make simple dump (using goto instead of 'if', 'for', etc)")
protected boolean fallbackMode = false;
@Parameter(names = { "-v", "--verbose" }, description = "verbose output")
@Parameter(names = { "-v", "--verbose" }, description = "verbose output (set --log-level to DEBUG)")
protected boolean verbose = false;
@Parameter(names = { "-q", "--quiet" }, description = "turn off output (set --log-level to QUIET)")
protected boolean quiet = false;
@Parameter(
names = { "--log-level" },
description = "set log level, values: QUIET, PROGRESS, ERROR, WARN, INFO, DEBUG",
converter = LogHelper.LogLevelConverter.class
)
protected LogHelper.LogLevelEnum logLevel = LogHelper.LogLevelEnum.PROGRESS;
@Parameter(names = { "--version" }, description = "print jadx version")
protected boolean printVersion = false;
@@ -158,15 +162,7 @@ public class JadxCLIArgs {
if (threadsCount <= 0) {
throw new JadxException("Threads count must be positive, got: " + threadsCount);
}
if (verbose) {
ch.qos.logback.classic.Logger rootLogger =
(ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
// remove INFO ThresholdFilter
Appender<ILoggingEvent> appender = rootLogger.getAppender("STDOUT");
if (appender != null) {
appender.clearAllFilters();
}
}
LogHelper.setLogLevelFromArgs(this);
} catch (JadxException e) {
System.err.println("ERROR: " + e.getMessage());
jcw.printUsage();
@@ -339,15 +335,18 @@ public class JadxCLIArgs {
try {
set.add(RenameEnum.valueOf(s.toUpperCase(Locale.ROOT)));
} catch (IllegalArgumentException e) {
String values = Arrays.stream(RenameEnum.values())
.map(v -> '\'' + v.name().toLowerCase(Locale.ROOT) + '\'')
.collect(Collectors.joining(", "));
throw new IllegalArgumentException(
'\'' + s + "' is unknown for parameter " + paramName
+ ", possible values are " + values);
+ ", possible values are " + enumValuesString(RenameEnum.values()));
}
}
return set;
}
}
public static String enumValuesString(Enum<?>[] values) {
return Arrays.stream(values)
.map(v -> '\'' + v.name().toLowerCase(Locale.ROOT) + '\'')
.collect(Collectors.joining(", "));
}
}
@@ -0,0 +1,93 @@
package jadx.cli;
import org.slf4j.LoggerFactory;
import com.beust.jcommander.IStringConverter;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import jadx.api.JadxDecompiler;
public class LogHelper {
private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(LogHelper.class);
public enum LogLevelEnum {
QUIET(Level.OFF),
PROGRESS(Level.OFF),
ERROR(Level.ERROR),
WARN(Level.WARN),
INFO(Level.INFO),
DEBUG(Level.DEBUG);
private final Level level;
LogLevelEnum(Level level) {
this.level = level;
}
public Level getLevel() {
return level;
}
}
public static void setLogLevelFromArgs(JadxCLIArgs args) {
if (isCustomLogConfig()) {
return;
}
LogLevelEnum logLevel = args.logLevel;
if (args.quiet) {
logLevel = LogLevelEnum.QUIET;
} else if (args.verbose) {
logLevel = LogLevelEnum.DEBUG;
}
applyLogLevel(logLevel);
}
public static void applyLogLevel(LogLevelEnum logLevel) {
Logger rootLogger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
rootLogger.setLevel(logLevel.getLevel());
if (logLevel != LogLevelEnum.QUIET) {
// show progress for all levels except quiet
setLevelForClass(JadxCLI.class, Level.INFO);
setLevelForClass(JadxDecompiler.class, Level.INFO);
}
}
private static void setLevelForClass(Class<?> cls, Level level) {
((Logger) LoggerFactory.getLogger(cls)).setLevel(level);
}
/**
* Try to detect if user provide custom logback config via -Dlogback.configurationFile=
*/
private static boolean isCustomLogConfig() {
try {
String logbackConfig = System.getProperty("logback.configurationFile");
if (logbackConfig == null) {
return false;
}
LOG.debug("Use custom log config: {}", logbackConfig);
return true;
} catch (Exception e) {
LOG.error("Failed to detect custom log config", e);
}
return false;
}
public static class LogLevelConverter implements IStringConverter<LogLevelEnum> {
@Override
public LogLevelEnum convert(String value) {
try {
return LogLevelEnum.valueOf(value.toUpperCase());
} catch (Exception e) {
throw new IllegalArgumentException(
'\'' + value + "' is unknown log level, possible values are "
+ JadxCLIArgs.enumValuesString(LogLevelEnum.values()));
}
}
}
}
+1 -4
View File
@@ -1,14 +1,11 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
<encoder>
<pattern>%-5level - %msg%n</pattern>
</encoder>
</appender>
<root level="DEBUG">
<root level="INFO">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
+12 -14
View File
@@ -1,20 +1,18 @@
ext.jadxClasspath = 'clsp-data/android-5.1.jar'
dependencies {
runtime files(jadxClasspath)
runtime files('clsp-data/android-29-clst.jar')
runtime files('clsp-data/android-29-res.jar')
compile files('lib/dx-1.16.jar')
compile files('lib/dx-1.16.jar') // TODO: dx don't support java version > 9 (53)
compile 'org.ow2.asm:asm:7.1'
compile 'org.jetbrains:annotations:17.0.0'
compile 'uk.com.robust-it:cloning:1.9.12'
compile 'com.google.code.gson:gson:2.8.5'
compile 'org.ow2.asm:asm:7.2'
compile 'org.jetbrains:annotations:18.0.0'
compile 'com.google.code.gson:gson:2.8.6'
compile 'org.smali:baksmali:2.2.7'
compile('org.smali:smali:2.2.7') {
exclude group: 'com.google.guava'
}
compile 'com.google.guava:guava:27.1-jre'
compile 'org.smali:baksmali:2.3.4'
compile('org.smali:smali:2.3.4') {
exclude group: 'com.google.guava'
}
compile 'com.google.guava:guava:28.1-jre'
testCompile 'org.apache.commons:commons-lang3:3.8.1'
testCompile 'org.apache.commons:commons-lang3:3.9'
}
Binary file not shown.
@@ -0,0 +1,11 @@
package jadx.api;
import org.jetbrains.annotations.Nullable;
public interface ICodeCache {
void add(String clsFullName, ICodeInfo codeInfo);
@Nullable
ICodeInfo get(String clsFullName);
}
@@ -0,0 +1,11 @@
package jadx.api;
import java.util.Map;
public interface ICodeInfo {
String getCodeStr();
Map<Integer, Integer> getLineMapping();
Map<CodePosition, Object> getAnnotations();
}
@@ -8,6 +8,8 @@ import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import jadx.api.impl.InMemoryCodeCache;
public class JadxArgs {
public static final int DEFAULT_THREADS_COUNT = Math.max(1, Runtime.getRuntime().availableProcessors() / 2);
@@ -22,6 +24,8 @@ public class JadxArgs {
private File outDirSrc;
private File outDirRes;
private ICodeCache codeCache = new InMemoryCodeCache();
private int threadsCount = DEFAULT_THREADS_COUNT;
private boolean cfgOutput = false;
@@ -326,6 +330,14 @@ public class JadxArgs {
this.outputFormat = outputFormat;
}
public ICodeCache getCodeCache() {
return codeCache;
}
public void setCodeCache(ICodeCache codeCache) {
this.codeCache = codeCache;
}
@Override
public String toString() {
return "JadxArgs{" + "inputFiles=" + inputFiles
@@ -352,6 +364,7 @@ public class JadxArgs {
+ ", fsCaseSensitive=" + fsCaseSensitive
+ ", renameFlags=" + renameFlags
+ ", outputFormat=" + outputFormat
+ ", codeCache=" + codeCache
+ '}';
}
}
@@ -1,8 +1,6 @@
package jadx.api;
import java.io.File;
import java.io.StringWriter;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@@ -15,27 +13,19 @@ import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import org.jf.baksmali.Adaptors.ClassDefinition;
import org.jf.baksmali.BaksmaliOptions;
import org.jf.dexlib2.DexFileFactory;
import org.jf.dexlib2.Opcodes;
import org.jf.dexlib2.dexbacked.DexBackedClassDef;
import org.jf.dexlib2.dexbacked.DexBackedDexFile;
import org.jf.util.IndentingWriter;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.Jadx;
import jadx.core.ProcessClass;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.dex.visitors.SaveCode;
import jadx.core.export.ExportGradleProject;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.InputFile;
import jadx.core.xmlgen.BinaryXMLParser;
@@ -70,12 +60,9 @@ public final class JadxDecompiler {
private static final Logger LOG = LoggerFactory.getLogger(JadxDecompiler.class);
private JadxArgs args;
private final List<InputFile> inputFiles = new ArrayList<>();
private List<InputFile> inputFiles;
private RootNode root;
private List<IDexTreeVisitor> passes;
private List<JavaClass> classes;
private List<ResourceFile> resources;
@@ -96,48 +83,45 @@ public final class JadxDecompiler {
public void load() {
reset();
JadxArgsValidator.validate(args);
init();
LOG.info("loading ...");
loadFiles(args.getInputFiles());
inputFiles = loadFiles(args.getInputFiles());
root = new RootNode(args);
root.load(inputFiles);
root.initClassPath();
root.loadResources(getResources());
initVisitors();
root.initPasses();
}
void init() {
this.passes = Jadx.getPassesList(args);
}
void reset() {
private void reset() {
root = null;
classes = null;
resources = null;
xmlParser = null;
root = null;
passes = null;
classesMap.clear();
methodsMap.clear();
fieldsMap.clear();
}
public static String getVersion() {
return Jadx.getVersion();
}
private void loadFiles(List<File> files) {
private List<InputFile> loadFiles(List<File> files) {
if (files.isEmpty()) {
throw new JadxRuntimeException("Empty file list");
}
inputFiles.clear();
List<InputFile> filesList = new ArrayList<>();
for (File file : files) {
try {
InputFile.addFilesFrom(file, inputFiles, args.isSkipSources());
InputFile.addFilesFrom(file, filesList, args.isSkipSources());
} catch (Exception e) {
throw new JadxRuntimeException("Error load file: " + file, e);
}
}
return filesList;
}
public void save() {
@@ -214,8 +198,8 @@ public final class JadxDecompiler {
}
executor.execute(() -> {
try {
cls.decompile();
SaveCode.save(outDir, cls.getClassNode());
ICodeInfo code = cls.getCodeInfo();
SaveCode.save(outDir, cls.getClassNode(), code);
} catch (Exception e) {
LOG.error("Error saving class: {}", cls.getFullName(), e);
}
@@ -297,52 +281,10 @@ public final class JadxDecompiler {
root.getErrorsCounter().printReport();
}
private void initVisitors() {
for (IDexTreeVisitor pass : passes) {
try {
pass.init(root);
} catch (Exception e) {
LOG.error("Visitor init failed: {}", pass.getClass().getSimpleName(), e);
}
}
}
void processClass(ClassNode cls) {
ProcessClass.process(cls, passes, true);
}
void generateSmali(ClassNode cls) {
Path path = cls.dex().getDexFile().getPath();
String className = Utils.makeQualifiedObjectName(cls.getClassInfo().getType().getObject());
try {
DexBackedDexFile dexFile = DexFileFactory.loadDexFile(path.toFile(), Opcodes.getDefault());
boolean decompiled = false;
for (DexBackedClassDef classDef : dexFile.getClasses()) {
if (classDef.getType().equals(className)) {
ClassDefinition classDefinition = new ClassDefinition(new BaksmaliOptions(), classDef);
StringWriter sw = new StringWriter();
classDefinition.writeTo(new IndentingWriter(sw));
cls.setSmali(sw.toString());
decompiled = true;
break;
}
}
if (!decompiled) {
LOG.error("Failed to find smali class {}", className);
}
} catch (Exception e) {
LOG.error("Error generating smali", e);
}
}
RootNode getRoot() {
return root;
}
List<IDexTreeVisitor> getPasses() {
return passes;
}
synchronized BinaryXMLParser getXmlParser() {
if (xmlParser == null) {
xmlParser = new BinaryXMLParser(root);
@@ -350,44 +292,133 @@ public final class JadxDecompiler {
return xmlParser;
}
Map<ClassNode, JavaClass> getClassesMap() {
return classesMap;
private void loadJavaClass(JavaClass javaClass) {
javaClass.getMethods().forEach(mth -> methodsMap.put(mth.getMethodNode(), mth));
javaClass.getFields().forEach(fld -> fieldsMap.put(fld.getFieldNode(), fld));
for (JavaClass innerCls : javaClass.getInnerClasses()) {
classesMap.put(innerCls.getClassNode(), innerCls);
loadJavaClass(innerCls);
}
}
Map<MethodNode, JavaMethod> getMethodsMap() {
return methodsMap;
@Nullable("For not generated classes")
private JavaClass getJavaClassByNode(ClassNode cls) {
JavaClass javaClass = classesMap.get(cls);
if (javaClass != null) {
return javaClass;
}
// load parent class if inner
ClassNode parentClass = cls.getTopParentClass();
if (parentClass.contains(AFlag.DONT_GENERATE)) {
return null;
}
if (parentClass != cls) {
JavaClass parentJavaClass = classesMap.get(parentClass);
if (parentJavaClass == null) {
getClasses();
parentJavaClass = classesMap.get(parentClass);
}
loadJavaClass(parentJavaClass);
javaClass = classesMap.get(cls);
if (javaClass != null) {
return javaClass;
}
}
// class or parent classes can be excluded from generation
if (cls.hasNotGeneratedParent()) {
return null;
}
throw new JadxRuntimeException("JavaClass not found by ClassNode: " + cls);
}
JavaMethod getJavaMethodByNode(MethodNode mth) {
@Nullable
private JavaMethod getJavaMethodByNode(MethodNode mth) {
JavaMethod javaMethod = methodsMap.get(mth);
if (javaMethod != null) {
return javaMethod;
}
// parent class not loaded yet
JavaClass javaClass = classesMap.get(mth.getParentClass());
if (javaClass != null) {
javaClass.decompile();
return methodsMap.get(mth);
JavaClass javaClass = getJavaClassByNode(mth.getParentClass().getTopParentClass());
if (javaClass == null) {
return null;
}
return null;
loadJavaClass(javaClass);
javaMethod = methodsMap.get(mth);
if (javaMethod != null) {
return javaMethod;
}
if (mth.getParentClass().hasNotGeneratedParent()) {
return null;
}
throw new JadxRuntimeException("JavaMethod not found by MethodNode: " + mth);
}
Map<FieldNode, JavaField> getFieldsMap() {
return fieldsMap;
}
JavaField getJavaFieldByNode(FieldNode fld) {
@Nullable
private JavaField getJavaFieldByNode(FieldNode fld) {
JavaField javaField = fieldsMap.get(fld);
if (javaField != null) {
return javaField;
}
// parent class not loaded yet
JavaClass javaClass = classesMap.get(fld.getParentClass());
if (javaClass != null) {
javaClass.decompile();
return fieldsMap.get(fld);
JavaClass javaClass = getJavaClassByNode(fld.getParentClass().getTopParentClass());
if (javaClass == null) {
return null;
}
return null;
loadJavaClass(javaClass);
javaField = fieldsMap.get(fld);
if (javaField != null) {
return javaField;
}
if (fld.getParentClass().hasNotGeneratedParent()) {
return null;
}
throw new JadxRuntimeException("JavaField not found by FieldNode: " + fld);
}
@Nullable
JavaNode convertNode(Object obj) {
if (!(obj instanceof LineAttrNode)) {
return null;
}
LineAttrNode node = (LineAttrNode) obj;
if (node.contains(AFlag.DONT_GENERATE)) {
return null;
}
if (obj instanceof ClassNode) {
return getJavaClassByNode((ClassNode) obj);
}
if (obj instanceof MethodNode) {
return getJavaMethodByNode(((MethodNode) obj));
}
if (obj instanceof FieldNode) {
return getJavaFieldByNode((FieldNode) obj);
}
throw new JadxRuntimeException("Unexpected node type: " + obj);
}
@Nullable
public JavaNode getJavaNodeAtPosition(ICodeInfo codeInfo, int line, int offset) {
Map<CodePosition, Object> map = codeInfo.getAnnotations();
if (map.isEmpty()) {
return null;
}
Object obj = map.get(new CodePosition(line, offset));
if (obj == null) {
return null;
}
return convertNode(obj);
}
@Nullable
public CodePosition getDefinitionPosition(JavaNode javaNode) {
JavaClass jCls = javaNode.getTopParentClass();
jCls.decompile();
int defLine = javaNode.getDecompiledLine();
if (defLine == 0) {
return null;
}
return new CodePosition(jCls, defLine, 0);
}
public JadxArgs getArgs() {
+32 -74
View File
@@ -9,9 +9,7 @@ import java.util.Map;
import org.jetbrains.annotations.Nullable;
import jadx.core.codegen.CodeWriter;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
@@ -26,6 +24,7 @@ public final class JavaClass implements JavaNode {
private List<JavaClass> innerClasses = Collections.emptyList();
private List<JavaField> fields = Collections.emptyList();
private List<JavaMethod> methods = Collections.emptyList();
private boolean listsLoaded;
JavaClass(ClassNode classNode, JadxDecompiler decompiler) {
this.decompiler = decompiler;
@@ -43,56 +42,49 @@ public final class JavaClass implements JavaNode {
}
public String getCode() {
CodeWriter code = cls.getCode();
ICodeInfo code = getCodeInfo();
if (code == null) {
decompile();
code = cls.getCode();
if (code == null) {
return "";
}
return "";
}
return code.getCodeStr();
}
public synchronized void decompile() {
if (decompiler == null) {
return;
}
if (cls.getCode() == null) {
decompiler.processClass(cls);
load();
}
public ICodeInfo getCodeInfo() {
return cls.decompile();
}
public void decompile() {
cls.decompile();
}
public synchronized String getSmali() {
if (decompiler == null) {
return null;
}
if (cls.getSmali() == null) {
decompiler.generateSmali(cls);
}
return cls.getSmali();
}
public synchronized void unload() {
cls.unload();
listsLoaded = false;
}
public ClassNode getClassNode() {
return cls;
}
private void load() {
JadxDecompiler rootDecompiler = getRootDecompiler();
private void loadLists() {
if (listsLoaded) {
return;
}
listsLoaded = true;
decompile();
int inClsCount = cls.getInnerClasses().size();
if (inClsCount != 0) {
List<JavaClass> list = new ArrayList<>(inClsCount);
for (ClassNode inner : cls.getInnerClasses()) {
if (!inner.contains(AFlag.DONT_GENERATE)) {
JavaClass javaClass = new JavaClass(inner, this);
javaClass.load();
javaClass.loadLists();
list.add(javaClass);
rootDecompiler.getClassesMap().put(inner, javaClass);
}
}
this.innerClasses = Collections.unmodifiableList(list);
@@ -105,7 +97,6 @@ public final class JavaClass implements JavaNode {
if (!f.contains(AFlag.DONT_GENERATE)) {
JavaField javaField = new JavaField(f, this);
flds.add(javaField);
rootDecompiler.getFieldsMap().put(f, javaField);
}
}
this.fields = Collections.unmodifiableList(flds);
@@ -118,7 +109,6 @@ public final class JavaClass implements JavaNode {
if (!m.contains(AFlag.DONT_GENERATE)) {
JavaMethod javaMethod = new JavaMethod(this, m);
mths.add(javaMethod);
rootDecompiler.getMethodsMap().put(m, javaMethod);
}
}
mths.sort(Comparator.comparing(JavaMethod::getName));
@@ -134,8 +124,7 @@ public final class JavaClass implements JavaNode {
}
private Map<CodePosition, Object> getCodeAnnotations() {
decompile();
CodeWriter code = cls.getCode();
ICodeInfo code = getCodeInfo();
if (code == null) {
return Collections.emptyMap();
}
@@ -151,60 +140,28 @@ public final class JavaClass implements JavaNode {
for (Map.Entry<CodePosition, Object> entry : map.entrySet()) {
CodePosition codePosition = entry.getKey();
Object obj = entry.getValue();
if (obj instanceof LineAttrNode) {
JavaNode node = convertNode(obj);
if (node != null) {
resultMap.put(codePosition, node);
}
JavaNode node = getRootDecompiler().convertNode(obj);
if (node != null) {
resultMap.put(codePosition, node);
}
}
return resultMap;
}
@Nullable
private JavaNode convertNode(Object obj) {
if (!(obj instanceof LineAttrNode)) {
return null;
}
if (obj instanceof ClassNode) {
return getRootDecompiler().getClassesMap().get(obj);
}
if (obj instanceof MethodNode) {
return getRootDecompiler().getJavaMethodByNode(((MethodNode) obj));
}
if (obj instanceof FieldNode) {
return getRootDecompiler().getJavaFieldByNode((FieldNode) obj);
}
return null;
}
@Nullable
@Deprecated
public JavaNode getJavaNodeAtPosition(int line, int offset) {
Map<CodePosition, Object> map = getCodeAnnotations();
if (map.isEmpty()) {
return null;
}
Object obj = map.get(new CodePosition(line, offset));
if (obj == null) {
return null;
}
return convertNode(obj);
return getRootDecompiler().getJavaNodeAtPosition(getCodeInfo(), line, offset);
}
@Nullable
public CodePosition getDefinitionPosition(JavaNode javaNode) {
JavaClass jCls = javaNode.getTopParentClass();
jCls.decompile();
int defLine = javaNode.getDecompiledLine();
if (defLine == 0) {
return null;
}
return new CodePosition(jCls, defLine, 0);
@Deprecated
public CodePosition getDefinitionPosition() {
return getRootDecompiler().getDefinitionPosition(this);
}
public Integer getSourceLine(int decompiledLine) {
decompile();
return cls.getCode().getLineMapping().get(decompiledLine);
return getCodeInfo().getLineMapping().get(decompiledLine);
}
@Override
@@ -236,20 +193,21 @@ public final class JavaClass implements JavaNode {
}
public List<JavaClass> getInnerClasses() {
decompile();
loadLists();
return innerClasses;
}
public List<JavaField> getFields() {
decompile();
loadLists();
return fields;
}
public List<JavaMethod> getMethods() {
decompile();
loadLists();
return methods;
}
@Override
public int getDecompiledLine() {
return cls.getDecompiledLine();
}
@@ -46,6 +46,10 @@ public final class JavaField implements JavaNode {
return field.getDecompiledLine();
}
FieldNode getFieldNode() {
return field;
}
@Override
public int hashCode() {
return field.hashCode();
@@ -2,13 +2,11 @@ package jadx.api;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.Utils;
public final class JavaMethod implements JavaNode {
private final MethodNode mth;
@@ -44,19 +42,16 @@ public final class JavaMethod implements JavaNode {
}
public List<ArgType> getArguments() {
if (mth.getMethodInfo().getArgumentsTypes().isEmpty()) {
List<ArgType> infoArgTypes = mth.getMethodInfo().getArgumentsTypes();
if (infoArgTypes.isEmpty()) {
return Collections.emptyList();
}
List<RegisterArg> arguments = mth.getArguments(false);
Stream<ArgType> argTypeStream;
if (arguments == null || arguments.isEmpty() || mth.isNoCode()) {
argTypeStream = mth.getMethodInfo().getArgumentsTypes().stream();
} else {
argTypeStream = arguments.stream().map(RegisterArg::getType);
List<ArgType> arguments = mth.getArgTypes();
if (arguments == null) {
arguments = infoArgTypes;
}
return argTypeStream
.map(type -> ArgType.tryToResolveClassAlias(mth.dex(), type))
.collect(Collectors.toList());
return Utils.collectionMap(arguments,
type -> ArgType.tryToResolveClassAlias(mth.dex(), type));
}
public ArgType getReturnType() {
@@ -79,6 +74,10 @@ public final class JavaMethod implements JavaNode {
return mth.getDecompiledLine();
}
MethodNode getMethodNode() {
return mth;
}
@Override
public int hashCode() {
return mth.hashCode();
@@ -5,8 +5,9 @@ public enum ResourceType {
MANIFEST("AndroidManifest.xml"),
XML(".xml"),
ARSC(".arsc"),
FONT(".ttf"),
FONT(".ttf", ".otf"),
IMG(".png", ".gif", ".jpg"),
MEDIA(".mp3", ".wav"),
LIB(".so"),
UNKNOWN;
@@ -23,7 +24,7 @@ public enum ResourceType {
public static ResourceType getFileType(String fileName) {
for (ResourceType type : ResourceType.values()) {
for (String ext : type.getExts()) {
if (fileName.endsWith(ext)) {
if (fileName.toLowerCase().endsWith(ext)) {
return type;
}
}
@@ -111,9 +111,8 @@ public final class ResourcesLoader {
private static ResContainer decodeImage(ResourceFile rf, InputStream inputStream) {
String name = rf.getName();
if (name.endsWith(".9.png")) {
Res9patchStreamDecoder decoder = new Res9patchStreamDecoder();
ByteArrayOutputStream os = new ByteArrayOutputStream();
try {
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
Res9patchStreamDecoder decoder = new Res9patchStreamDecoder();
decoder.decode(inputStream, os);
return ResContainer.decodedData(rf.getName(), os.toByteArray());
} catch (Exception e) {
@@ -164,10 +163,8 @@ public final class ResourcesLoader {
}
public static CodeWriter loadToCodeWriter(InputStream is) throws IOException {
CodeWriter cw = new CodeWriter();
ByteArrayOutputStream baos = new ByteArrayOutputStream(READ_BUFFER_SIZE);
copyStream(is, baos);
cw.add(baos.toString("UTF-8"));
return cw;
return new CodeWriter(baos.toString("UTF-8"));
}
}
@@ -0,0 +1,24 @@
package jadx.api.impl;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.jetbrains.annotations.Nullable;
import jadx.api.ICodeCache;
import jadx.api.ICodeInfo;
public class InMemoryCodeCache implements ICodeCache {
private final Map<String, ICodeInfo> storage = new ConcurrentHashMap<>();
@Override
public void add(String clsFullName, ICodeInfo codeInfo) {
storage.put(clsFullName, codeInfo);
}
@Override
public @Nullable ICodeInfo get(String clsFullName) {
return storage.get(clsFullName);
}
}
@@ -0,0 +1,19 @@
package jadx.api.impl;
import org.jetbrains.annotations.Nullable;
import jadx.api.ICodeCache;
import jadx.api.ICodeInfo;
public class NoOpCodeCache implements ICodeCache {
@Override
public void add(String clsFullName, ICodeInfo codeInfo) {
// do nothing
}
@Override
public @Nullable ICodeInfo get(String clsFullName) {
return null;
}
}
@@ -14,6 +14,7 @@ import jadx.api.JadxArgs;
import jadx.core.dex.visitors.ClassModifier;
import jadx.core.dex.visitors.ConstInlineVisitor;
import jadx.core.dex.visitors.ConstructorVisitor;
import jadx.core.dex.visitors.DeboxingVisitor;
import jadx.core.dex.visitors.DependencyCollector;
import jadx.core.dex.visitors.DotGraphVisitor;
import jadx.core.dex.visitors.EnumVisitor;
@@ -87,6 +88,7 @@ public class Jadx {
passes.add(new DebugInfoApplyVisitor());
}
passes.add(new DeboxingVisitor());
passes.add(new ModVisitor());
passes.add(new CodeShrinkVisitor());
passes.add(new ReSugarCode());
@@ -1,51 +1,68 @@
package jadx.core;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import jadx.api.ICodeInfo;
import jadx.core.codegen.CodeGen;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.visitors.DepthTraversal;
import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.exceptions.JadxRuntimeException;
import static jadx.core.dex.nodes.ProcessState.LOADED;
import static jadx.core.dex.nodes.ProcessState.NOT_LOADED;
import static jadx.core.dex.nodes.ProcessState.PROCESSED;
import static jadx.core.dex.nodes.ProcessState.STARTED;
import static jadx.core.dex.nodes.ProcessState.PROCESS_COMPLETE;
import static jadx.core.dex.nodes.ProcessState.PROCESS_STARTED;
public final class ProcessClass {
private ProcessClass() {
}
public static void process(ClassNode cls, List<IDexTreeVisitor> passes, boolean generateCode) {
if (!generateCode && cls.getState() == PROCESSED) {
public static void process(ClassNode cls) {
ClassNode topParentClass = cls.getTopParentClass();
if (topParentClass != cls) {
process(topParentClass);
return;
}
synchronized (getSyncObj(cls)) {
if (cls.getState().isProcessed()) {
// nothing to do
return;
}
synchronized (cls.getClassInfo()) {
try {
if (cls.getState() == NOT_LOADED) {
cls.load();
cls.setState(STARTED);
for (IDexTreeVisitor visitor : passes) {
}
if (cls.getState() == LOADED) {
cls.setState(PROCESS_STARTED);
for (IDexTreeVisitor visitor : cls.root().getPasses()) {
DepthTraversal.visit(visitor, cls);
}
cls.setState(PROCESSED);
cls.setState(PROCESS_COMPLETE);
}
if (cls.getState() == PROCESSED && generateCode) {
processDependencies(cls, passes);
CodeGen.generate(cls);
}
} catch (Exception e) {
} catch (Throwable e) {
ErrorsCounter.classError(cls, e.getClass().getSimpleName(), e);
}
}
}
public static Object getSyncObj(ClassNode cls) {
return cls.getClassInfo();
}
@NotNull
public static ICodeInfo generateCode(ClassNode cls) {
ClassNode topParentClass = cls.getTopParentClass();
if (topParentClass != cls) {
return generateCode(topParentClass);
}
try {
process(cls);
cls.getDependencies().forEach(ProcessClass::process);
private static void processDependencies(ClassNode cls, List<IDexTreeVisitor> passes) {
cls.getDependencies().forEach(depCls -> process(depCls, passes, false));
ICodeInfo code = CodeGen.generate(cls);
cls.unload();
return code;
} catch (Throwable e) {
throw new JadxRuntimeException("Failed to generate code for class: " + cls.getFullName(), e);
}
}
}
+135 -104
View File
@@ -12,6 +12,7 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -22,9 +23,10 @@ import java.util.zip.ZipOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.GenericInfo;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.exceptions.DecodeException;
@@ -55,7 +57,16 @@ public class ClsSet {
private NClass[] classes;
public void load(RootNode root) {
public void loadFromClstFile() throws IOException, DecodeException {
try (InputStream input = getClass().getResourceAsStream(CLST_FILENAME)) {
if (input == null) {
throw new JadxRuntimeException("Can't load classpath file: " + CLST_FILENAME);
}
load(input);
}
}
public void loadFrom(RootNode root) {
List<ClassNode> list = root.getClasses(true);
Map<String, NClass> names = new HashMap<>(list.size());
int k = 0;
@@ -68,7 +79,8 @@ public class ClsSet {
throw new JadxRuntimeException("Duplicate class: " + clsRawName);
}
k++;
nClass.setMethods(loadMethods(cls, nClass));
nClass.setGenerics(cls.getGenerics());
nClass.setMethods(getMethodsDetails(cls));
} else {
names.put(clsRawName, null);
}
@@ -88,45 +100,42 @@ public class ClsSet {
}
}
private NMethod[] loadMethods(ClassNode cls, NClass nClass) {
private List<NMethod> getMethodsDetails(ClassNode cls) {
List<NMethod> methods = new ArrayList<>();
for (MethodNode m : cls.getMethods()) {
if (!m.getAccessFlags().isPublic()
&& !m.getAccessFlags().isProtected()) {
continue;
}
List<ArgType> args = new ArrayList<>();
boolean genericArg = false;
for (RegisterArg r : m.getArguments(false)) {
ArgType argType = r.getType();
if (argType.isGeneric() || argType.isGenericType()) {
args.add(argType);
genericArg = true;
} else {
args.add(null);
}
}
ArgType retType = m.getReturnType();
if (!retType.isGeneric() && !retType.isGenericType()) {
retType = null;
}
boolean varArgs = m.getAccessFlags().isVarArgs();
if (genericArg || retType != null || varArgs) {
methods.add(new NMethod(
m.getMethodInfo().getShortId(),
args.isEmpty()
? new ArgType[0]
: args.toArray(new ArgType[args.size()]),
retType,
varArgs));
AccessInfo accessFlags = m.getAccessFlags();
if (accessFlags.isPublic() || accessFlags.isProtected()) {
processMethodDetails(methods, m, accessFlags);
}
}
return methods.toArray(new NMethod[methods.size()]);
return methods;
}
private void processMethodDetails(List<NMethod> methods, MethodNode mth, AccessInfo accessFlags) {
List<ArgType> args = mth.getArgTypes();
boolean genericArg = false;
ArgType[] genericArgs;
if (args.isEmpty()) {
genericArgs = null;
} else {
int argsCount = args.size();
genericArgs = new ArgType[argsCount];
for (int i = 0; i < argsCount; i++) {
ArgType argType = args.get(i);
if (argType.isGeneric() || argType.isGenericType()) {
genericArgs[i] = argType;
genericArg = true;
}
}
}
ArgType retType = mth.getReturnType();
if (!retType.isGeneric() && !retType.isGenericType()) {
retType = null;
}
boolean varArgs = accessFlags.isVarArgs();
if (genericArg || retType != null || varArgs) {
methods.add(new NMethod(mth.getMethodInfo().getShortId(), genericArgs, retType, varArgs));
}
}
public static NClass[] makeParentsArray(ClassNode cls, Map<String, NClass> names) {
@@ -207,51 +216,67 @@ public class ClsSet {
for (NClass parent : parents) {
out.writeInt(parent.getId());
}
NMethod[] methods = cls.getMethods();
out.writeByte(methods.length);
writeGenerics(out, cls, names);
List<NMethod> methods = cls.getMethodsList();
out.writeByte(methods.size());
for (NMethod method : methods) {
writeMethod(out, method, names);
}
}
}
private static void writeGenerics(DataOutputStream out, NClass cls, Map<String, NClass> names) throws IOException {
List<GenericInfo> genericsList = cls.getGenerics();
out.writeByte(genericsList.size());
for (GenericInfo genericInfo : genericsList) {
writeArgType(out, genericInfo.getGenericType(), names);
List<ArgType> extendsList = genericInfo.getExtendsList();
out.writeByte(extendsList.size());
for (ArgType type : extendsList) {
writeArgType(out, type, names);
}
}
}
private static void writeMethod(DataOutputStream out, NMethod method, Map<String, NClass> names) throws IOException {
int argCount = 0;
ArgType[] argTypes = method.getArgType();
for (ArgType arg : argTypes) {
if (arg != null) {
argCount++;
}
}
writeLongString(out, method.getShortId());
out.writeByte(argCount);
// last argument first
for (int i = argTypes.length - 1; i >= 0; i--) {
ArgType argType = argTypes[i];
if (argType != null) {
out.writeByte(i);
writeArgType(out, argType, names);
ArgType[] argTypes = method.getGenericArgs();
if (argTypes == null) {
out.writeByte(0);
} else {
int argCount = 0;
for (ArgType arg : argTypes) {
if (arg != null) {
argCount++;
}
}
out.writeByte(argCount);
// last argument first
for (int i = argTypes.length - 1; i >= 0; i--) {
ArgType argType = argTypes[i];
if (argType != null) {
out.writeByte(i);
writeArgType(out, argType, names);
}
}
}
if (method.getReturnType() != null) {
if (method.getReturnType() == null) {
out.writeBoolean(false);
} else {
out.writeBoolean(true);
writeArgType(out, method.getReturnType(), names);
} else {
out.writeBoolean(false);
}
out.writeBoolean(method.isVarArgs());
}
private static void writeArgType(DataOutputStream out, ArgType argType, Map<String, NClass> names) throws IOException {
if (argType.getWildcardType() != null) {
out.writeByte(TypeEnum.WILDCARD.ordinal());
int bounds = argType.getWildcardBounds();
out.writeByte(bounds);
if (bounds != 0) {
ArgType.WildcardBound bound = argType.getWildcardBound();
out.writeByte(bound.getNum());
if (bound != ArgType.WildcardBound.UNBOUND) {
writeArgType(out, argType.getWildcardType(), names);
}
} else if (argType.isGeneric()) {
@@ -283,16 +308,7 @@ public class ClsSet {
}
}
public void load() throws IOException, DecodeException {
try (InputStream input = getClass().getResourceAsStream(CLST_FILENAME)) {
if (input == null) {
throw new JadxRuntimeException("Can't load classpath file: " + CLST_FILENAME);
}
load(input);
}
}
public void load(File input) throws IOException, DecodeException {
private void load(File input) throws IOException, DecodeException {
String name = input.getName();
try (InputStream inputStream = new FileInputStream(input)) {
if (name.endsWith(CLST_EXTENSION)) {
@@ -313,7 +329,7 @@ public class ClsSet {
}
}
public void load(InputStream input) throws IOException, DecodeException {
private void load(InputStream input) throws IOException, DecodeException {
try (DataInputStream in = new DataInputStream(input)) {
byte[] header = new byte[JADX_CLS_SET_HEADER.length()];
int readHeaderLength = in.read(header);
@@ -335,18 +351,46 @@ public class ClsSet {
for (int j = 0; j < pCount; j++) {
parents[j] = classes[in.readInt()];
}
classes[i].setParents(parents);
int mCount = in.readByte();
NMethod[] methods = new NMethod[mCount];
for (int j = 0; j < mCount; j++) {
methods[j] = readMethod(in);
}
classes[i].setMethods(methods);
NClass nClass = classes[i];
nClass.setParents(parents);
nClass.setGenerics(readGenerics(in));
nClass.setMethods(readClsMethods(in));
}
}
}
private List<GenericInfo> readGenerics(DataInputStream in) throws IOException {
int count = in.readByte();
if (count == 0) {
return Collections.emptyList();
}
List<GenericInfo> list = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
ArgType genericType = readArgType(in);
List<ArgType> extendsList;
byte extCount = in.readByte();
if (extCount == 0) {
extendsList = Collections.emptyList();
} else {
extendsList = new ArrayList<>(extCount);
for (int j = 0; j < extCount; j++) {
extendsList.add(readArgType(in));
}
}
list.add(new GenericInfo(genericType, extendsList));
}
return list;
}
private List<NMethod> readClsMethods(DataInputStream in) throws IOException {
int mCount = in.readByte();
List<NMethod> methods = new ArrayList<>(mCount);
for (int j = 0; j < mCount; j++) {
methods.add(readMethod(in));
}
return methods;
}
private NMethod readMethod(DataInputStream in) throws IOException {
String shortId = readLongString(in);
int argCount = in.readByte();
@@ -371,7 +415,8 @@ public class ClsSet {
int bounds = in.readByte();
return bounds == 0
? ArgType.wildcard()
: ArgType.wildcard(readArgType(in), bounds);
: ArgType.wildcard(readArgType(in), ArgType.WildcardBound.getByNum(bounds));
case GENERIC:
String obj = classes[in.readInt()].getName();
int typeLength = in.readByte();
@@ -385,34 +430,20 @@ public class ClsSet {
}
}
return ArgType.generic(obj, generics);
case GENERIC_TYPE:
return ArgType.genericType(readString(in));
case OBJECT:
return ArgType.object(classes[in.readInt()].getName());
case ARRAY:
return ArgType.array(readArgType(in));
case PRIMITIVE:
int shortName = in.readByte();
switch (shortName) {
case 'Z':
return ArgType.BOOLEAN;
case 'C':
return ArgType.CHAR;
case 'B':
return ArgType.BYTE;
case 'S':
return ArgType.SHORT;
case 'I':
return ArgType.INT;
case 'F':
return ArgType.FLOAT;
case 'J':
return ArgType.LONG;
case 'D':
return ArgType.DOUBLE;
default:
return ArgType.VOID;
}
char shortName = (char) in.readByte();
return ArgType.parse(shortName);
default:
throw new JadxRuntimeException("Unsupported Arg Type: " + ordinal);
}
@@ -10,15 +10,18 @@ import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxRuntimeException;
/**
* Classes hierarchy graph
* Classes hierarchy graph with methods additional info
*/
public class ClspGraph {
private static final Logger LOG = LoggerFactory.getLogger(ClspGraph.class);
@@ -30,7 +33,7 @@ public class ClspGraph {
public void load() throws IOException, DecodeException {
ClsSet set = new ClsSet();
set.load();
set.loadFromClstFile();
addClasspath(set);
}
@@ -62,6 +65,19 @@ public class ClspGraph {
return nameMap.containsKey(fullName);
}
public NClass getClsDetails(ArgType type) {
return nameMap.get(type.getObject());
}
@Nullable
public NMethod getMethodDetails(MethodInfo methodInfo) {
NClass cls = nameMap.get(methodInfo.getDeclClass().getRawName());
if (cls == null) {
return null;
}
return cls.getMethodsMap().get(methodInfo.getShortId());
}
private NClass addClass(ClassNode cls) {
String rawName = cls.getRawName();
NClass nClass = new NClass(rawName, -1);
@@ -49,7 +49,7 @@ public class ConvertToClsSet {
root.load(inputFiles);
ClsSet set = new ClsSet();
set.load(root);
set.loadFrom(root);
set.save(output);
LOG.info("Output: {}", output);
LOG.info("done");
@@ -1,14 +1,24 @@
package jadx.core.clsp;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import jadx.core.dex.nodes.GenericInfo;
/**
* Class node in classpath graph
*/
public class NClass {
private final String name;
private NClass[] parents;
private NMethod[] methods;
private final int id;
private NClass[] parents;
private Map<String, NMethod> methodsMap = Collections.emptyMap();
private List<GenericInfo> generics = Collections.emptyList();
public NClass(String name, int id) {
this.name = name;
@@ -31,6 +41,37 @@ public class NClass {
this.parents = parents;
}
public Map<String, NMethod> getMethodsMap() {
return methodsMap;
}
public List<NMethod> getMethodsList() {
List<NMethod> list = new ArrayList<>(methodsMap.size());
list.addAll(methodsMap.values());
Collections.sort(list);
return list;
}
public void setMethodsMap(Map<String, NMethod> methodsMap) {
this.methodsMap = Objects.requireNonNull(methodsMap);
}
public void setMethods(List<NMethod> methods) {
Map<String, NMethod> map = new HashMap<>(methods.size());
for (NMethod mth : methods) {
map.put(mth.getShortId(), mth);
}
setMethodsMap(map);
}
public List<GenericInfo> getGenerics() {
return generics;
}
public void setGenerics(List<GenericInfo> generics) {
this.generics = generics;
}
@Override
public int hashCode() {
return name.hashCode();
@@ -52,12 +93,4 @@ public class NClass {
public String toString() {
return name;
}
public void setMethods(NMethod[] methods) {
this.methods = methods;
}
public NMethod[] getMethods() {
return methods;
}
}
@@ -1,20 +1,33 @@
package jadx.core.clsp;
import java.util.Arrays;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import jadx.core.dex.instructions.args.ArgType;
/**
* Generic method node in classpath graph.
*/
public class NMethod {
public class NMethod implements Comparable<NMethod> {
private final String shortId;
private final ArgType[] argType;
/**
* Array contains only generic args, others set to 'null', size can be less than total args count
*/
@Nullable
private final ArgType[] genericArgs;
@Nullable
private final ArgType retType;
private final boolean varArgs;
public NMethod(String shortId, ArgType[] argType, ArgType retType, boolean varArgs) {
public NMethod(String shortId, @Nullable ArgType[] genericArgs, @Nullable ArgType retType, boolean varArgs) {
this.shortId = shortId;
this.argType = argType;
this.genericArgs = genericArgs;
this.retType = retType;
this.varArgs = varArgs;
}
@@ -23,10 +36,21 @@ public class NMethod {
return shortId;
}
public ArgType[] getArgType() {
return argType;
@Nullable
public ArgType[] getGenericArgs() {
return genericArgs;
}
@Nullable
public ArgType getGenericArg(int i) {
ArgType[] args = this.genericArgs;
if (args != null && i < args.length) {
return args[i];
}
return null;
}
@Nullable
public ArgType getReturnType() {
return retType;
}
@@ -34,4 +58,35 @@ public class NMethod {
public boolean isVarArgs() {
return varArgs;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof NMethod)) {
return false;
}
NMethod other = (NMethod) o;
return shortId.equals(other.shortId);
}
@Override
public int hashCode() {
return shortId.hashCode();
}
@Override
public int compareTo(@NotNull NMethod other) {
return this.shortId.compareTo(other.shortId);
}
@Override
public String toString() {
return "NMethod{'" + shortId + '\''
+ ", argTypes=" + Arrays.toString(genericArgs)
+ ", retType=" + retType
+ ", varArgs=" + varArgs
+ '}';
}
}
@@ -147,7 +147,7 @@ public class AnnotationGen {
if (val instanceof String) {
code.add(getStringUtils().unescapeString((String) val));
} else if (val instanceof Integer) {
code.add(TypeGen.formatInteger((Integer) val));
code.add(TypeGen.formatInteger((Integer) val, false));
} else if (val instanceof Character) {
code.add(getStringUtils().unescapeChar((Character) val));
} else if (val instanceof Boolean) {
@@ -157,11 +157,11 @@ public class AnnotationGen {
} else if (val instanceof Double) {
code.add(TypeGen.formatDouble((Double) val));
} else if (val instanceof Long) {
code.add(TypeGen.formatLong((Long) val));
code.add(TypeGen.formatLong((Long) val, false));
} else if (val instanceof Short) {
code.add(TypeGen.formatShort((Short) val));
code.add(TypeGen.formatShort((Short) val, false));
} else if (val instanceof Byte) {
code.add(TypeGen.formatByte((Byte) val));
code.add(TypeGen.formatByte((Byte) val, false));
} else if (val instanceof ArgType) {
classGen.useType(code, (ArgType) val);
code.add(".class");
@@ -1,15 +1,16 @@
package jadx.core.codegen;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import com.android.dx.rop.code.AccessFlags;
import com.google.common.collect.Streams;
import jadx.api.JadxArgs;
import jadx.core.dex.attributes.AFlag;
@@ -27,6 +28,7 @@ import jadx.core.dex.instructions.mods.ConstructorInsn;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.GenericInfo;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.parser.FieldInitAttr;
@@ -129,7 +131,8 @@ public class ClassGen {
annotationGen.addForClass(clsCode);
insertRenameInfo(clsCode, cls);
CodeGenUtils.addSourceFileInfo(clsCode, cls);
clsCode.startLine(af.makeString());
clsCode.startLineWithNum(cls.getSourceLine());
clsCode.add(af.makeString());
if (af.isInterface()) {
if (af.isAnnotation()) {
clsCode.add('@');
@@ -143,13 +146,13 @@ public class ClassGen {
clsCode.attachDefinition(cls);
clsCode.add(cls.getClassInfo().getAliasShortName());
addGenericMap(clsCode, cls.getGenericMap(), true);
addGenericMap(clsCode, cls.getGenerics(), true);
clsCode.add(' ');
ArgType sup = cls.getSuperClass();
if (sup != null
&& !sup.equals(ArgType.OBJECT)
&& !sup.getObject().equals(ArgType.ENUM.getObject())) {
&& !cls.isEnum()) {
clsCode.add("extends ");
useClass(clsCode, sup);
clsCode.add(' ');
@@ -174,23 +177,23 @@ public class ClassGen {
}
}
public boolean addGenericMap(CodeWriter code, Map<ArgType, List<ArgType>> gmap, boolean classDeclaration) {
if (gmap == null || gmap.isEmpty()) {
public boolean addGenericMap(CodeWriter code, List<GenericInfo> generics, boolean classDeclaration) {
if (generics == null || generics.isEmpty()) {
return false;
}
code.add('<');
int i = 0;
for (Entry<ArgType, List<ArgType>> e : gmap.entrySet()) {
ArgType type = e.getKey();
List<ArgType> list = e.getValue();
for (GenericInfo genericInfo : generics) {
if (i != 0) {
code.add(", ");
}
ArgType type = genericInfo.getGenericType();
if (type.isGenericType()) {
code.add(type.getObject());
} else {
useClass(code, type);
}
List<ArgType> list = genericInfo.getExtendsList();
if (list != null && !list.isEmpty()) {
code.add(" extends ");
for (Iterator<ArgType> it = list.iterator(); it.hasNext();) {
@@ -221,21 +224,32 @@ public class ClassGen {
clsDeclLine = clsCode.getLine();
clsCode.incIndent();
addFields(clsCode);
addInnerClasses(clsCode, cls);
addMethods(clsCode);
addInnerClsAndMethods(clsCode);
clsCode.decIndent();
clsCode.startLine('}');
}
private void addInnerClasses(CodeWriter code, ClassNode cls) throws CodegenException {
for (ClassNode innerCls : cls.getInnerClasses()) {
if (innerCls.contains(AFlag.DONT_GENERATE)) {
continue;
}
private void addInnerClsAndMethods(CodeWriter clsCode) {
Streams.concat(cls.getInnerClasses().stream(), cls.getMethods().stream())
.filter(node -> !node.contains(AFlag.DONT_GENERATE))
.sorted(Comparator.comparingInt(LineAttrNode::getSourceLine))
.forEach(node -> {
if (node instanceof ClassNode) {
addInnerClass(clsCode, (ClassNode) node);
} else {
addMethod(clsCode, (MethodNode) node);
}
});
}
private void addInnerClass(CodeWriter code, ClassNode innerCls) {
try {
ClassGen inClGen = new ClassGen(innerCls, getParentGen());
code.newLine();
inClGen.addClassCode(code);
imports.addAll(inClGen.getImports());
} catch (Exception e) {
ErrorsCounter.classError(innerCls, "Inner class code generation error", e);
}
}
@@ -248,36 +262,24 @@ public class ClassGen {
return false;
}
private void addMethods(CodeWriter code) {
List<MethodNode> methods = sortMethodsByLine(cls.getMethods());
for (MethodNode mth : methods) {
if (mth.contains(AFlag.DONT_GENERATE)) {
continue;
}
if (code.getLine() != clsDeclLine) {
code.newLine();
}
int savedIndent = code.getIndent();
try {
addMethod(code, mth);
} catch (Exception e) {
if (mth.getParentClass().getTopParentClass().contains(AFlag.RESTART_CODEGEN)) {
throw new JadxRuntimeException("Method generation error", e);
}
code.newLine().add("/*");
code.newLine().addMultiLine(ErrorsCounter.methodError(mth, "Method generation error", e));
Utils.appendStackTrace(code, e);
code.newLine().add("*/");
code.setIndent(savedIndent);
mth.addError("Method generation error: " + e.getMessage(), e);
}
private void addMethod(CodeWriter code, MethodNode mth) {
if (code.getLine() != clsDeclLine) {
code.newLine();
}
int savedIndent = code.getIndent();
try {
addMethodCode(code, mth);
} catch (Exception e) {
if (mth.getParentClass().getTopParentClass().contains(AFlag.RESTART_CODEGEN)) {
throw new JadxRuntimeException("Method generation error", e);
}
code.newLine().add("/*");
code.newLine().addMultiLine(ErrorsCounter.methodError(mth, "Method generation error", e));
Utils.appendStackTrace(code, e);
code.newLine().add("*/");
code.setIndent(savedIndent);
mth.addError("Method generation error: " + e.getMessage(), e);
}
}
private static List<MethodNode> sortMethodsByLine(List<MethodNode> methods) {
List<MethodNode> out = new ArrayList<>(methods);
out.sort(Comparator.comparingInt(LineAttrNode::getSourceLine));
return out;
}
private boolean isMethodsPresents() {
@@ -289,7 +291,7 @@ public class ClassGen {
return false;
}
public void addMethod(CodeWriter code, MethodNode mth) throws CodegenException {
public void addMethodCode(CodeWriter code, MethodNode mth) throws CodegenException {
CodeGenUtils.addComments(code, mth);
if (mth.getAccessFlags().isAbstract() || mth.getAccessFlags().isNative()) {
MethodGen mthGen = new MethodGen(this, mth);
@@ -457,6 +459,14 @@ public class ClassGen {
}
public void useClass(CodeWriter code, ArgType type) {
ArgType outerType = type.getOuterType();
if (outerType != null) {
useClass(code, outerType);
code.add('.');
useClass(code, type.getInnerType());
return;
}
useClass(code, ClassInfo.fromType(cls.root(), type));
ArgType[] generics = type.getGenericTypes();
if (generics != null) {
@@ -469,10 +479,9 @@ public class ClassGen {
ArgType gt = generics[i];
ArgType wt = gt.getWildcardType();
if (wt != null) {
code.add('?');
int bounds = gt.getWildcardBounds();
if (bounds != 0) {
code.add(bounds == -1 ? " super " : " extends ");
ArgType.WildcardBound bound = gt.getWildcardBound();
code.add(bound.getStr());
if (bound != ArgType.WildcardBound.UNBOUND) {
useType(code, wt);
}
} else {
@@ -514,6 +523,9 @@ public class ClassGen {
if (isClassInnerFor(useCls, extClsInfo)) {
return shortName;
}
if (extClsInfo.isInner()) {
return expandInnerClassName(useCls, extClsInfo);
}
if (isBothClassesInOneTopClass(useCls, extClsInfo)) {
return shortName;
}
@@ -551,6 +563,26 @@ public class ClassGen {
return shortName;
}
private String expandInnerClassName(ClassInfo useCls, ClassInfo extClsInfo) {
List<ClassInfo> clsList = new ArrayList<>();
clsList.add(extClsInfo);
ClassInfo parentCls = extClsInfo.getParentClass();
boolean addImport = true;
while (parentCls != null) {
if (parentCls == useCls || isClassInnerFor(useCls, parentCls)) {
addImport = false;
break;
}
clsList.add(parentCls);
parentCls = parentCls.getParentClass();
}
Collections.reverse(clsList);
if (addImport) {
addImport(clsList.get(0));
}
return Utils.listToString(clsList, ".", ClassInfo::getAliasShortName);
}
private void addImport(ClassInfo classInfo) {
if (parentGen != null) {
parentGen.addImport(classInfo);
@@ -580,7 +612,7 @@ public class ClassGen {
private static boolean isClassInnerFor(ClassInfo inner, ClassInfo parent) {
if (inner.isInner()) {
ClassInfo p = inner.getParentClass();
return p.equals(parent) || isClassInnerFor(p, parent);
return Objects.equals(p, parent) || isClassInnerFor(p, parent);
}
return false;
}
@@ -2,6 +2,7 @@ package jadx.core.codegen;
import java.util.concurrent.Callable;
import jadx.api.ICodeInfo;
import jadx.api.JadxArgs;
import jadx.core.codegen.json.JsonCodeGen;
import jadx.core.dex.attributes.AFlag;
@@ -10,33 +11,32 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
public class CodeGen {
public static void generate(ClassNode cls) {
public static ICodeInfo generate(ClassNode cls) {
if (cls.contains(AFlag.DONT_GENERATE)) {
cls.setCode(CodeWriter.EMPTY);
} else {
JadxArgs args = cls.root().getArgs();
switch (args.getOutputFormat()) {
case JAVA:
generateJavaCode(cls, args);
break;
return CodeWriter.EMPTY;
}
JadxArgs args = cls.root().getArgs();
switch (args.getOutputFormat()) {
case JAVA:
return generateJavaCode(cls, args);
case JSON:
generateJson(cls);
break;
}
case JSON:
return generateJson(cls);
default:
throw new JadxRuntimeException("Unknown output format");
}
}
private static void generateJavaCode(ClassNode cls, JadxArgs args) {
private static ICodeInfo generateJavaCode(ClassNode cls, JadxArgs args) {
ClassGen clsGen = new ClassGen(cls, args);
CodeWriter code = wrapCodeGen(cls, clsGen::makeClass);
cls.setCode(code);
return wrapCodeGen(cls, clsGen::makeClass);
}
private static void generateJson(ClassNode cls) {
private static ICodeInfo generateJson(ClassNode cls) {
JsonCodeGen codeGen = new JsonCodeGen(cls);
String clsJson = wrapCodeGen(cls, codeGen::process);
cls.setCode(new CodeWriter(clsJson));
return new CodeWriter(clsJson);
}
private static <R> R wrapCodeGen(ClassNode cls, Callable<R> codeGenFunc) {
@@ -12,19 +12,18 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.CodePosition;
import jadx.api.ICodeInfo;
import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.utils.StringUtils;
import jadx.core.utils.files.FileUtils;
import jadx.core.utils.files.ZipSecurity;
public class CodeWriter {
public class CodeWriter implements ICodeInfo {
private static final Logger LOG = LoggerFactory.getLogger(CodeWriter.class);
public static final String NL = System.getProperty("line.separator");
public static final String INDENT_STR = " ";
public static final CodeWriter EMPTY = new CodeWriter().finish();
private static final boolean ADD_LINE_NUMBERS = false;
private static final String[] INDENT_CACHE = {
@@ -36,6 +35,8 @@ public class CodeWriter {
INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR,
};
public static final CodeWriter EMPTY = new CodeWriter().finish();
private StringBuilder buf;
@Nullable
private String code;
@@ -52,7 +53,8 @@ public class CodeWriter {
this.indent = 0;
this.indentStr = "";
if (ADD_LINE_NUMBERS) {
incIndent(2);
incIndent(3);
add(indentStr);
}
}
@@ -242,6 +244,7 @@ public class CodeWriter {
return annotations.put(pos, obj);
}
@Override
public Map<CodePosition, Object> getAnnotations() {
return annotations;
}
@@ -260,6 +263,7 @@ public class CodeWriter {
lineMap.put(decompiledLine, sourceLine);
}
@Override
public Map<Integer, Integer> getLineMapping() {
return lineMap;
}
@@ -293,7 +297,11 @@ public class CodeWriter {
return buf.length();
}
@Override
public String getCodeStr() {
if (code == null) {
throw new NullPointerException("Code not set");
}
return code;
}
@@ -17,11 +17,13 @@ import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
import jadx.core.dex.attributes.nodes.MethodInlineAttr;
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.ArithNode;
import jadx.core.dex.instructions.ArithOp;
import jadx.core.dex.instructions.CallMthInterface;
import jadx.core.dex.instructions.ConstClassNode;
import jadx.core.dex.instructions.ConstStringNode;
import jadx.core.dex.instructions.FillArrayNode;
@@ -36,11 +38,11 @@ import jadx.core.dex.instructions.NewArrayNode;
import jadx.core.dex.instructions.SwitchNode;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.CodeVar;
import jadx.core.dex.instructions.args.FieldArg;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.InsnWrapArg;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.Named;
import jadx.core.dex.instructions.args.NamedArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.instructions.mods.ConstructorInsn;
@@ -51,6 +53,7 @@ import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.RegionUtils;
import jadx.core.utils.TypeUtils;
import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.JadxRuntimeException;
@@ -101,22 +104,26 @@ public class InsnGen {
} else if (arg.isLiteral()) {
code.add(lit((LiteralArg) arg));
} else if (arg.isInsnWrap()) {
Flags flag = wrap ? Flags.BODY_ONLY : Flags.BODY_ONLY_NOWRAP;
makeInsn(((InsnWrapArg) arg).getWrapInsn(), code, flag);
addWrappedArg(code, (InsnWrapArg) arg, wrap);
} else if (arg.isNamed()) {
code.add(((Named) arg).getName());
} else if (arg.isField()) {
FieldArg f = (FieldArg) arg;
if (f.isStatic()) {
staticField(code, f.getField());
} else {
instanceField(code, f.getField(), f.getInstanceArg());
}
} else {
throw new CodegenException("Unknown arg type " + arg);
}
}
private void addWrappedArg(CodeWriter code, InsnWrapArg arg, boolean wrap) throws CodegenException {
InsnNode wrapInsn = arg.getWrapInsn();
if (wrapInsn.contains(AFlag.FORCE_ASSIGN_INLINE)) {
code.add('(');
makeInsn(wrapInsn, code, Flags.INLINE);
code.add(')');
} else {
Flags flags = wrap ? Flags.BODY_ONLY : Flags.BODY_ONLY_NOWRAP;
makeInsn(wrapInsn, code, flags);
}
}
public void assignVar(CodeWriter code, InsnNode insn) throws CodegenException {
RegisterArg arg = insn.getResult();
if (insn.contains(AFlag.DECLARE_VAR)) {
@@ -140,7 +147,7 @@ public class InsnGen {
}
private String lit(LiteralArg arg) {
return TypeGen.literalToString(arg.getLiteral(), arg.getType(), mth, fallback);
return TypeGen.literalToString(arg, mth, fallback);
}
private void instanceField(CodeWriter code, FieldInfo field, InsnArg arg) throws CodegenException {
@@ -174,6 +181,7 @@ public class InsnGen {
public static void makeStaticFieldAccess(CodeWriter code, FieldInfo field, ClassGen clsGen) {
ClassInfo declClass = field.getDeclClass();
// TODO
boolean fieldFromThisClass = clsGen.getClassNode().getClassInfo().equals(declClass);
if (!fieldFromThisClass) {
// Android specific resources class handler
@@ -227,11 +235,14 @@ public class InsnGen {
if (attachInsns) {
code.attachLineAnnotation(insn);
}
if (insn.contains(AFlag.COMMENT_OUT)) {
code.add("// ");
}
}
if (insn.getResult() != null) {
SSAVar var = insn.getResult().getSVar();
if ((var == null || var.getUseCount() != 0 || insn.getType() != InsnType.CONSTRUCTOR)
&& !insn.contains(AFlag.ARITH_ONEARG)) {
RegisterArg resArg = insn.getResult();
if (resArg != null) {
SSAVar var = resArg.getSVar();
if (var == null || var.getUseCount() != 0 || insn.getType() != InsnType.CONSTRUCTOR) {
assignVar(code, insn);
code.add(" = ");
}
@@ -592,6 +603,7 @@ public class InsnGen {
throws CodegenException {
ClassNode cls = mth.dex().resolveClass(insn.getClassType());
if (cls != null && cls.isAnonymous() && !fallback) {
cls.ensureProcessed();
inlineAnonymousConstructor(code, cls, insn);
return;
}
@@ -606,7 +618,10 @@ public class InsnGen {
code.add("new ");
useClass(code, insn.getClassType());
ArgType argType = insn.getResult().getSVar().getCodeVar().getType();
if (argType.isGeneric()) {
boolean genericCls = cls == null || !cls.getGenerics().isEmpty();
if (argType != null
&& argType.getGenericTypes() != null
&& genericCls) {
code.add('<');
if (insn.contains(AFlag.EXPLICIT_GENERICS)) {
boolean first = true;
@@ -759,14 +774,13 @@ public class InsnGen {
if (arg.contains(AFlag.SKIP_ARG)) {
continue;
}
RegisterArg callArg = getCallMthArg(callMth, i - startArgNum);
if (callArg != null && callArg.contains(AFlag.SKIP_ARG)) {
if (SkipMethodArgsAttr.isSkip(callMth, i - startArgNum)) {
continue;
}
if (!firstArg) {
code.add(", ");
}
boolean cast = overloaded && processOverloadedArg(code, callMth, arg, i - startArgNum);
boolean cast = addArgCast(code, insn, callMth, arg, i - startArgNum, overloaded);
if (!cast && i == argsCount - 1 && processVarArg(code, callMth, arg)) {
continue;
}
@@ -777,58 +791,66 @@ public class InsnGen {
code.add(')');
}
private static RegisterArg getCallMthArg(@Nullable MethodNode callMth, int num) {
if (callMth == null) {
return null;
}
List<RegisterArg> args = callMth.getArguments(false);
if (args != null && num < args.size()) {
return args.get(num);
}
return null;
}
/**
* Add additional cast for overloaded method argument.
* Add additional cast for method argument.
*/
private boolean processOverloadedArg(CodeWriter code, MethodNode callMth, InsnArg arg, int origPos) {
ArgType origType;
List<RegisterArg> arguments = callMth.getArguments(false);
if (arguments == null || arguments.isEmpty()) {
mth.addComment("JADX INFO: used method not loaded: " + callMth + ", types can be incorrect");
origType = callMth.getMethodInfo().getArgumentsTypes().get(origPos);
} else {
origType = arguments.get(origPos).getInitType();
private boolean addArgCast(CodeWriter code, InsnNode insn, @Nullable MethodNode callMth,
InsnArg arg, int origPos, boolean overloaded) {
ArgType castType = null;
if (callMth != null) {
List<ArgType> argTypes = callMth.getArgTypes();
ArgType origType = argTypes.get(origPos);
if (origType.isGenericType() && !callMth.getParentClass().equals(mth.getParentClass())) {
// cancel cast
return false;
}
if (insn instanceof CallMthInterface && origType.containsGenericType()) {
ArgType clsType;
CallMthInterface mthCall = (CallMthInterface) insn;
RegisterArg instanceArg = mthCall.getInstanceArg();
if (instanceArg != null) {
clsType = instanceArg.getType();
} else {
clsType = mthCall.getCallMth().getDeclClass().getType();
}
ArgType replacedType = TypeUtils.replaceClassGenerics(root, clsType, origType);
if (replacedType != null) {
castType = replacedType;
}
if (castType == null) {
ArgType invReplType = TypeUtils.replaceMethodGenerics(root, insn, origType);
if (invReplType != null) {
castType = invReplType;
}
}
}
if (castType == null) {
castType = origType;
}
} else {
castType = arg.getType();
}
// TODO: check castType for left type variables
if (isCastNeeded(arg, castType, overloaded)) {
code.add('(');
useType(code, castType);
code.add(") ");
return true;
}
return false;
}
private boolean isCastNeeded(InsnArg arg, ArgType origType, boolean overloaded) {
ArgType argType = arg.getType();
if (argType.equals(origType)
// null cast to object
&& (!arg.isLiteral() || ((LiteralArg) arg).getLiteral() != 0
|| (!argType.isArray() && !argType.isObject()))) {
if (arg.isLiteral() && ((LiteralArg) arg).getLiteral() == 0
&& (argType.isObject() || argType.isArray())) {
return true;
}
if (argType.equals(origType)) {
return false;
}
if (origType.isGeneric()) {
if (argType.isObject()) {
if (!argType.isGeneric() && arg.isInsnWrap()) {
((InsnWrapArg) arg).getWrapInsn().getResult().setType(
ArgType.generic(argType.getObject(), origType.getGenericTypes()));
}
if (origType.getObject().equals(argType.getObject())) {
return false;
}
}
if (arg.isInsnWrap()) {
((InsnWrapArg) arg).getWrapInsn().add(AFlag.EXPLICIT_GENERICS);
}
}
code.add('(');
useType(code, origType);
code.add(") ");
return true;
return overloaded;
}
/**
@@ -865,16 +887,22 @@ public class InsnGen {
if (Consts.DEBUG) {
code.add("/* inline method: ").add(callMthNode.toString()).add("*/").startLine();
}
if (forceAssign(inl, insn, callMthNode)) {
ArgType varType = callMthNode.getReturnType();
useType(code, varType);
code.add(' ');
code.add(mgen.getNameGen().assignNamedArg(new NamedArg("unused", varType)));
code.add(" = ");
}
if (callMthNode.getMethodInfo().getArgumentsTypes().isEmpty()) {
makeInsn(inl, code, Flags.BODY_ONLY);
} else {
// remap args
InsnArg[] regs = new InsnArg[callMthNode.getRegsCount()];
List<RegisterArg> callArgs = callMthNode.getArguments(true);
for (int i = 0; i < callArgs.size(); i++) {
int[] regNums = mia.getArgsRegNums();
for (int i = 0; i < regNums.length; i++) {
InsnArg arg = insn.getArg(i);
RegisterArg callArg = callArgs.get(i);
regs[callArg.getRegNum()] = arg;
regs[regNums[i]] = arg;
}
// replace args
InsnNode inlCopy = inl.copy();
@@ -898,6 +926,16 @@ public class InsnGen {
return true;
}
private boolean forceAssign(InsnNode inlineInsn, InvokeNode parentInsn, MethodNode callMthNode) {
if (parentInsn.getResult() != null) {
return false;
}
if (parentInsn.contains(AFlag.WRAPPED)) {
return false;
}
return !callMthNode.getReturnType().equals(ArgType.VOID);
}
private void makeTernary(TernaryInsn insn, CodeWriter code, Set<Flags> state) throws CodegenException {
boolean wrap = state.contains(Flags.BODY_ONLY);
if (wrap) {
@@ -970,19 +1008,22 @@ public class InsnGen {
private void makeArithOneArg(ArithNode insn, CodeWriter code) throws CodegenException {
ArithOp op = insn.getOp();
InsnArg resArg = insn.getArg(0);
InsnArg arg = insn.getArg(1);
// "++" or "--"
if (arg.isLiteral() && (op == ArithOp.ADD || op == ArithOp.SUB)) {
LiteralArg lit = (LiteralArg) arg;
if (lit.isInteger() && lit.getLiteral() == 1) {
assignVar(code, insn);
if (lit.getLiteral() == 1 && lit.isInteger()) {
addArg(code, resArg, false);
String opSymbol = op.getSymbol();
code.add(opSymbol).add(opSymbol);
return;
}
}
// +=, -= ...
assignVar(code, insn);
// +=, -=, ...
addArg(code, resArg, false);
code.add(' ').add(op.getSymbol()).add("= ");
addArg(code, arg, false);
}
@@ -1,5 +1,6 @@
package jadx.core.codegen;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
@@ -88,7 +89,6 @@ public class MethodGen {
if (mth.getMethodInfo().hasAlias() && !ai.isConstructor()) {
CodeGenUtils.addRenamedComment(code, mth, mth.getName());
}
CodeGenUtils.addSourceFileInfo(code, mth);
if (mth.contains(AFlag.INCONSISTENT_CODE)) {
code.startLine("/* Code decompiled incorrectly, please refer to instructions dump. */");
}
@@ -99,7 +99,7 @@ public class MethodGen {
code.add(mth.isVirtual() ? "/* virtual */ " : "/* direct */ ");
}
if (classGen.addGenericMap(code, mth.getGenericMap(), false)) {
if (classGen.addGenericMap(code, mth.getGenerics(), false)) {
code.add(' ');
}
if (ai.isConstructor()) {
@@ -113,11 +113,11 @@ public class MethodGen {
}
code.add('(');
List<RegisterArg> args = mth.getArguments(false);
List<RegisterArg> args = mth.getArgRegs();
if (mth.getMethodInfo().isConstructor()
&& mth.getParentClass().contains(AType.ENUM_CLASS)) {
if (args.size() == 2) {
args.clear();
args = Collections.emptyList();
} else if (args.size() > 2) {
args = args.subList(2, args.size());
} else {
@@ -165,11 +165,12 @@ public class MethodGen {
code.add("final ");
}
ArgType argType;
if (var.getType() == ArgType.UNKNOWN) {
ArgType varType = var.getType();
if (varType == null || varType == ArgType.UNKNOWN) {
// occur on decompilation errors
argType = mthArg.getInitType();
} else {
argType = var.getType();
argType = varType;
}
if (!it.hasNext() && mth.getAccessFlags().isVarArgs()) {
// change last array argument to varargs
@@ -132,11 +132,18 @@ public class NameGen {
if (!NameMapper.isValidAndPrintable(varName)) {
varName = getFallbackName(var);
}
if (Consts.DEBUG) {
varName += '_' + getFallbackName(var);
}
return varName;
}
private String getFallbackName(CodeVar var) {
return getFallbackName(var.getSsaVars().get(0).getAssign());
List<SSAVar> ssaVars = var.getSsaVars();
if (ssaVars.isEmpty()) {
return "v";
}
return getFallbackName(ssaVars.get(0).getAssign());
}
private String getFallbackName(RegisterArg arg) {
@@ -131,12 +131,20 @@ public class RegionGen extends InsnGen {
}
}
}
boolean comment = region.contains(AFlag.COMMENT_OUT);
if (comment) {
code.add("// ");
}
code.add("if (");
new ConditionGen(this).add(code, region.getCondition());
code.add(") {");
makeRegionIndent(code, region.getThenRegion());
code.startLine('}');
if (comment) {
code.startLine("// }");
} else {
code.startLine('}');
}
IContainer els = region.getElseRegion();
if (RegionUtils.notEmpty(els)) {
@@ -146,7 +154,11 @@ public class RegionGen extends InsnGen {
}
code.add('{');
makeRegionIndent(code, els);
code.startLine('}');
if (comment) {
code.startLine("// }");
} else {
code.startLine('}');
}
}
}
@@ -4,7 +4,9 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.deobf.NameMapper;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.PrimitiveType;
import jadx.core.dex.nodes.IDexNode;
import jadx.core.utils.StringUtils;
@@ -28,16 +30,26 @@ public class TypeGen {
return stype.getShortName();
}
/**
* Convert literal arg to string (preferred method)
*/
public static String literalToString(LiteralArg arg, IDexNode dexNode, boolean fallback) {
return literalToString(arg.getLiteral(), arg.getType(),
dexNode.root().getStringUtils(),
fallback,
arg.contains(AFlag.EXPLICIT_PRIMITIVE_TYPE));
}
/**
* Convert literal value to string according to value type
*
* @throws JadxRuntimeException for incorrect type or literal value
*/
public static String literalToString(long lit, ArgType type, IDexNode dexNode, boolean fallback) {
return literalToString(lit, type, dexNode.root().getStringUtils(), fallback);
return literalToString(lit, type, dexNode.root().getStringUtils(), fallback, false);
}
public static String literalToString(long lit, ArgType type, StringUtils stringUtils, boolean fallback) {
public static String literalToString(long lit, ArgType type, StringUtils stringUtils, boolean fallback, boolean cast) {
if (type == null || !type.isTypeKnown()) {
String n = Long.toString(lit);
if (fallback && Math.abs(lit) > 100) {
@@ -65,13 +77,13 @@ public class TypeGen {
}
return stringUtils.unescapeChar(ch);
case BYTE:
return formatByte(lit);
return formatByte(lit, cast);
case SHORT:
return formatShort(lit);
return formatShort(lit, cast);
case INT:
return formatInteger(lit);
return formatInteger(lit, cast);
case LONG:
return formatLong(lit);
return formatLong(lit, cast);
case FLOAT:
return formatFloat(Float.intBitsToFloat((int) lit));
case DOUBLE:
@@ -90,37 +102,40 @@ public class TypeGen {
}
}
public static String formatShort(long l) {
public static String formatShort(long l, boolean cast) {
if (l == Short.MAX_VALUE) {
return "Short.MAX_VALUE";
}
if (l == Short.MIN_VALUE) {
return "Short.MIN_VALUE";
}
return Long.toString(l);
String str = Long.toString(l);
return cast ? "(short) " + str : str;
}
public static String formatByte(long l) {
public static String formatByte(long l, boolean cast) {
if (l == Byte.MAX_VALUE) {
return "Byte.MAX_VALUE";
}
if (l == Byte.MIN_VALUE) {
return "Byte.MIN_VALUE";
}
return Long.toString(l);
String str = Long.toString(l);
return cast ? "(byte) " + str : str;
}
public static String formatInteger(long l) {
public static String formatInteger(long l, boolean cast) {
if (l == Integer.MAX_VALUE) {
return "Integer.MAX_VALUE";
}
if (l == Integer.MIN_VALUE) {
return "Integer.MIN_VALUE";
}
return Long.toString(l);
String str = Long.toString(l);
return cast ? "(int) " + str : str;
}
public static String formatLong(long l) {
public static String formatLong(long l, boolean cast) {
if (l == Long.MAX_VALUE) {
return "Long.MAX_VALUE";
}
@@ -128,8 +143,8 @@ public class TypeGen {
return "Long.MIN_VALUE";
}
String str = Long.toString(l);
if (Math.abs(l) >= Integer.MAX_VALUE) {
str += 'L';
if (cast || Math.abs(l) >= Integer.MAX_VALUE) {
return str + 'L';
}
return str;
}
@@ -193,7 +193,7 @@ public class JsonCodeGen {
jsonCodeLine.setSourceLine(lineMapping.get(line));
Object obj = annotations.get(new CodePosition(line, 0));
if (obj instanceof InsnNode) {
int offset = ((InsnNode) obj).getOffset();
long offset = ((InsnNode) obj).getOffset();
jsonCodeLine.setOffset("0x" + Long.toHexString(mthCodeOffset + offset * 2));
}
codeLines.add(jsonCodeLine);
@@ -346,7 +346,7 @@ public class Deobfuscator {
ClassInfo classInfo = cls.getClassInfo();
String pkgFullName = classInfo.getPackage();
PackageNode pkg = getPackageNode(pkgFullName, true);
doPkg(pkg, pkgFullName);
processPackageFull(pkg, pkgFullName);
String alias = deobfPresets.getForCls(classInfo);
if (alias != null) {
@@ -372,6 +372,24 @@ public class Deobfuscator {
return makeClsAlias(cls);
}
public String getPkgAlias(ClassNode cls) {
ClassInfo classInfo = cls.getClassInfo();
PackageNode pkg = null;
DeobfClsInfo deobfClsInfo = clsMap.get(classInfo);
if (deobfClsInfo != null) {
pkg = deobfClsInfo.getPkg();
} else {
String fullPkgName = classInfo.getPackage();
pkg = getPackageNode(fullPkgName, true);
processPackageFull(pkg, fullPkgName);
}
if (pkg.hasAnyAlias()) {
return pkg.getFullAlias();
} else {
return pkg.getFullName();
}
}
private String makeClsAlias(ClassNode cls) {
ClassInfo classInfo = cls.getClassInfo();
String alias = null;
@@ -472,7 +490,7 @@ public class Deobfuscator {
return alias;
}
private void doPkg(PackageNode pkg, String fullName) {
private void processPackageFull(PackageNode pkg, String fullName) {
if (pkgSet.contains(fullName)) {
return;
}
@@ -482,15 +500,19 @@ public class Deobfuscator {
PackageNode parentPkg = pkg.getParentPackage();
while (!parentPkg.getName().isEmpty()) {
if (!parentPkg.hasAlias()) {
doPkg(parentPkg, parentPkg.getFullName());
processPackageFull(parentPkg, parentPkg.getFullName());
}
parentPkg = parentPkg.getParentPackage();
}
String pkgName = pkg.getName();
if (!pkg.hasAlias() && shouldRename(pkgName)) {
String pkgAlias = String.format("p%03d%s", pkgIndex++, prepareNamePart(pkgName));
pkg.setAlias(pkgAlias);
if (!pkg.hasAlias()) {
String pkgName = pkg.getName();
if ((args.isDeobfuscationOn() && shouldRename(pkgName))
|| (args.isRenameValid() && !NameMapper.isValidIdentifier(pkgName))
|| (args.isRenamePrintable() && !NameMapper.isAllCharsPrintable(pkgName))) {
String pkgAlias = String.format("p%03d%s", pkgIndex++, prepareNamePart(pkg.getName()));
pkg.setAlias(pkgAlias);
}
}
}
@@ -1,6 +1,7 @@
package jadx.core.dex.attributes;
public enum AFlag {
MTH_ENTER_BLOCK,
TRY_ENTER,
TRY_LEAVE,
@@ -15,9 +16,13 @@ public enum AFlag {
DONT_WRAP,
DONT_INLINE,
DONT_GENERATE, // process as usual, but don't output to generated code
COMMENT_OUT, // process as usual, but comment insn in generated code
REMOVE, // can be completely removed
HIDDEN, // instruction used inside other instruction but not listed in args
RESTART_CODEGEN,
DONT_RENAME, // do not rename during deobfuscation
REMOVE, // can be completely removed
ADDED_TO_REGION,
FINALLY_INSNS,
@@ -39,6 +44,11 @@ public enum AFlag {
*/
IMMUTABLE_TYPE,
/**
* Force inline instruction with inline assign
*/
FORCE_ASSIGN_INLINE,
CUSTOM_DECLARE, // variable for this register don't need declaration
DECLARE_VAR,
@@ -51,5 +61,11 @@ public enum AFlag {
EXPLICIT_GENERICS,
/**
* Use constants with explicit type: cast '(byte) 1' or type letter '7L'
*/
EXPLICIT_PRIMITIVE_TYPE,
EXPLICIT_CAST,
INCONSISTENT_CODE, // warning about incorrect decompilation
}
@@ -1,5 +1,9 @@
package jadx.core.dex.attributes;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import jadx.core.dex.attributes.annotations.AnnotationsList;
import jadx.core.dex.attributes.annotations.MethodParameters;
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
@@ -18,6 +22,7 @@ import jadx.core.dex.attributes.nodes.MethodInlineAttr;
import jadx.core.dex.attributes.nodes.PhiListAttr;
import jadx.core.dex.attributes.nodes.RegDebugInfoAttr;
import jadx.core.dex.attributes.nodes.RenameReasonAttr;
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
import jadx.core.dex.attributes.nodes.SourceFileAttr;
import jadx.core.dex.nodes.parser.FieldInitAttr;
import jadx.core.dex.trycatch.CatchAttr;
@@ -32,35 +37,54 @@ import jadx.core.dex.trycatch.SplitterBlockAttr;
*/
public class AType<T extends IAttribute> {
public static final AType<AttrList<JumpInfo>> JUMP = new AType<>();
public static final AType<AttrList<LoopInfo>> LOOP = new AType<>();
public static final AType<AttrList<EdgeInsnAttr>> EDGE_INSN = new AType<>();
// class, method, field
public static final AType<AnnotationsList> ANNOTATION_LIST = new AType<>();
public static final AType<RenameReasonAttr> RENAME_REASON = new AType<>();
// class, method
public static final AType<AttrList<JadxError>> JADX_ERROR = new AType<>(); // code failed to decompile completely
public static final AType<AttrList<String>> JADX_WARN = new AType<>(); // mark code as inconsistent (code can be viewed)
public static final AType<AttrList<String>> COMMENTS = new AType<>(); // any additional info about decompilation
public static final AType<ExcHandlerAttr> EXC_HANDLER = new AType<>();
public static final AType<CatchAttr> CATCH_BLOCK = new AType<>();
public static final AType<SplitterBlockAttr> SPLITTER_BLOCK = new AType<>();
public static final AType<ForceReturnAttr> FORCE_RETURN = new AType<>();
public static final AType<FieldInitAttr> FIELD_INIT = new AType<>();
public static final AType<FieldReplaceAttr> FIELD_REPLACE = new AType<>();
public static final AType<MethodInlineAttr> METHOD_INLINE = new AType<>();
// class
public static final AType<SourceFileAttr> SOURCE_FILE = new AType<>();
public static final AType<EnumClassAttr> ENUM_CLASS = new AType<>();
public static final AType<EnumMapAttr> ENUM_MAP = new AType<>();
public static final AType<AnnotationsList> ANNOTATION_LIST = new AType<>();
public static final AType<MethodParameters> ANNOTATION_MTH_PARAMETERS = new AType<>();
public static final AType<PhiListAttr> PHI_LIST = new AType<>();
public static final AType<SourceFileAttr> SOURCE_FILE = new AType<>();
public static final AType<DeclareVariablesAttr> DECLARE_VARIABLES = new AType<>();
public static final AType<LoopLabelAttr> LOOP_LABEL = new AType<>();
public static final AType<IgnoreEdgeAttr> IGNORE_EDGE = new AType<>();
public static final AType<RenameReasonAttr> RENAME_REASON = new AType<>();
// field
public static final AType<FieldInitAttr> FIELD_INIT = new AType<>();
public static final AType<FieldReplaceAttr> FIELD_REPLACE = new AType<>();
// method
public static final AType<LocalVarsDebugInfoAttr> LOCAL_VARS_DEBUG_INFO = new AType<>();
public static final AType<MethodInlineAttr> METHOD_INLINE = new AType<>();
public static final AType<MethodParameters> ANNOTATION_MTH_PARAMETERS = new AType<>();
public static final AType<SkipMethodArgsAttr> SKIP_MTH_ARGS = new AType<>();
// registers
// region
public static final AType<DeclareVariablesAttr> DECLARE_VARIABLES = new AType<>();
// block
public static final AType<PhiListAttr> PHI_LIST = new AType<>();
public static final AType<IgnoreEdgeAttr> IGNORE_EDGE = new AType<>();
public static final AType<ForceReturnAttr> FORCE_RETURN = new AType<>();
public static final AType<CatchAttr> CATCH_BLOCK = new AType<>();
public static final AType<SplitterBlockAttr> SPLITTER_BLOCK = new AType<>();
public static final AType<AttrList<LoopInfo>> LOOP = new AType<>();
public static final AType<AttrList<EdgeInsnAttr>> EDGE_INSN = new AType<>();
// block or insn
public static final AType<ExcHandlerAttr> EXC_HANDLER = new AType<>();
// instruction
public static final AType<LoopLabelAttr> LOOP_LABEL = new AType<>();
public static final AType<AttrList<JumpInfo>> JUMP = new AType<>();
// register
public static final AType<RegDebugInfoAttr> REG_DEBUG_INFO = new AType<>();
public static final Set<AType<?>> SKIP_ON_UNLOAD = new HashSet<>(Arrays.asList(
FIELD_REPLACE,
METHOD_INLINE,
SKIP_MTH_ARGS));
}
@@ -97,6 +97,17 @@ public abstract class AttrNode implements IAttributeNode {
unloadIfEmpty();
}
/**
* Remove all attribute with exceptions from {@link AType#SKIP_ON_UNLOAD}
*/
public void unloadAttributes() {
if (storage == EMPTY_ATTR_STORAGE) {
return;
}
storage.unloadAttributes();
unloadIfEmpty();
}
@Override
public List<String> getAttributesStringsList() {
return storage.getAttributeStrings();
@@ -20,11 +20,11 @@ import jadx.core.utils.Utils;
public class AttributeStorage {
private final Set<AFlag> flags;
private final Map<AType<?>, IAttribute> attributes;
private Map<AType<?>, IAttribute> attributes;
public AttributeStorage() {
flags = EnumSet.noneOf(AFlag.class);
attributes = new IdentityHashMap<>();
attributes = Collections.emptyMap();
}
public void add(AFlag flag) {
@@ -32,7 +32,7 @@ public class AttributeStorage {
}
public void add(IAttribute attr) {
attributes.put(attr.getType(), attr);
writeAttributes().put(attr.getType(), attr);
}
public <T> void add(AType<AttrList<T>> type, T obj) {
@@ -46,7 +46,7 @@ public class AttributeStorage {
public void addAll(AttributeStorage otherList) {
flags.addAll(otherList.flags);
attributes.putAll(otherList.attributes);
writeAttributes().putAll(otherList.attributes);
}
public boolean contains(AFlag flag) {
@@ -80,20 +80,41 @@ public class AttributeStorage {
}
public <T extends IAttribute> void remove(AType<T> type) {
attributes.remove(type);
}
public void remove(IAttribute attr) {
AType<? extends IAttribute> type = attr.getType();
IAttribute a = attributes.get(type);
if (a == attr) {
if (!attributes.isEmpty()) {
attributes.remove(type);
}
}
public void remove(IAttribute attr) {
if (!attributes.isEmpty()) {
AType<? extends IAttribute> type = attr.getType();
IAttribute a = attributes.get(type);
if (a == attr) {
attributes.remove(type);
}
}
}
private Map<AType<?>, IAttribute> writeAttributes() {
if (attributes.isEmpty()) {
attributes = new IdentityHashMap<>(5);
}
return attributes;
}
public void clear() {
flags.clear();
attributes.clear();
if (!attributes.isEmpty()) {
attributes.clear();
}
}
public synchronized void unloadAttributes() {
if (attributes.isEmpty()) {
return;
}
Set<AType<?>> skipOnUnload = AType.SKIP_ON_UNLOAD;
attributes.keySet().removeIf(attrType -> !skipOnUnload.contains(attrType));
}
public List<String> getAttributeStrings() {
@@ -1,5 +1,7 @@
package jadx.core.dex.attributes.nodes;
import java.util.Objects;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.AttrList;
import jadx.core.dex.attributes.IAttribute;
@@ -14,8 +16,12 @@ public class EdgeInsnAttr implements IAttribute {
public static void addEdgeInsn(BlockNode start, BlockNode end, InsnNode insn) {
EdgeInsnAttr edgeInsnAttr = new EdgeInsnAttr(start, end, insn);
start.addAttr(AType.EDGE_INSN, edgeInsnAttr);
end.addAttr(AType.EDGE_INSN, edgeInsnAttr);
if (!start.getAll(AType.EDGE_INSN).contains(edgeInsnAttr)) {
start.addAttr(AType.EDGE_INSN, edgeInsnAttr);
}
if (!end.getAll(AType.EDGE_INSN).contains(edgeInsnAttr)) {
end.addAttr(AType.EDGE_INSN, edgeInsnAttr);
}
}
public EdgeInsnAttr(BlockNode start, BlockNode end, InsnNode insn) {
@@ -41,6 +47,25 @@ public class EdgeInsnAttr implements IAttribute {
return insn;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
EdgeInsnAttr that = (EdgeInsnAttr) o;
return start.equals(that.start)
&& end.equals(that.end)
&& insn.isDeepEquals(that.insn);
}
@Override
public int hashCode() {
return Objects.hash(start, end, insn);
}
@Override
public String toString() {
return "EDGE_INSN: " + start + "->" + end + ' ' + insn;
@@ -1,21 +1,46 @@
package jadx.core.dex.attributes.nodes;
import java.util.List;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
public class MethodInlineAttr implements IAttribute {
public static void markForInline(MethodNode mth, InsnNode replaceInsn) {
List<RegisterArg> allArgRegs = mth.getAllArgRegs();
int argsCount = allArgRegs.size();
int[] regNums = new int[argsCount];
for (int i = 0; i < argsCount; i++) {
RegisterArg reg = allArgRegs.get(i);
regNums[i] = reg.getRegNum();
}
mth.addAttr(new MethodInlineAttr(replaceInsn, regNums));
}
private final InsnNode insn;
public MethodInlineAttr(InsnNode insn) {
/**
* Store method arguments register numbers to allow remap registers
*/
private final int[] argsRegNums;
private MethodInlineAttr(InsnNode insn, int[] argsRegNums) {
this.insn = insn;
this.argsRegNums = argsRegNums;
}
public InsnNode getInsn() {
return insn;
}
public int[] getArgsRegNums() {
return argsRegNums;
}
@Override
public AType<MethodInlineAttr> getType() {
return AType.METHOD_INLINE;
@@ -0,0 +1,67 @@
package jadx.core.dex.attributes.nodes;
import java.util.BitSet;
import org.jetbrains.annotations.Nullable;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
public class SkipMethodArgsAttr implements IAttribute {
public static void skipArg(MethodNode mth, RegisterArg arg) {
int argNum = Utils.indexInListByRef(mth.getArgRegs(), arg);
if (argNum == -1) {
throw new JadxRuntimeException("Arg not found: " + arg);
}
skipArg(mth, argNum);
}
public static void skipArg(MethodNode mth, int argNum) {
SkipMethodArgsAttr attr = mth.get(AType.SKIP_MTH_ARGS);
if (attr == null) {
attr = new SkipMethodArgsAttr(mth);
mth.addAttr(attr);
}
attr.skip(argNum);
}
public static boolean isSkip(@Nullable MethodNode mth, int argNum) {
if (mth == null) {
return false;
}
SkipMethodArgsAttr attr = mth.get(AType.SKIP_MTH_ARGS);
if (attr == null) {
return false;
}
return attr.isSkip(argNum);
}
private final BitSet skipArgs;
private SkipMethodArgsAttr(MethodNode mth) {
this.skipArgs = new BitSet(mth.getArgRegs().size());
}
public void skip(int argNum) {
skipArgs.set(argNum);
}
public boolean isSkip(int argNum) {
return skipArgs.get(argNum);
}
@Override
public AType<SkipMethodArgsAttr> getType() {
return AType.SKIP_MTH_ARGS;
}
@Override
public String toString() {
return "SKIP_MTH_ARGS: " + skipArgs;
}
}
@@ -3,6 +3,7 @@ package jadx.core.dex.info;
import com.android.dx.rop.code.AccessFlags;
import jadx.core.Consts;
import jadx.core.utils.exceptions.JadxRuntimeException;
public class AccessInfo {
@@ -63,6 +64,10 @@ public class AccessInfo {
return (accFlags & AccessFlags.ACC_PRIVATE) != 0;
}
public boolean isPackagePrivate() {
return (accFlags & VISIBILITY_FLAGS) == 0;
}
public boolean isAbstract() {
return (accFlags & AccessFlags.ACC_ABSTRACT) != 0;
}
@@ -188,6 +193,22 @@ public class AccessInfo {
return code.toString();
}
public String visibilityName() {
if (isPackagePrivate()) {
return "package-private";
}
if (isPublic()) {
return "public";
}
if (isPrivate()) {
return "private";
}
if (isProtected()) {
return "protected";
}
throw new JadxRuntimeException("Unknown visibility flags: " + getVisibility());
}
public String rawString() {
switch (type) {
case CLASS:
@@ -35,13 +35,12 @@ public final class MethodInfo {
private MethodInfo(ClassInfo declClass, String name, List<ArgType> args, ArgType retType) {
this.name = name;
alias = name;
aliasFromPreset = false;
this.alias = name;
this.aliasFromPreset = false;
this.declClass = declClass;
this.args = args;
this.retType = retType;
shortId = makeSignature(true);
this.shortId = makeSignature(true);
}
public static MethodInfo externalMth(ClassInfo declClass, String name, List<ArgType> args, ArgType retType) {
@@ -5,6 +5,7 @@ import com.android.dx.io.instructions.DecodedInstruction;
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.LiteralArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.InsnUtils;
@@ -50,9 +51,16 @@ public class ArithNode extends InsnNode {
addArg(b);
}
public ArithNode(ArithOp op, RegisterArg res, InsnArg a) {
this(op, res, res, a);
add(AFlag.ARITH_ONEARG);
/**
* Create one argument arithmetic instructions (a+=2).
* Result is not set (null).
*
* @param res argument to change
*/
public static ArithNode oneArgOp(ArithOp op, InsnArg res, InsnArg a) {
ArithNode insn = new ArithNode(op, null, res, a);
insn.add(AFlag.ARITH_ONEARG);
return insn;
}
public ArithOp getOp() {
@@ -68,7 +76,32 @@ public class ArithNode extends InsnNode {
return false;
}
ArithNode other = (ArithNode) obj;
return op == other.op;
return op == other.op && isSameLiteral(other);
}
private boolean isSameLiteral(ArithNode other) {
InsnArg thisSecond = getArg(1);
InsnArg otherSecond = other.getArg(1);
if (thisSecond.isLiteral() != otherSecond.isLiteral()) {
return false;
}
if (!thisSecond.isLiteral()) {
// both not literals
return true;
}
// both literals
long thisLit = ((LiteralArg) thisSecond).getLiteral();
long otherLit = ((LiteralArg) otherSecond).getLiteral();
return thisLit == otherLit;
}
@Override
public InsnNode copy() {
ArithNode copy = new ArithNode(op,
getResult().duplicate(),
getArg(0).duplicate(),
getArg(1).duplicate());
return copyCommonParams(copy);
}
@Override
@@ -1,8 +1,19 @@
package jadx.core.dex.instructions;
import org.jetbrains.annotations.Nullable;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.RegisterArg;
public interface CallMthInterface {
MethodInfo getCallMth();
@Nullable
RegisterArg getInstanceArg();
/**
* Return offset to match method args from {@link #getCallMth()}
*/
int getFirstArgOffset();
}
@@ -35,6 +35,6 @@ public final class ConstClassNode extends InsnNode {
@Override
public String toString() {
return super.toString() + ' ' + clsType;
return super.toString() + ' ' + clsType + ".class";
}
}
@@ -25,13 +25,15 @@ public final class FillArrayNode extends InsnNode {
private ArgType elemType;
public FillArrayNode(int resReg, FillArrayDataPayloadDecodedInstruction payload) {
super(InsnType.FILL_ARRAY, 1);
ArgType elType = getElementType(payload.getElementWidthUnit());
addArg(InsnArg.reg(resReg, ArgType.array(elType)));
this(payload.getData(), payload.getSize(), getElementType(payload.getElementWidthUnit()));
addArg(InsnArg.reg(resReg, ArgType.array(elemType)));
}
this.data = payload.getData();
this.size = payload.getSize();
this.elemType = elType;
private FillArrayNode(Object data, int size, ArgType elemType) {
super(InsnType.FILL_ARRAY, 1);
this.data = data;
this.size = size;
this.elemType = elemType;
}
private static ArgType getElementType(short elementWidthUnit) {
@@ -98,6 +100,11 @@ public final class FillArrayNode extends InsnNode {
return elemType.equals(other.elemType) && data == other.data;
}
@Override
public InsnNode copy() {
return copyCommonParams(new FillArrayNode(data, size, elemType));
}
public String dataToString() {
if (data instanceof int[]) {
return Arrays.toString((int[]) data);
@@ -34,6 +34,11 @@ public class FilledNewArrayNode extends InsnNode {
return elemType == other.elemType;
}
@Override
public InsnNode copy() {
return copyCommonParams(new FilledNewArrayNode(elemType, getArgsCount()));
}
@Override
public String toString() {
return super.toString() + " elemType: " + elemType;
@@ -1,5 +1,6 @@
package jadx.core.dex.instructions;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.InsnUtils;
public class GotoNode extends TargetInsnNode {
@@ -19,6 +20,11 @@ public class GotoNode extends TargetInsnNode {
return target;
}
@Override
public InsnNode copy() {
return copyCommonParams(new GotoNode(target));
}
@Override
public String toString() {
return super.toString() + "-> " + InsnUtils.formatOffset(target);
@@ -34,12 +34,16 @@ public class IfNode extends GotoNode {
}
public IfNode(IfOp op, int targetOffset, InsnArg arg1, InsnArg arg2) {
super(InsnType.IF, targetOffset, 2);
this.op = op;
this(op, targetOffset);
addArg(arg1);
addArg(arg2);
}
private IfNode(IfOp op, int targetOffset) {
super(InsnType.IF, targetOffset, 2);
this.op = op;
}
// change default types priority
private static final ArgType WIDE_TYPE = ArgType.unknown(
PrimitiveType.INT, PrimitiveType.BOOLEAN,
@@ -123,6 +127,14 @@ public class IfNode extends GotoNode {
return op == other.op;
}
@Override
public InsnNode copy() {
IfNode copy = new IfNode(op, target);
copy.thenBlock = thenBlock;
copy.elseBlock = elseBlock;
return copyCommonParams(copy);
}
@Override
public String toString() {
return InsnUtils.formatOffset(offset) + ": "
@@ -1,13 +1,15 @@
package jadx.core.dex.instructions;
import org.jetbrains.annotations.Nullable;
import com.android.dx.io.instructions.DecodedInstruction;
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.RegisterArg;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.Utils;
public class InvokeNode extends InsnNode implements CallMthInterface {
@@ -15,7 +17,7 @@ public class InvokeNode extends InsnNode implements CallMthInterface {
private final MethodInfo mth;
public InvokeNode(MethodInfo mth, DecodedInstruction insn, InvokeType type, boolean isRange, int resReg) {
super(InsnType.INVOKE, mth.getArgsCount() + (type != InvokeType.STATIC ? 1 : 0));
super(InsnType.INVOKE, mth.getArgsCount() + (type == InvokeType.STATIC ? 0 : 1));
this.mth = mth;
this.type = type;
@@ -51,6 +53,23 @@ public class InvokeNode extends InsnNode implements CallMthInterface {
return mth;
}
@Override
@Nullable
public RegisterArg getInstanceArg() {
if (type != InvokeType.STATIC && getArgsCount() > 0) {
InsnArg firstArg = getArg(0);
if (firstArg.isRegister()) {
return ((RegisterArg) firstArg);
}
}
return null;
}
@Override
public int getFirstArgOffset() {
return type == InvokeType.STATIC ? 0 : 1;
}
@Override
public InsnNode copy() {
return copyCommonParams(new InvokeNode(mth, type, getArgsCount()));
@@ -70,11 +89,6 @@ public class InvokeNode extends InsnNode implements CallMthInterface {
@Override
public String toString() {
return InsnUtils.formatOffset(offset) + ": "
+ InsnUtils.insnTypeToString(insnType)
+ (getResult() == null ? "" : getResult() + " = ")
+ Utils.listToString(getArguments())
+ ' ' + mth
+ " type: " + type;
return super.toString() + ' ' + mth + " type: " + type;
}
}
@@ -12,12 +12,16 @@ public class NewArrayNode extends InsnNode {
private final ArgType arrType;
public NewArrayNode(@NotNull ArgType arrType, RegisterArg res, InsnArg size) {
super(InsnType.NEW_ARRAY, 1);
this.arrType = arrType;
this(arrType);
setResult(res);
addArg(size);
}
private NewArrayNode(ArgType arrType) {
super(InsnType.NEW_ARRAY, 1);
this.arrType = arrType;
}
public ArgType getArrayType() {
return arrType;
}
@@ -34,6 +38,11 @@ public class NewArrayNode extends InsnNode {
return arrType == other.arrType;
}
@Override
public InsnNode copy() {
return copyCommonParams(new NewArrayNode(arrType));
}
@Override
public String toString() {
return super.toString() + " type: " + arrType;
@@ -21,13 +21,17 @@ public final class PhiInsn extends InsnNode {
private final List<BlockNode> blockBinds;
public PhiInsn(int regNum, int predecessors) {
super(InsnType.PHI, predecessors);
this.blockBinds = new ArrayList<>(predecessors);
this(predecessors);
setResult(InsnArg.reg(regNum, ArgType.UNKNOWN));
add(AFlag.DONT_INLINE);
add(AFlag.DONT_GENERATE);
}
private PhiInsn(int argsCount) {
super(InsnType.PHI, argsCount);
this.blockBinds = new ArrayList<>(argsCount);
}
public RegisterArg bindArg(BlockNode pred) {
RegisterArg arg = InsnArg.reg(getResult().getRegNum(), getResult().getInitType());
bindArg(arg, pred);
@@ -72,7 +76,7 @@ public final class PhiInsn extends InsnNode {
}
@Override
protected RegisterArg removeArg(int index) {
public RegisterArg removeArg(int index) {
RegisterArg reg = (RegisterArg) super.removeArg(index);
blockBinds.remove(index);
reg.getSVar().updateUsedInPhiList();
@@ -111,6 +115,11 @@ public final class PhiInsn extends InsnNode {
throw new JadxRuntimeException("Direct setArg is forbidden for PHI insn, bindArg must be used");
}
@Override
public InsnNode copy() {
return copyCommonParams(new PhiInsn(getArgsCount()));
}
@Override
public String toString() {
return "PHI: " + getResult() + " = " + Utils.listToString(getArguments())
@@ -20,11 +20,15 @@ public class SwitchNode extends TargetInsnNode {
private BlockNode defTargetBlock;
public SwitchNode(InsnArg arg, Object[] keys, int[] targets, int def) {
this(keys, targets, def);
addArg(arg);
}
private SwitchNode(Object[] keys, int[] targets, int def) {
super(InsnType.SWITCH, 1);
this.keys = keys;
this.targets = targets;
this.def = def;
addArg(arg);
}
public int getCasesCount() {
@@ -96,6 +100,14 @@ public class SwitchNode extends TargetInsnNode {
&& Arrays.equals(targets, other.targets);
}
@Override
public InsnNode copy() {
SwitchNode copy = new SwitchNode(keys, targets, def);
copy.targetBlocks = targetBlocks;
copy.defTargetBlock = defTargetBlock;
return copyCommonParams(copy);
}
@Override
public String toString() {
StringBuilder targ = new StringBuilder();
@@ -3,6 +3,7 @@ package jadx.core.dex.instructions.args;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import jadx.core.Consts;
import jadx.core.dex.info.ClassInfo;
@@ -74,14 +75,14 @@ public abstract class ArgType {
}
public static ArgType wildcard() {
return new WildcardType(OBJECT, 0);
return new WildcardType(OBJECT, WildcardBound.UNBOUND);
}
public static ArgType wildcard(ArgType obj, int bounds) {
return new WildcardType(obj, bounds);
public static ArgType wildcard(ArgType obj, WildcardBound bound) {
return new WildcardType(obj, bound);
}
public static ArgType generic(String sign) {
public static ArgType parseGenericSignature(String sign) {
return new SignatureParser(sign).consumeType();
}
@@ -89,8 +90,8 @@ public abstract class ArgType {
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 outerGeneric(ArgType genericOuterType, ArgType innerType) {
return new OuterGenericObject((GenericObject) genericOuterType, (ObjectType) innerType);
}
public static ArgType array(ArgType vtype) {
@@ -156,7 +157,7 @@ public abstract class ArgType {
}
private static class ObjectType extends KnownType {
private final String objName;
protected final String objName;
public ObjectType(String obj) {
this.objName = Utils.cleanObjectName(obj);
@@ -212,14 +213,40 @@ public abstract class ArgType {
}
}
public enum WildcardBound {
EXTENDS(1, "? extends "), // upper bound (? extends A)
UNBOUND(0, "?"), // no bounds (?)
SUPER(-1, "? super "); // lower bound (? super A)
private final int num;
private final String str;
WildcardBound(int val, String str) {
this.num = val;
this.str = str;
}
public int getNum() {
return num;
}
public String getStr() {
return str;
}
public static WildcardBound getByNum(int num) {
return num == 0 ? UNBOUND : (num == 1 ? EXTENDS : SUPER);
}
}
private static final class WildcardType extends ObjectType {
private final ArgType type;
private final int bounds;
private final WildcardBound bound;
public WildcardType(ArgType obj, int bounds) {
public WildcardType(ArgType obj, WildcardBound bound) {
super(OBJECT.getObject());
this.type = obj;
this.bounds = bounds;
this.type = Objects.requireNonNull(obj);
this.bound = Objects.requireNonNull(bound);
}
@Override
@@ -232,52 +259,38 @@ public abstract class ArgType {
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;
public WildcardBound getWildcardBound() {
return bound;
}
@Override
boolean internalEquals(Object obj) {
return super.internalEquals(obj)
&& bounds == ((WildcardType) obj).bounds
&& bound == ((WildcardType) obj).bound
&& type.equals(((WildcardType) obj).type);
}
@Override
public String toString() {
if (bounds == 0) {
return "?";
if (bound == WildcardBound.UNBOUND) {
return bound.getStr();
}
return "? " + (bounds == -1 ? "super" : "extends") + ' ' + type;
return bound.getStr() + 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);
this.generics = Objects.requireNonNull(generics);
this.hash = calcHash();
}
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);
private int calcHash() {
return objName.hashCode() + 31 * Arrays.hashCode(generics);
}
@Override
@@ -290,11 +303,6 @@ public abstract class ArgType {
return generics;
}
@Override
public ArgType getOuterType() {
return outerType;
}
@Override
boolean internalEquals(Object obj) {
return super.internalEquals(obj)
@@ -307,6 +315,54 @@ public abstract class ArgType {
}
}
private static class OuterGenericObject extends ObjectType {
private final GenericObject outerType;
private final ObjectType innerType;
public OuterGenericObject(GenericObject outerType, ObjectType innerType) {
super(outerType.getObject() + '$' + innerType.getObject());
this.outerType = outerType;
this.innerType = innerType;
this.hash = calcHash();
}
private int calcHash() {
return objName.hashCode() + 31 * (outerType.hashCode() + 31 * innerType.hashCode());
}
@Override
public boolean isGeneric() {
return true;
}
@Override
public ArgType[] getGenericTypes() {
return innerType.getGenericTypes();
}
@Override
public ArgType getOuterType() {
return outerType;
}
@Override
public ArgType getInnerType() {
return innerType;
}
@Override
boolean internalEquals(Object obj) {
return super.internalEquals(obj)
&& Objects.equals(outerType, ((OuterGenericObject) obj).outerType)
&& Objects.equals(innerType, ((OuterGenericObject) obj).innerType);
}
@Override
public String toString() {
return outerType.toString() + '$' + innerType.toString();
}
}
private static final class ArrayArg extends KnownType {
private static final PrimitiveType[] ARRAY_POSSIBLES = new PrimitiveType[] { PrimitiveType.ARRAY };
private final ArgType arrayElement;
@@ -465,17 +521,18 @@ public abstract class ArgType {
return null;
}
/**
* @see WildcardType#getWildcardBounds()
*/
public int getWildcardBounds() {
return 0;
public WildcardBound getWildcardBound() {
return null;
}
public ArgType getOuterType() {
return null;
}
public ArgType getInnerType() {
return null;
}
public boolean isArray() {
return false;
}
@@ -622,6 +679,28 @@ public abstract class ArgType {
return 1;
}
public boolean containsGenericType() {
if (isGenericType()) {
return true;
}
ArgType wildcardType = getWildcardType();
if (wildcardType != null) {
return wildcardType.containsGenericType();
}
if (isGeneric()) {
ArgType[] genericTypes = getGenericTypes();
if (genericTypes != null) {
for (ArgType genericType : genericTypes) {
if (genericType.containsGenericType()) {
return true;
}
}
}
return false;
}
return false;
}
public static ArgType tryToResolveClassAlias(DexNode dex, ArgType type) {
if (!type.isObject() || type.isGenericType()) {
return type;
@@ -641,7 +720,7 @@ public abstract class ArgType {
return new GenericObject(aliasFullName, type.getGenericTypes());
}
if (type instanceof WildcardType) {
return new WildcardType(ArgType.object(aliasFullName), type.getWildcardBounds());
return new WildcardType(ArgType.object(aliasFullName), type.getWildcardBound());
}
}
return ArgType.object(aliasFullName);
@@ -19,7 +19,9 @@ public class CodeVar {
var.setName(mthArg.getName());
var.setDeclared(true);
var.setThis(mthArg.isThis());
var.setSsaVars(Collections.singletonList(new SSAVar(mthArg.getRegNum(), 0, mthArg)));
SSAVar ssaVar = new SSAVar(mthArg.getRegNum(), 0, mthArg);
ssaVar.setCodeVar(var);
var.setSsaVars(Collections.singletonList(ssaVar));
return var;
}
@@ -1,88 +0,0 @@
package jadx.core.dex.instructions.args;
import java.util.Objects;
import org.jetbrains.annotations.Nullable;
import jadx.core.dex.info.FieldInfo;
import jadx.core.utils.exceptions.JadxRuntimeException;
// 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
@Nullable
private final InsnArg instArg;
public FieldArg(FieldInfo field, @Nullable InsnArg reg) {
super(-1, field.getType());
this.instArg = reg;
this.field = field;
}
public FieldInfo getField() {
return field;
}
public InsnArg getInstanceArg() {
return instArg;
}
public boolean isStatic() {
return instArg == null;
}
@Override
public boolean isField() {
return true;
}
@Override
public boolean isRegister() {
return false;
}
@Override
public ArgType getType() {
return this.field.getType();
}
@Override
public ArgType getInitType() {
return this.field.getType();
}
@Override
public void setType(ArgType newType) {
throw new JadxRuntimeException("Can't set type for FieldArg");
}
@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;
}
return Objects.equals(instArg, fieldArg.instArg);
}
@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,8 +1,5 @@
package jadx.core.dex.instructions.args;
import java.util.ArrayList;
import java.util.List;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -10,8 +7,12 @@ import org.slf4j.LoggerFactory;
import com.android.dx.io.instructions.DecodedInstruction;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.InsnRemover;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.exceptions.JadxRuntimeException;
/**
* Instruction argument,
@@ -83,10 +84,6 @@ public abstract class InsnArg extends Typed {
return false;
}
public boolean isField() {
return false;
}
@Nullable
public InsnNode getParentInsn() {
return parentInsn;
@@ -96,7 +93,8 @@ public abstract class InsnArg extends Typed {
this.parentInsn = parentInsn;
}
public InsnArg wrapInstruction(InsnNode insn) {
@Nullable("if wrap failed")
public InsnArg wrapInstruction(MethodNode mth, InsnNode insn) {
InsnNode parent = parentInsn;
if (parent == null) {
return null;
@@ -109,18 +107,23 @@ public abstract class InsnArg extends Typed {
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<>();
fromInsn.getRegisterArgs(args);
for (RegisterArg reg : args) {
reg.setParentInsn(toInsn);
if (insn.getType() == InsnType.MOVE && this.isRegister()) {
// preserve variable name for move insn (needed in `for-each` loop for iteration variable)
String name = ((RegisterArg) this).getName();
if (name != null) {
InsnArg arg = insn.getArg(0);
if (arg.isRegister()) {
((RegisterArg) arg).setNameIfUnknown(name);
} else if (arg.isInsnWrap()) {
((InsnWrapArg) arg).getWrapInsn().getResult().setNameIfUnknown(name);
}
}
}
InsnArg arg = wrapInsnIntoArg(insn);
parent.setArg(i, arg);
InsnRemover.unbindArgUsage(mth, this);
InsnRemover.unbindResult(mth, insn);
return arg;
}
private static int getArgIndex(InsnNode parent, InsnArg arg) {
@@ -133,23 +136,50 @@ public abstract class InsnArg extends Typed {
return -1;
}
public static InsnArg wrapArg(InsnNode insn) {
public static InsnArg wrapInsnIntoArg(InsnNode insn) {
InsnArg arg;
InsnType type = insn.getType();
if (type == InsnType.CONST || type == InsnType.MOVE) {
arg = insn.getArg(0);
insn.add(AFlag.REMOVE);
insn.add(AFlag.DONT_GENERATE);
} else {
arg = wrapArg(insn);
}
return arg;
}
/**
* Prefer {@link InsnArg#wrapInsnIntoArg}.
* This method don't support MOVE and CONST insns!
*/
public static InsnArg wrapArg(InsnNode insn) {
RegisterArg resArg = insn.getResult();
InsnArg arg = wrap(insn);
insn.add(AFlag.WRAPPED);
switch (insn.getType()) {
case MOVE:
case CONST:
arg = insn.getArg(0);
break;
case MOVE:
throw new JadxRuntimeException("Don't wrap MOVE or CONST insns: " + insn);
case CONST_STR:
arg = wrap(insn);
arg.setType(ArgType.STRING);
if (resArg != null) {
resArg.setType(ArgType.STRING);
}
break;
case CONST_CLASS:
arg = wrap(insn);
arg.setType(ArgType.CLASS);
if (resArg != null) {
resArg.setType(ArgType.CLASS);
}
break;
default:
arg = wrap(insn);
if (resArg != null) {
arg.setType(resArg.getType());
}
break;
}
return arg;
@@ -159,6 +189,12 @@ public abstract class InsnArg extends Typed {
return contains(AFlag.THIS);
}
protected final <T extends InsnArg> T copyCommonParams(T copy) {
copy.copyAttributesFrom(this);
copy.setParentInsn(parentInsn);
return copy;
}
public InsnArg duplicate() {
return this;
}
@@ -1,7 +1,11 @@
package jadx.core.dex.instructions.args;
import java.util.Objects;
import org.jetbrains.annotations.NotNull;
import jadx.core.dex.instructions.ConstStringNode;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.exceptions.JadxRuntimeException;
@@ -9,9 +13,12 @@ public final class InsnWrapArg extends InsnArg {
private final InsnNode wrappedInsn;
public InsnWrapArg(@NotNull InsnNode insn) {
/**
* Use {@link InsnArg#wrapInsnIntoArg(InsnNode)} instead this constructor
*/
InsnWrapArg(@NotNull InsnNode insn) {
RegisterArg result = insn.getResult();
this.type = result != null ? result.getType() : ArgType.VOID;
this.type = result != null ? result.getType() : ArgType.UNKNOWN;
this.wrappedInsn = insn;
}
@@ -27,6 +34,13 @@ public final class InsnWrapArg extends InsnArg {
this.parentInsn = parentInsn;
}
@Override
public InsnArg duplicate() {
InsnWrapArg copy = new InsnWrapArg(wrappedInsn.copy());
copy.setType(type);
return copyCommonParams(copy);
}
@Override
public boolean isInsnWrap() {
return true;
@@ -62,6 +76,9 @@ public final class InsnWrapArg extends InsnArg {
@Override
public String toString() {
return "(wrap: " + type + "\n " + wrappedInsn + ')';
if (wrappedInsn.getType() == InsnType.CONST_STR && Objects.equals(type, ArgType.STRING)) {
return "(\"" + ((ConstStringNode) wrappedInsn).getString() + "\")";
}
return "(wrap: " + type + " : " + wrappedInsn + ')';
}
}
@@ -48,6 +48,13 @@ public final class LiteralArg extends InsnArg {
|| type == PrimitiveType.LONG;
}
@Override
public InsnArg duplicate() {
LiteralArg copy = new LiteralArg(literal, getType());
copy.type = type;
return copyCommonParams(copy);
}
@Override
public int hashCode() {
return (int) (literal ^ literal >>> 32) + 31 * getType().hashCode();
@@ -70,7 +77,7 @@ public final class LiteralArg extends InsnArg {
@Override
public String toString() {
try {
String value = TypeGen.literalToString(literal, getType(), DEF_STRING_UTILS, true);
String value = TypeGen.literalToString(literal, getType(), DEF_STRING_UTILS, true, false);
if (getType().equals(ArgType.BOOLEAN) && (value.equals("true") || value.equals("false"))) {
return value;
}
@@ -26,6 +26,16 @@ public final class NamedArg extends InsnArg implements Named {
this.name = name;
}
@Override
public InsnArg duplicate() {
return copyCommonParams(new NamedArg(name, type));
}
@Override
public int hashCode() {
return name.hashCode();
}
@Override
public boolean equals(Object o) {
if (this == o) {
@@ -37,11 +47,6 @@ public final class NamedArg extends InsnArg implements Named {
return name.equals(((NamedArg) o).name);
}
@Override
public int hashCode() {
return name.hashCode();
}
@Override
public String toString() {
return '(' + name + ' ' + type + ')';
@@ -4,15 +4,12 @@ import java.util.Objects;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.exceptions.JadxRuntimeException;
public class RegisterArg extends InsnArg implements Named {
private static final Logger LOG = LoggerFactory.getLogger(RegisterArg.class);
public static final String THIS_ARG_NAME = "this";
protected final int regNum;
@@ -38,15 +35,6 @@ public class RegisterArg extends InsnArg implements Named {
if (sVar == null) {
throw new JadxRuntimeException("Can't change type for register without SSA variable: " + this);
}
if (contains(AFlag.IMMUTABLE_TYPE)) {
if (!type.isTypeKnown()) {
throw new JadxRuntimeException("Unknown immutable type '" + type + "' in " + this);
}
if (!type.equals(newType)) {
LOG.warn("JADX WARNING: Can't change immutable type from '{}' to '{}' for {}", type, newType, this);
return;
}
}
sVar.setType(newType);
}
@@ -55,17 +43,30 @@ public class RegisterArg extends InsnArg implements Named {
if (sVar != null) {
return sVar.getTypeInfo().getType();
}
LOG.warn("Register type unknown, SSA variable not initialized: r{}", regNum);
return type;
return ArgType.UNKNOWN;
}
public ArgType getInitType() {
return type;
}
@Nullable
public ArgType getImmutableType() {
if (contains(AFlag.IMMUTABLE_TYPE)) {
return type;
}
if (sVar != null) {
return sVar.getImmutableType();
}
return null;
}
@Override
public boolean isTypeImmutable() {
return contains(AFlag.IMMUTABLE_TYPE) || (sVar != null && sVar.contains(AFlag.IMMUTABLE_TYPE));
if (contains(AFlag.IMMUTABLE_TYPE)) {
return true;
}
return sVar != null && sVar.isTypeImmutable();
}
public SSAVar getSVar() {
@@ -74,9 +75,14 @@ public class RegisterArg extends InsnArg implements Named {
void setSVar(@NotNull SSAVar sVar) {
this.sVar = sVar;
if (contains(AFlag.IMMUTABLE_TYPE)) {
sVar.add(AFlag.IMMUTABLE_TYPE);
}
@Override
public void add(AFlag flag) {
if (flag == AFlag.IMMUTABLE_TYPE && !type.isTypeKnown()) {
throw new JadxRuntimeException("Can't mark unknown type as immutable, type: " + type + ", reg: " + this);
}
super.add(flag);
}
@Override
@@ -97,6 +103,12 @@ public class RegisterArg extends InsnArg implements Named {
}
}
public void setNameIfUnknown(String name) {
if (getName() == null) {
setName(name);
}
}
public boolean isNameEquals(InsnArg arg) {
String n = getName();
if (n == null || !(arg instanceof Named)) {
@@ -113,10 +125,10 @@ public class RegisterArg extends InsnArg implements Named {
public RegisterArg duplicate(int regNum, @Nullable SSAVar sVar) {
RegisterArg dup = new RegisterArg(regNum, getInitType());
if (sVar != null) {
// only 'set' here, 'assign' or 'use' will binds later
dup.setSVar(sVar);
}
dup.copyAttributesFrom(this);
return dup;
return copyCommonParams(dup);
}
@Nullable
@@ -132,6 +144,9 @@ public class RegisterArg extends InsnArg implements Named {
}
public boolean sameRegAndSVar(InsnArg arg) {
if (this == arg) {
return true;
}
if (!arg.isRegister()) {
return false;
}
@@ -140,6 +155,13 @@ public class RegisterArg extends InsnArg implements Named {
&& Objects.equals(sVar, reg.getSVar());
}
public boolean sameReg(InsnArg arg) {
if (!arg.isRegister()) {
return false;
}
return regNum == ((RegisterArg) arg).getRegNum();
}
public boolean sameCodeVar(RegisterArg arg) {
return this.getSVar().getCodeVar() == arg.getSVar().getCodeVar();
}
@@ -165,8 +187,7 @@ public class RegisterArg extends InsnArg implements Named {
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("(r");
sb.append(regNum);
sb.append("(r").append(regNum);
if (sVar != null) {
sb.append('v').append(sVar.getVersion());
}
@@ -9,8 +9,8 @@ import java.util.Set;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.AttrNode;
import jadx.core.dex.attributes.nodes.RegDebugInfoAttr;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.PhiInsn;
@@ -20,7 +20,7 @@ import jadx.core.dex.visitors.typeinference.TypeInfo;
import jadx.core.utils.StringUtils;
import jadx.core.utils.exceptions.JadxRuntimeException;
public class SSAVar extends AttrNode {
public class SSAVar {
private final int regNum;
private final int version;
@@ -66,8 +66,29 @@ public class SSAVar extends AttrNode {
return useList.size();
}
// must be used only from RegisterArg#setType()
void setType(ArgType type) {
@Nullable
public ArgType getImmutableType() {
if (assign.contains(AFlag.IMMUTABLE_TYPE)) {
return assign.getInitType();
}
for (RegisterArg useArg : useList) {
if (useArg.contains(AFlag.IMMUTABLE_TYPE)) {
return useArg.getInitType();
}
}
return null;
}
public boolean isTypeImmutable() {
return getImmutableType() != null;
}
public void setType(ArgType type) {
ArgType imType = getImmutableType();
if (imType != null && !imType.equals(type)) {
throw new JadxRuntimeException("Can't change immutable type " + imType + " to " + type + " for " + this);
}
typeInfo.setType(type);
if (codeVar != null) {
codeVar.setType(type);
@@ -175,6 +196,11 @@ public class SSAVar extends AttrNode {
codeVar.addSsaVar(this);
}
public void resetTypeAndCodeVar() {
this.typeInfo.reset();
this.codeVar = null;
}
public boolean isCodeVarSet() {
return codeVar != null;
}
@@ -1,5 +1,7 @@
package jadx.core.dex.instructions.mods;
import org.jetbrains.annotations.Nullable;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.CallMthInterface;
@@ -9,13 +11,12 @@ import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
public class ConstructorInsn extends InsnNode implements CallMthInterface {
public final class ConstructorInsn extends InsnNode implements CallMthInterface {
private final MethodInfo callMth;
private final CallType callType;
private final RegisterArg instanceArg;
private enum CallType {
public enum CallType {
CONSTRUCTOR, // just new instance
SUPER, // super call
THIS, // call constructor from other constructor
@@ -26,7 +27,7 @@ public class ConstructorInsn extends InsnNode implements CallMthInterface {
super(InsnType.CONSTRUCTOR, invoke.getArgsCount() - 1);
this.callMth = invoke.getCallMth();
ClassInfo classType = callMth.getDeclClass();
instanceArg = (RegisterArg) invoke.getArg(0);
RegisterArg instanceArg = (RegisterArg) invoke.getArg(0);
if (instanceArg.isThis()) {
if (classType.equals(mth.getParentClass().getClassInfo())) {
@@ -52,19 +53,20 @@ public class ConstructorInsn extends InsnNode implements CallMthInterface {
}
}
public ConstructorInsn(MethodInfo callMth, CallType callType, RegisterArg instanceArg) {
public ConstructorInsn(MethodInfo callMth, CallType callType) {
super(InsnType.CONSTRUCTOR, callMth.getArgsCount());
this.callMth = callMth;
this.callType = callType;
this.instanceArg = instanceArg;
}
public MethodInfo getCallMth() {
return callMth;
}
@Override
@Nullable
public RegisterArg getInstanceArg() {
return instanceArg;
return null;
}
public ClassInfo getClassType() {
@@ -91,6 +93,11 @@ public class ConstructorInsn extends InsnNode implements CallMthInterface {
return callType == CallType.SELF;
}
@Override
public int getFirstArgOffset() {
return 0;
}
@Override
public boolean isSame(InsnNode obj) {
if (this == obj) {
@@ -104,8 +111,13 @@ public class ConstructorInsn extends InsnNode implements CallMthInterface {
&& callType == other.callType;
}
@Override
public InsnNode copy() {
return copyCommonParams(new ConstructorInsn(callMth, callType));
}
@Override
public String toString() {
return super.toString() + ' ' + callMth + ' ' + callType;
return super.toString() + " call: " + callMth + " type: " + callType;
}
}
@@ -9,14 +9,13 @@ import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.regions.conditions.IfCondition;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.Utils;
public final class TernaryInsn extends InsnNode {
private IfCondition condition;
public TernaryInsn(IfCondition condition, RegisterArg result, InsnArg th, InsnArg els) {
super(InsnType.TERNARY, 2);
this();
setResult(result);
if (th.equals(LiteralArg.FALSE) && els.equals(LiteralArg.TRUE)) {
@@ -31,6 +30,10 @@ public final class TernaryInsn extends InsnNode {
}
}
private TernaryInsn() {
super(InsnType.TERNARY, 2);
}
public IfCondition getCondition() {
return condition;
}
@@ -67,10 +70,27 @@ public final class TernaryInsn extends InsnNode {
return condition.equals(that.condition);
}
@Override
public InsnNode copy() {
TernaryInsn copy = new TernaryInsn();
copy.condition = condition;
return copyCommonParams(copy);
}
@Override
public void rebindArgs() {
super.rebindArgs();
for (RegisterArg reg : condition.getRegisterArgs()) {
InsnNode parentInsn = reg.getParentInsn();
if (parentInsn != null) {
parentInsn.rebindArgs();
}
}
}
@Override
public String toString() {
return InsnUtils.formatOffset(offset) + ": TERNARY"
+ getResult() + " = "
+ Utils.listToString(getArguments());
+ getResult() + " = (" + condition + ") ? " + getArg(0) + " : " + getArg(1);
}
}
@@ -16,8 +16,10 @@ import com.android.dex.ClassData.Method;
import com.android.dex.ClassDef;
import com.android.dex.Dex;
import jadx.api.ICodeCache;
import jadx.api.ICodeInfo;
import jadx.core.Consts;
import jadx.core.codegen.CodeWriter;
import jadx.core.ProcessClass;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.annotations.Annotation;
import jadx.core.dex.attributes.nodes.LineAttrNode;
@@ -33,33 +35,34 @@ import jadx.core.dex.nodes.parser.AnnotationsParser;
import jadx.core.dex.nodes.parser.FieldInitAttr;
import jadx.core.dex.nodes.parser.SignatureParser;
import jadx.core.dex.nodes.parser.StaticValuesParser;
import jadx.core.utils.SmaliUtils;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import static jadx.core.dex.nodes.ProcessState.LOADED;
import static jadx.core.dex.nodes.ProcessState.UNLOADED;
public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
private static final Logger LOG = LoggerFactory.getLogger(ClassNode.class);
private final DexNode dex;
private final int clsDefOffset;
private final ClassInfo clsInfo;
private AccessInfo accessFlags;
private ArgType superClass;
private List<ArgType> interfaces;
private Map<ArgType, List<ArgType>> genericMap;
private List<GenericInfo> generics = Collections.emptyList();
private final List<MethodNode> methods;
private final List<FieldNode> fields;
private List<ClassNode> innerClasses = new ArrayList<>();
private List<ClassNode> innerClasses = Collections.emptyList();
// store decompiled code
private CodeWriter code;
// store smali
private String smali;
// store parent for inner classes or 'this' otherwise
private ClassNode parentClass;
private ProcessState state = ProcessState.NOT_LOADED;
private volatile ProcessState state = ProcessState.NOT_LOADED;
private List<ClassNode> dependencies = Collections.emptyList();
// cache maps
@@ -67,6 +70,7 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
public ClassNode(DexNode dex, ClassDef cls) {
this.dex = dex;
this.clsDefOffset = cls.getOffset();
this.clsInfo = ClassInfo.fromDex(dex, cls.getTypeIndex());
try {
if (cls.getSupertypeIndex() == DexNode.NO_INDEX) {
@@ -106,9 +110,10 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
}
loadAnnotations(cls);
initAccessFlags(cls);
parseClassSignature();
setFieldsTypesFromSignature();
methods.forEach(MethodNode::initMethodTypes);
int sfIdx = cls.getSourceFileIndex();
if (sfIdx != DexNode.NO_INDEX) {
@@ -116,24 +121,30 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
addSourceFilenameAttr(fileName);
}
// restore original access flags from dalvik annotation if present
int accFlagsValue;
Annotation a = getAnnotation(Consts.DALVIK_INNER_CLASS);
if (a != null) {
accFlagsValue = (Integer) a.getValues().get("accessFlags");
} else {
accFlagsValue = cls.getAccessFlags();
}
this.accessFlags = new AccessInfo(accFlagsValue, AFType.CLASS);
buildCache();
} catch (Exception e) {
throw new JadxRuntimeException("Error decode class: " + clsInfo, e);
}
}
/**
* Restore original access flags from Dalvik annotation if present
*/
private void initAccessFlags(ClassDef cls) {
int accFlagsValue;
Annotation a = getAnnotation(Consts.DALVIK_INNER_CLASS);
if (a != null) {
accFlagsValue = (Integer) a.getValues().get("accessFlags");
} else {
accFlagsValue = cls.getAccessFlags();
}
this.accessFlags = new AccessInfo(accFlagsValue, AFType.CLASS);
}
// empty synthetic class
public ClassNode(DexNode dex, String name, int accessFlags) {
this.dex = dex;
this.clsDefOffset = 0;
this.clsInfo = ClassInfo.fromName(dex.root(), name);
this.interfaces = new ArrayList<>();
this.methods = new ArrayList<>();
@@ -180,7 +191,7 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
}
try {
// parse class generic map
genericMap = sp.consumeGenericMap();
generics = sp.consumeGenericMap();
// parse super class signature
superClass = sp.consumeType();
// parse interfaces signatures
@@ -234,14 +245,40 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
&& fileName.endsWith('$' + name)) {
return;
}
ClassInfo parentClass = clsInfo.getTopParentClass();
if (parentClass != null && fileName.equals(parentClass.getShortName())) {
ClassInfo parentCls = clsInfo.getTopParentClass();
if (parentCls != null && fileName.equals(parentCls.getShortName())) {
return;
}
}
this.addAttr(new SourceFileAttr(fileName));
}
public void ensureProcessed() {
ClassNode topClass = getTopParentClass();
ProcessState topState = topClass.getState();
if (!topState.isProcessed()) {
throw new JadxRuntimeException("Expected class to be processed at this point,"
+ " class: " + topClass + ", state: " + topState);
}
}
public synchronized ICodeInfo decompile() {
ICodeCache codeCache = root().getCodeCache();
ClassNode topParentClass = getTopParentClass();
String clsRawName = topParentClass.getRawName();
ICodeInfo code = codeCache.get(clsRawName);
if (code != null) {
return code;
}
ICodeInfo codeInfo = ProcessClass.generateCode(topParentClass);
codeCache.add(clsRawName, codeInfo);
return codeInfo;
}
public ICodeInfo getCode() {
return decompile();
}
@Override
public void load() {
for (MethodNode mth : getMethods()) {
@@ -254,16 +291,15 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
for (ClassNode innerCls : getInnerClasses()) {
innerCls.load();
}
setState(LOADED);
}
@Override
public void unload() {
for (MethodNode mth : getMethods()) {
mth.unload();
}
for (ClassNode innerCls : getInnerClasses()) {
innerCls.unload();
}
methods.forEach(MethodNode::unload);
innerClasses.forEach(ClassNode::unload);
fields.forEach(FieldNode::unloadAttributes);
unloadAttributes();
setState(UNLOADED);
}
@@ -283,8 +319,8 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
return interfaces;
}
public Map<ArgType, List<ArgType>> getGenericMap() {
return genericMap;
public List<GenericInfo> getGenerics() {
return generics;
}
public List<MethodNode> getMethods() {
@@ -389,11 +425,25 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
return parent == this ? this : parent.getTopParentClass();
}
public boolean hasNotGeneratedParent() {
if (contains(AFlag.DONT_GENERATE)) {
return true;
}
ClassNode parent = getParentClass();
if (parent == this) {
return false;
}
return parent.hasNotGeneratedParent();
}
public List<ClassNode> getInnerClasses() {
return innerClasses;
}
public void addInnerClass(ClassNode cls) {
if (innerClasses.isEmpty()) {
innerClasses = new ArrayList<>(5);
}
innerClasses.add(cls);
cls.parentClass = this;
}
@@ -471,19 +521,10 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
return clsInfo.getAliasPkg();
}
public void setCode(CodeWriter code) {
this.code = code;
}
public CodeWriter getCode() {
return code;
}
public void setSmali(String smali) {
this.smali = smali;
}
public String getSmali() {
if (smali == null) {
smali = SmaliUtils.getSmaliCode(dex, clsDefOffset);
}
return smali;
}
@@ -524,4 +565,5 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
public String toString() {
return clsInfo.getFullName();
}
}
@@ -9,6 +9,8 @@ import java.util.Map;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.android.dex.ClassData;
import com.android.dex.ClassData.Method;
@@ -25,9 +27,11 @@ import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.files.DexFile;
public class DexNode implements IDexNode {
private static final Logger LOG = LoggerFactory.getLogger(DexNode.class);
public static final int NO_INDEX = -1;
@@ -50,12 +54,35 @@ public class DexNode implements IDexNode {
public void loadClasses() {
for (ClassDef cls : dexBuf.classDefs()) {
addClassNode(new ClassNode(this, cls));
try {
addClassNode(new ClassNode(this, cls));
} catch (Exception e) {
addDummyClass(cls, e);
}
}
// sort classes by name, expect top classes before inner
classes.sort(Comparator.comparing(ClassNode::getFullName));
}
private void addDummyClass(ClassDef classDef, Exception exc) {
int typeIndex = classDef.getTypeIndex();
String name = null;
try {
ClassInfo clsInfo = ClassInfo.fromDex(this, typeIndex);
if (clsInfo != null) {
name = clsInfo.getShortName();
}
} catch (Exception e) {
LOG.error("Failed to get name for class with type {}", typeIndex, e);
}
if (name == null || name.isEmpty()) {
name = "CLASS_" + typeIndex;
}
ClassNode clsNode = new ClassNode(this, name, classDef.getAccessFlags());
ErrorsCounter.classError(clsNode, "Load error", exc);
addClassNode(clsNode);
}
public void addClassNode(ClassNode clsNode) {
classes.add(clsNode);
clsMap.put(clsNode.getClassInfo(), clsNode);
@@ -0,0 +1,46 @@
package jadx.core.dex.nodes;
import java.util.List;
import jadx.core.dex.instructions.args.ArgType;
public class GenericInfo {
private final ArgType genericType;
private final List<ArgType> extendsList;
public GenericInfo(ArgType genericType, List<ArgType> extendsList) {
this.genericType = genericType;
this.extendsList = extendsList;
}
public ArgType getGenericType() {
return genericType;
}
public List<ArgType> getExtendsList() {
return extendsList;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
GenericInfo other = (GenericInfo) o;
return genericType.equals(other.genericType)
&& extendsList.equals(other.extendsList);
}
@Override
public int hashCode() {
return 31 * genericType.hashCode() + extendsList.hashCode();
}
@Override
public String toString() {
return "GenericInfo{" + genericType + " extends: " + extendsList + '}';
}
}
@@ -9,7 +9,6 @@ import java.util.Objects;
import org.jetbrains.annotations.Nullable;
import com.android.dx.io.instructions.DecodedInstruction;
import com.rits.cloning.Cloner;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.nodes.LineAttrNode;
@@ -17,23 +16,14 @@ 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.instructions.args.NamedArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.utils.InsnRemover;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
public class InsnNode extends LineAttrNode {
private static final Cloner INSN_CLONER = new Cloner();
static {
INSN_CLONER.dontClone(ArgType.class, SSAVar.class, LiteralArg.class, NamedArg.class);
INSN_CLONER.dontCloneInstanceOf(RegisterArg.class);
}
protected final InsnType insnType;
private RegisterArg result;
@@ -41,13 +31,15 @@ public class InsnNode extends LineAttrNode {
protected int offset;
public InsnNode(InsnType type, int argsCount) {
this.insnType = type;
this.offset = -1;
this(type, argsCount == 0 ? Collections.emptyList() : new ArrayList<>(argsCount));
}
if (argsCount == 0) {
this.arguments = Collections.emptyList();
} else {
this.arguments = new ArrayList<>(argsCount);
public InsnNode(InsnType type, List<InsnArg> args) {
this.insnType = type;
this.arguments = args;
this.offset = -1;
for (InsnArg arg : args) {
attachArg(arg);
}
}
@@ -78,7 +70,7 @@ public class InsnNode extends LineAttrNode {
attachArg(arg);
}
private void attachArg(InsnArg arg) {
protected void attachArg(InsnArg arg) {
arg.setParentInsn(this);
if (arg.isRegister()) {
RegisterArg reg = (RegisterArg) arg;
@@ -109,10 +101,24 @@ public class InsnNode extends LineAttrNode {
return arguments.get(n);
}
public boolean containsArg(RegisterArg arg) {
public boolean containsArg(InsnArg arg) {
if (getArgsCount() == 0) {
return false;
}
for (InsnArg a : arguments) {
if (a == arg
|| a.isRegister() && ((RegisterArg) a).getRegNum() == arg.getRegNum()) {
if (a == arg) {
return true;
}
}
return false;
}
public boolean containsVar(RegisterArg arg) {
if (getArgsCount() == 0) {
return false;
}
for (InsnArg insnArg : arguments) {
if (insnArg == arg || arg.sameRegAndSVar(insnArg)) {
return true;
}
}
@@ -147,14 +153,14 @@ public class InsnNode extends LineAttrNode {
return true;
}
protected InsnArg removeArg(int index) {
public InsnArg removeArg(int index) {
InsnArg arg = arguments.get(index);
arguments.remove(index);
InsnRemover.unbindArgUsage(null, arg);
return arg;
}
protected int getArgIndex(InsnArg arg) {
public int getArgIndex(InsnArg arg) {
int count = getArgsCount();
for (int i = 0; i < count; i++) {
if (arg == arguments.get(i)) {
@@ -217,6 +223,15 @@ public class InsnNode extends LineAttrNode {
}
return true;
}
for (InsnArg arg : getArguments()) {
if (arg.isInsnWrap()) {
InsnNode wrapInsn = ((InsnWrapArg) arg).getWrapInsn();
if (!wrapInsn.canReorder()) {
return false;
}
}
}
switch (getType()) {
case CONST:
case CONST_STR:
@@ -256,30 +271,6 @@ public class InsnNode extends LineAttrNode {
return true;
}
@Override
public String toString() {
return InsnUtils.formatOffset(offset) + ": "
+ InsnUtils.insnTypeToString(insnType)
+ (result == null ? "" : result + " = ")
+ Utils.listToString(arguments);
}
/**
* Compare instruction only by identity.
*/
@Override
public final int hashCode() {
return super.hashCode();
}
/**
* Compare instruction only by identity.
*/
@Override
public final boolean equals(Object obj) {
return super.equals(obj);
}
/**
* 'Soft' equals, don't compare arguments, only instruction specific parameters.
*/
@@ -324,15 +315,17 @@ public class InsnNode extends LineAttrNode {
&& Objects.equals(arguments, other.arguments);
}
protected <T extends InsnNode> T copyCommonParams(T copy) {
copy.setResult(result);
protected final <T extends InsnNode> T copyCommonParams(T copy) {
if (copy.getResult() == null && result != null) {
copy.setResult(result.duplicate());
}
if (copy.getArgsCount() == 0) {
for (InsnArg arg : this.getArguments()) {
if (arg.isInsnWrap()) {
InsnNode wrapInsn = ((InsnWrapArg) arg).getWrapInsn();
copy.addArg(InsnArg.wrapArg(wrapInsn.copy()));
copy.addArg(InsnArg.wrapInsnIntoArg(wrapInsn.copy()));
} else {
copy.addArg(arg);
copy.addArg(arg.duplicate());
}
}
}
@@ -346,10 +339,31 @@ public class InsnNode extends LineAttrNode {
* Make copy of InsnNode object.
*/
public InsnNode copy() {
if (this.getClass() == InsnNode.class) {
return copyCommonParams(new InsnNode(insnType, getArgsCount()));
if (this.getClass() != InsnNode.class) {
throw new JadxRuntimeException("Copy method not implemented in insn class " + this.getClass().getSimpleName());
}
return copyCommonParams(new InsnNode(insnType, getArgsCount()));
}
/**
* Fix SSAVar info in register arguments.
* Must be used after altering instructions.
*/
public void rebindArgs() {
RegisterArg resArg = getResult();
if (resArg != null) {
resArg.getSVar().setAssign(resArg);
}
for (InsnArg arg : getArguments()) {
if (arg instanceof RegisterArg) {
RegisterArg reg = (RegisterArg) arg;
SSAVar ssaVar = reg.getSVar();
ssaVar.use(reg);
ssaVar.updateUsedInPhiList();
} else if (arg instanceof InsnWrapArg) {
((InsnWrapArg) arg).getWrapInsn().rebindArgs();
}
}
return INSN_CLONER.deepClone(this);
}
public boolean canThrowException() {
@@ -371,4 +385,45 @@ public class InsnNode extends LineAttrNode {
return true;
}
}
/**
* Compare instruction only by identity.
*/
@Override
public final int hashCode() {
return super.hashCode();
}
/**
* Compare instruction only by identity.
*/
@Override
public final boolean equals(Object obj) {
return super.equals(obj);
}
protected void appendArgs(StringBuilder sb) {
String argsStr = Utils.listToString(arguments);
if (argsStr.length() < 60) {
sb.append(argsStr);
} else {
// wrap args
String separator = "\n ";
sb.append(separator).append(Utils.listToString(arguments, separator));
sb.append('\n');
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(InsnUtils.formatOffset(offset));
sb.append(": ");
sb.append(InsnUtils.insnTypeToString(insnType));
if (result != null) {
sb.append(result).append(" = ");
}
appendArgs(sb);
return sb.toString();
}
}
@@ -4,7 +4,6 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jetbrains.annotations.NotNull;
@@ -59,23 +58,27 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
private boolean noCode;
private int regsCount;
private InsnNode[] instructions;
private int codeSize;
private int debugInfoOffset;
private boolean loaded;
// additional info available after load, keep on unload
private ArgType retType;
private List<ArgType> argTypes;
private List<GenericInfo> generics;
// decompilation data, reset on unload
private RegisterArg thisArg;
private List<RegisterArg> argsList;
private List<SSAVar> sVars;
private Map<ArgType, List<ArgType>> genericMap;
private InsnNode[] instructions;
private List<BlockNode> blocks;
private BlockNode enterBlock;
private List<BlockNode> exitBlocks;
private Region region;
private List<SSAVar> sVars;
private List<ExceptionHandler> exceptionHandlers;
private List<LoopInfo> loops;
private Region region;
public MethodNode(ClassNode classNode, Method mthData, boolean isVirtual) {
this.mthInfo = MethodInfo.fromDex(classNode.dex(), mthData.getMethodIndex());
@@ -89,13 +92,14 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
@Override
public void unload() {
loaded = false;
if (noCode) {
return;
}
// don't unload retType and argsList, will be used in jadx-gui after class unload
// don't unload retType, argTypes, generics
thisArg = null;
argsList = null;
sVars = Collections.emptyList();
genericMap = null;
instructions = null;
blocks = null;
enterBlock = null;
@@ -103,22 +107,29 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
region = null;
exceptionHandlers = Collections.emptyList();
loops = Collections.emptyList();
unloadAttributes();
}
@Override
public void load() throws DecodeException {
if (loaded) {
// method already loaded
return;
}
try {
loaded = true;
if (noCode) {
regsCount = 0;
codeSize = 0;
initMethodTypes();
// TODO: registers not needed without code
initArguments(this.argTypes);
return;
}
DexNode dex = parentClass.dex();
Code mthCode = dex.readCode(methodData);
this.regsCount = mthCode.getRegistersSize();
initMethodTypes();
initArguments(this.argTypes);
InsnDecoder decoder = new InsnDecoder(this);
decoder.decodeInsns(mthCode);
@@ -131,6 +142,7 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
this.debugInfoOffset = mthCode.getDebugInfoOffset();
} catch (Exception e) {
if (!noCode) {
unload();
noCode = true;
// load without code
load();
@@ -161,49 +173,63 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
}
}
private void initMethodTypes() {
if (!parseSignature()) {
retType = mthInfo.getReturnType();
initArguments(mthInfo.getArgumentsTypes());
public void initMethodTypes() {
List<ArgType> types = parseSignature();
if (types == null) {
this.retType = mthInfo.getReturnType();
this.argTypes = mthInfo.getArgumentsTypes();
} else {
this.argTypes = types;
}
}
private boolean parseSignature() {
@Nullable
private List<ArgType> parseSignature() {
SignatureParser sp = SignatureParser.fromNode(this);
if (sp == null) {
return false;
return null;
}
try {
genericMap = sp.consumeGenericMap();
this.generics = sp.consumeGenericMap();
List<ArgType> argsTypes = sp.consumeMethodArgs();
retType = sp.consumeType();
this.retType = sp.consumeType();
List<ArgType> mthArgs = mthInfo.getArgumentsTypes();
if (argsTypes.size() != mthArgs.size()) {
if (argsTypes.isEmpty()) {
return false;
return null;
}
if (!mthInfo.isConstructor()) {
LOG.warn("Wrong signature parse result: {} -> {}, not generic version: {}", sp, argsTypes, mthArgs);
return false;
} else if (getParentClass().getAccessFlags().isEnum()) {
// TODO:
argsTypes.add(0, mthArgs.get(0));
argsTypes.add(1, mthArgs.get(1));
} else {
// add synthetic arg for outer class
argsTypes.add(0, mthArgs.get(0));
}
if (argsTypes.size() != mthArgs.size()) {
return false;
if (!tryFixArgsCounts(argsTypes, mthArgs)) {
if (LOG.isDebugEnabled()) {
LOG.debug("Incorrect method signature, types: ({}), method: {}", Utils.listToString(argsTypes), this);
}
return null;
}
}
initArguments(argsTypes);
return true;
} catch (JadxRuntimeException e) {
LOG.error("Method signature parse error: {}", this, e);
return argsTypes;
} catch (Exception e) {
addWarningComment("Failed to parse method signature: " + sp.getSignature(), e);
return null;
}
}
private boolean tryFixArgsCounts(List<ArgType> argsTypes, List<ArgType> mthArgs) {
if (!mthInfo.isConstructor()) {
return false;
}
if (getParentClass().getAccessFlags().isEnum()) {
if (mthArgs.size() >= 2) {
// TODO:
argsTypes.add(0, mthArgs.get(0));
argsTypes.add(1, mthArgs.get(1));
}
} else {
if (!mthArgs.isEmpty()) {
// add synthetic arg for outer class
argsTypes.add(0, mthArgs.get(0));
}
}
return argsTypes.size() == mthArgs.size();
}
private void initArguments(List<ArgType> args) {
@@ -229,27 +255,40 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
return;
}
argsList = new ArrayList<>(args.size());
for (ArgType arg : args) {
RegisterArg regArg = InsnArg.reg(pos, arg);
for (ArgType argType : args) {
RegisterArg regArg = InsnArg.reg(pos, argType);
regArg.add(AFlag.METHOD_ARGUMENT);
regArg.add(AFlag.IMMUTABLE_TYPE);
argsList.add(regArg);
pos += arg.getRegCount();
pos += argType.getRegCount();
}
}
public List<RegisterArg> getArguments(boolean includeThis) {
if (includeThis && thisArg != null) {
List<RegisterArg> list = new ArrayList<>(argsList.size() + 1);
list.add(thisArg);
list.addAll(argsList);
return list;
@NotNull
public List<ArgType> getArgTypes() {
if (argTypes == null) {
throw new JadxRuntimeException("Method types not initialized: " + this);
}
return argTypes;
}
public List<RegisterArg> getArgRegs() {
if (argsList == null) {
throw new JadxRuntimeException("Method args not loaded: " + this
+ ", class status: " + parentClass.getTopParentClass().getState());
}
return argsList;
}
public void skipFirstArgument() {
this.add(AFlag.SKIP_FIRST_ARG);
public List<RegisterArg> getAllArgRegs() {
List<RegisterArg> argRegs = getArgRegs();
if (thisArg != null) {
List<RegisterArg> list = new ArrayList<>(argRegs.size() + 1);
list.add(thisArg);
list.addAll(argRegs);
return list;
}
return argRegs;
}
@Nullable
@@ -257,12 +296,16 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
return thisArg;
}
public void skipFirstArgument() {
this.add(AFlag.SKIP_FIRST_ARG);
}
public ArgType getReturnType() {
return retType;
}
public Map<ArgType, List<ArgType>> getGenericMap() {
return genericMap;
public List<GenericInfo> getGenerics() {
return generics;
}
private static void initTryCatches(MethodNode mth, Code mthCode, InsnNode[] insnByOffset) {
@@ -299,13 +342,11 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
// resolve nested try blocks:
// inner block contains all handlers from outer block => remove these handlers from inner block
// each handler must be only in one try/catch block
for (TryCatchBlock ct1 : catches) {
for (TryCatchBlock ct2 : catches) {
if (ct1 != ct2 && ct2.containsAllHandlers(ct1)) {
for (ExceptionHandler h : ct1.getHandlers()) {
ct2.removeHandler(mth, h);
h.setTryBlock(ct1);
}
for (TryCatchBlock outerTry : catches) {
for (TryCatchBlock innerTry : catches) {
if (outerTry != innerTry
&& innerTry.containsAllHandlers(outerTry)) {
innerTry.removeSameHandlers(outerTry);
}
}
}
@@ -518,6 +559,10 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
return handler;
}
public boolean clearExceptionHandlers() {
return exceptionHandlers.removeIf(ExceptionHandler::isRemoved);
}
public Iterable<ExceptionHandler> getExceptionHandlers() {
return exceptionHandlers;
}
@@ -653,6 +698,20 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
ErrorsCounter.methodWarn(this, warnStr);
}
public void addWarningComment(String warn) {
addWarningComment(warn, null);
}
public void addWarningComment(String warn, @Nullable Throwable exc) {
String commentStr = "JADX WARN: " + warn;
addAttr(AType.COMMENTS, commentStr);
if (exc != null) {
LOG.warn("{} in {}", warn, this, exc);
} else {
LOG.warn("{} in {}", warn, this);
}
}
public void addComment(String commentStr) {
addAttr(AType.COMMENTS, commentStr);
LOG.info("{} in {}", commentStr, this);
@@ -684,6 +743,10 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
return -1;
}
public boolean isLoaded() {
return loaded;
}
@Override
public int hashCode() {
return mthInfo.hashCode();
@@ -2,8 +2,17 @@ package jadx.core.dex.nodes;
public enum ProcessState {
NOT_LOADED,
STARTED,
PROCESSED,
LOADED,
PROCESS_STARTED,
PROCESS_COMPLETE,
GENERATED,
UNLOADED
UNLOADED;
public boolean isLoaded() {
return this != NOT_LOADED;
}
public boolean isProcessed() {
return this == PROCESS_COMPLETE || this == GENERATED || this == UNLOADED;
}
}
@@ -1,6 +1,7 @@
package jadx.core.dex.nodes;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.jetbrains.annotations.NotNull;
@@ -8,16 +9,22 @@ import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.ICodeCache;
import jadx.api.JadxArgs;
import jadx.api.ResourceFile;
import jadx.api.ResourceType;
import jadx.api.ResourcesLoader;
import jadx.core.Jadx;
import jadx.core.clsp.ClspGraph;
import jadx.core.clsp.NClass;
import jadx.core.clsp.NMethod;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.ConstStorage;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.InfoStorage;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.dex.visitors.typeinference.TypeUpdate;
import jadx.core.utils.CacheStorage;
import jadx.core.utils.ErrorsCounter;
@@ -32,14 +39,18 @@ import jadx.core.xmlgen.ResourceStorage;
public class RootNode {
private static final Logger LOG = LoggerFactory.getLogger(RootNode.class);
private final ErrorsCounter errorsCounter = new ErrorsCounter();
private final JadxArgs args;
private final List<IDexTreeVisitor> passes;
private final ErrorsCounter errorsCounter = new ErrorsCounter();
private final StringUtils stringUtils;
private final ConstStorage constValues;
private final InfoStorage infoStorage = new InfoStorage();
private final CacheStorage cacheStorage = new CacheStorage();
private final TypeUpdate typeUpdate;
private final ICodeCache codeCache;
private ClspGraph clsp;
private List<DexNode> dexNodes;
@Nullable
@@ -49,9 +60,11 @@ public class RootNode {
public RootNode(JadxArgs args) {
this.args = args;
this.passes = Jadx.getPassesList(args);
this.stringUtils = new StringUtils(args);
this.constValues = new ConstStorage(args);
this.typeUpdate = new TypeUpdate(this);
this.codeCache = args.getCodeCache();
}
public void load(List<InputFile> inputFiles) {
@@ -161,6 +174,20 @@ public class RootNode {
return resolveClass(clsInfo);
}
@Nullable
public ClassNode searchClassByFullAlias(String fullName) {
for (DexNode dexNode : dexNodes) {
for (ClassNode cls : dexNode.getClasses()) {
ClassInfo classInfo = cls.getClassInfo();
if (classInfo.getFullName().equals(fullName)
|| classInfo.getAliasFullName().equals(fullName)) {
return cls;
}
}
}
return null;
}
public List<ClassNode> searchClassByShortName(String shortName) {
List<ClassNode> list = new ArrayList<>();
for (DexNode dexNode : dexNodes) {
@@ -191,6 +218,74 @@ public class RootNode {
return cls.dex().deepResolveField(cls, field);
}
public List<IDexTreeVisitor> getPasses() {
return passes;
}
public void initPasses() {
for (IDexTreeVisitor pass : passes) {
try {
pass.init(this);
} catch (Exception e) {
LOG.error("Visitor init failed: {}", pass.getClass().getSimpleName(), e);
}
}
}
@Nullable
public ArgType getMethodGenericReturnType(MethodInfo callMth) {
MethodNode methodNode = deepResolveMethod(callMth);
if (methodNode != null) {
ArgType returnType = methodNode.getReturnType();
if (returnType != null && (returnType.isGeneric() || returnType.isGenericType())) {
return returnType;
}
return null;
}
NMethod methodDetails = clsp.getMethodDetails(callMth);
if (methodDetails != null) {
return methodDetails.getReturnType();
}
return null;
}
public List<ArgType> getMethodArgTypes(MethodInfo callMth) {
MethodNode methodNode = deepResolveMethod(callMth);
if (methodNode != null) {
return methodNode.getArgTypes();
}
NMethod methodDetails = clsp.getMethodDetails(callMth);
if (methodDetails != null && methodDetails.getGenericArgs() != null) {
List<ArgType> argTypes = callMth.getArgumentsTypes();
int argsCount = argTypes.size();
List<ArgType> list = new ArrayList<>(argsCount);
for (int i = 0; i < argsCount; i++) {
ArgType genericArgType = methodDetails.getGenericArg(i);
if (genericArgType != null) {
list.add(genericArgType);
} else {
list.add(argTypes.get(i));
}
}
return list;
}
return Collections.emptyList();
}
@NotNull
public List<GenericInfo> getClassGenerics(ArgType type) {
ClassNode classNode = resolveClass(ClassInfo.fromType(this, type));
if (classNode != null) {
return classNode.getGenerics();
}
NClass clsDetails = getClsp().getClsDetails(type);
if (clsDetails == null || clsDetails.getGenerics().isEmpty()) {
return Collections.emptyList();
}
List<GenericInfo> generics = clsDetails.getGenerics();
return generics == null ? Collections.emptyList() : generics;
}
public List<DexNode> getDexNodes() {
return dexNodes;
}
@@ -235,4 +330,9 @@ public class RootNode {
public TypeUpdate getTypeUpdate() {
return typeUpdate;
}
public ICodeCache getCodeCache() {
return codeCache;
}
}
@@ -1,10 +1,9 @@
package jadx.core.dex.nodes.parser;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -13,6 +12,7 @@ import jadx.core.Consts;
import jadx.core.dex.attributes.IAttributeNode;
import jadx.core.dex.attributes.annotations.Annotation;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.GenericInfo;
import jadx.core.utils.exceptions.JadxRuntimeException;
public class SignatureParser {
@@ -38,7 +38,8 @@ public class SignatureParser {
if (a == null) {
return null;
}
return new SignatureParser(mergeSignature((List<String>) a.getDefaultValue()));
String signature = mergeSignature((List<String>) a.getDefaultValue());
return new SignatureParser(signature);
}
private char next() {
@@ -183,7 +184,7 @@ public class SignatureParser {
if (inner == null) {
throw new JadxRuntimeException("No inner type found: " + debugString());
}
return ArgType.genericInner(genericType, inner.getObject(), inner.getGenericTypes());
return ArgType.outerGeneric(genericType, inner);
} else {
consume(';');
return genericType;
@@ -200,10 +201,10 @@ public class SignatureParser {
type = ArgType.wildcard();
} else if (lookAhead('+')) {
next();
type = ArgType.wildcard(consumeType(), 1);
type = ArgType.wildcard(consumeType(), ArgType.WildcardBound.EXTENDS);
} else if (lookAhead('-')) {
next();
type = ArgType.wildcard(consumeType(), -1);
type = ArgType.wildcard(consumeType(), ArgType.WildcardBound.SUPER);
} else {
type = consumeType();
}
@@ -219,11 +220,11 @@ public class SignatureParser {
* <p/>
* Example: "<T:Ljava/lang/Exception;:Ljava/lang/Object;>"
*/
public Map<ArgType, List<ArgType>> consumeGenericMap() {
public List<GenericInfo> consumeGenericMap() {
if (!lookAhead('<')) {
return Collections.emptyMap();
return Collections.emptyList();
}
Map<ArgType, List<ArgType>> map = new LinkedHashMap<>(2);
List<GenericInfo> list = new ArrayList<>();
consume('<');
while (true) {
if (lookAhead('>') || next() == STOP_CHAR) {
@@ -231,15 +232,15 @@ public class SignatureParser {
}
String id = consumeUntil(':');
if (id == null) {
LOG.error("Can't parse generic map: {}", sign);
return Collections.emptyMap();
LOG.error("Failed to parse generic map: {}", sign);
return Collections.emptyList();
}
tryConsume(':');
List<ArgType> types = consumeExtendsTypesList();
map.put(ArgType.genericType(id), types);
list.add(new GenericInfo(ArgType.genericType(id), types));
}
consume('>');
return map;
return list;
}
/**
@@ -291,6 +292,10 @@ public class SignatureParser {
return sb.toString();
}
public String getSignature() {
return sign;
}
private String debugString() {
if (pos >= sign.length()) {
return sign;
@@ -1,5 +1,6 @@
package jadx.core.dex.regions.conditions;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.instructions.IfNode;
import jadx.core.dex.instructions.IfOp;
import jadx.core.dex.instructions.args.InsnArg;
@@ -9,6 +10,7 @@ public final class Compare {
private final IfNode insn;
public Compare(IfNode insn) {
insn.add(AFlag.HIDDEN);
this.insn = insn;
}
@@ -1,9 +1,12 @@
package jadx.core.dex.regions.conditions;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.InsnNode;
public final class IfInfo {
private final IfCondition condition;
@@ -11,32 +14,35 @@ public final class IfInfo {
private final BlockNode thenBlock;
private final BlockNode elseBlock;
private final Set<BlockNode> skipBlocks;
private final List<InsnNode> forceInlineInsns;
private BlockNode outBlock;
@Deprecated
private BlockNode ifBlock;
public IfInfo(IfCondition condition, BlockNode thenBlock, BlockNode elseBlock) {
this(condition, thenBlock, elseBlock, new HashSet<>(), new HashSet<>());
this(condition, thenBlock, elseBlock, new HashSet<>(), new HashSet<>(), new ArrayList<>());
}
public IfInfo(IfInfo info, BlockNode thenBlock, BlockNode elseBlock) {
this(info.getCondition(), thenBlock, elseBlock, info.getMergedBlocks(), info.getSkipBlocks());
this(info.getCondition(), thenBlock, elseBlock,
info.getMergedBlocks(), info.getSkipBlocks(), info.getForceInlineInsns());
}
private IfInfo(IfCondition condition, BlockNode thenBlock, BlockNode elseBlock,
Set<BlockNode> mergedBlocks, Set<BlockNode> skipBlocks) {
Set<BlockNode> mergedBlocks, Set<BlockNode> skipBlocks, List<InsnNode> forceInlineInsns) {
this.condition = condition;
this.thenBlock = thenBlock;
this.elseBlock = elseBlock;
this.mergedBlocks = mergedBlocks;
this.skipBlocks = skipBlocks;
this.forceInlineInsns = forceInlineInsns;
}
public static IfInfo invert(IfInfo info) {
IfCondition invertedCondition = IfCondition.invert(info.getCondition());
IfInfo tmpIf = new IfInfo(invertedCondition,
info.getElseBlock(), info.getThenBlock(),
info.getMergedBlocks(), info.getSkipBlocks());
info.getMergedBlocks(), info.getSkipBlocks(), info.getForceInlineInsns());
tmpIf.setIfBlock(info.getIfBlock());
return tmpIf;
}
@@ -45,6 +51,7 @@ public final class IfInfo {
for (IfInfo info : arr) {
mergedBlocks.addAll(info.getMergedBlocks());
skipBlocks.addAll(info.getSkipBlocks());
addInsnsForForcedInline(info.getForceInlineInsns());
}
}
@@ -84,6 +91,18 @@ public final class IfInfo {
this.ifBlock = ifBlock;
}
public List<InsnNode> getForceInlineInsns() {
return forceInlineInsns;
}
public void resetForceInlineInsns() {
forceInlineInsns.clear();
}
public void addInsnsForForcedInline(List<InsnNode> insns) {
forceInlineInsns.addAll(insns);
}
@Override
public String toString() {
return "IfInfo: then: " + thenBlock + ", else: " + elseBlock;
@@ -102,12 +102,12 @@ public final class LoopRegion extends AbstractRegion {
boolean found = false;
// search result arg in other insns
for (int j = i + 1; j < size; j++) {
if (insns.get(i).containsArg(res)) {
if (insns.get(i).containsVar(res)) {
found = true;
}
}
// or in if insn
if (!found && ifInsn.containsArg(res)) {
if (!found && ifInsn.containsVar(res)) {
found = true;
}
if (!found) {
@@ -32,6 +32,8 @@ public class ExceptionHandler {
private TryCatchBlock tryBlock;
private boolean isFinally;
private boolean removed = false;
public ExceptionHandler(int addr, @Nullable ClassInfo type) {
this.handleOffset = addr;
addCatchType(type);
@@ -138,6 +140,14 @@ public class ExceptionHandler {
this.isFinally = isFinally;
}
public boolean isRemoved() {
return removed;
}
public void markForRemove() {
this.removed = true;
}
@Override
public boolean equals(Object o) {
if (this == o) {
@@ -52,6 +52,17 @@ public class TryCatchBlock {
return addedHandler;
}
/**
* Use only before BlockSplitter
*/
public void removeSameHandlers(TryCatchBlock outerTry) {
for (ExceptionHandler handler : outerTry.getHandlers()) {
if (handlers.remove(handler)) {
handler.setTryBlock(outerTry);
}
}
}
public void removeHandler(MethodNode mth, ExceptionHandler handler) {
for (Iterator<ExceptionHandler> it = handlers.iterator(); it.hasNext();) {
ExceptionHandler h = it.next();
@@ -78,9 +89,14 @@ public class TryCatchBlock {
}
SplitterBlockAttr splitter = handler.getHandlerBlock().get(AType.SPLITTER_BLOCK);
if (splitter != null) {
splitter.getBlock().remove(AType.SPLITTER_BLOCK);
BlockNode splitterBlock = splitter.getBlock();
splitterBlock.remove(AType.SPLITTER_BLOCK);
for (BlockNode successor : splitterBlock.getSuccessors()) {
successor.remove(AType.SPLITTER_BLOCK);
}
}
}
handler.markForRemove();
}
private void removeWholeBlock(MethodNode mth) {
@@ -1,5 +1,6 @@
package jadx.core.dex.visitors;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@@ -9,6 +10,7 @@ import jadx.core.Consts;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.FieldInfo;
@@ -102,7 +104,7 @@ public class ClassModifier extends AbstractVisitor {
if (mth.isNoCode() || !mth.getAccessFlags().isConstructor()) {
return false;
}
List<RegisterArg> args = mth.getArguments(false);
List<RegisterArg> args = mth.getArgRegs();
if (args.isEmpty() || mth.contains(AFlag.SKIP_FIRST_ARG)) {
return false;
}
@@ -130,8 +132,8 @@ public class ClassModifier extends AbstractVisitor {
if (arg.getSVar().getUseCount() != 0) {
InsnNode iget = new IndexInsnNode(InsnType.IGET, fieldInfo, 1);
iget.addArg(insn.getArg(1));
for (InsnArg insnArg : arg.getSVar().getUseList()) {
insnArg.wrapInstruction(iget);
for (InsnArg insnArg : new ArrayList<>(arg.getSVar().getUseList())) {
insnArg.wrapInstruction(mth, iget);
}
}
return true;
@@ -156,7 +158,7 @@ public class ClassModifier extends AbstractVisitor {
}
// remove synthetic constructor for inner classes
if (af.isConstructor() && mth.getBasicBlocks().size() == 2) {
List<RegisterArg> args = mth.getArguments(false);
List<RegisterArg> args = mth.getArgRegs();
if (isRemovedClassInArgs(cls, args)) {
modifySyntheticMethod(cls, mth, args);
}
@@ -197,13 +199,15 @@ public class ClassModifier extends AbstractVisitor {
// remove first arg for non-static class (references to outer class)
RegisterArg firstArg = args.get(0);
if (firstArg.getType().equals(cls.getParentClass().getClassInfo().getType())) {
firstArg.add(AFlag.SKIP_ARG);
SkipMethodArgsAttr.skipArg(mth, 0);
}
// remove unused args
for (RegisterArg arg : args) {
int argsCount = args.size();
for (int i = 0; i < argsCount; i++) {
RegisterArg arg = args.get(i);
SSAVar sVar = arg.getSVar();
if (sVar != null && sVar.getUseCount() == 0) {
arg.add(AFlag.SKIP_ARG);
SkipMethodArgsAttr.skipArg(mth, i);
}
}
mth.add(AFlag.DONT_GENERATE);
@@ -305,7 +309,7 @@ public class ClassModifier extends AbstractVisitor {
// remove public empty constructors (static or default)
if (af.isConstructor()
&& (af.isPublic() || af.isStatic())
&& mth.getArguments(false).isEmpty()) {
&& mth.getArgRegs().isEmpty()) {
List<BlockNode> bb = mth.getBasicBlocks();
if (bb == null || bb.isEmpty() || BlockUtils.isAllBlocksEmpty(bb)) {
if (af.isStatic() && mth.getMethodInfo().isClassInit()) {
@@ -4,6 +4,9 @@ import java.util.ArrayList;
import java.util.List;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.CallMthInterface;
import jadx.core.dex.instructions.ConstStringNode;
import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.InvokeNode;
@@ -22,7 +25,6 @@ import jadx.core.dex.visitors.ssa.SSATransform;
import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor;
import jadx.core.utils.InsnRemover;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.exceptions.JadxOverflowException;
@JadxVisitor(
name = "Constants Inline",
@@ -40,6 +42,10 @@ public class ConstInlineVisitor extends AbstractVisitor {
if (mth.isNoCode()) {
return;
}
process(mth);
}
public static void process(MethodNode mth) {
List<InsnNode> toRemove = new ArrayList<>();
for (BlockNode block : mth.getBasicBlocks()) {
toRemove.clear();
@@ -51,36 +57,55 @@ public class ConstInlineVisitor extends AbstractVisitor {
}
private static void checkInsn(MethodNode mth, InsnNode insn, List<InsnNode> toRemove) {
if (insn.contains(AFlag.DONT_INLINE) || insn.contains(AFlag.DONT_GENERATE)) {
if (insn.contains(AFlag.DONT_INLINE)
|| insn.contains(AFlag.DONT_GENERATE)
|| insn.getResult() == null) {
return;
}
InsnType insnType = insn.getType();
if (insnType != InsnType.CONST && insnType != InsnType.MOVE) {
return;
}
InsnArg arg = insn.getArg(0);
if (!arg.isLiteral()) {
return;
}
long lit = ((LiteralArg) arg).getLiteral();
SSAVar sVar = insn.getResult().getSVar();
if (lit == 0 && checkObjectInline(sVar)) {
if (sVar.getUseCount() == 1) {
InsnNode assignInsn = insn.getResult().getAssignInsn();
if (assignInsn != null) {
assignInsn.add(AFlag.DONT_INLINE);
}
InsnArg constArg;
InsnType insnType = insn.getType();
if (insnType == InsnType.CONST || insnType == InsnType.MOVE) {
constArg = insn.getArg(0);
if (!constArg.isLiteral()) {
return;
}
long lit = ((LiteralArg) constArg).getLiteral();
if (lit == 0 && checkObjectInline(sVar)) {
if (sVar.getUseCount() == 1) {
InsnNode assignInsn = insn.getResult().getAssignInsn();
if (assignInsn != null) {
assignInsn.add(AFlag.DONT_INLINE);
}
}
return;
}
} else if (insnType == InsnType.CONST_STR) {
if (sVar.isUsedInPhi()) {
return;
}
String s = ((ConstStringNode) insn).getString();
FieldNode f = mth.getParentClass().getConstField(s);
if (f == null) {
InsnNode copy = insn.copy();
copy.setResult(null);
constArg = InsnArg.wrapArg(copy);
} else {
InsnNode constGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0);
constArg = InsnArg.wrapArg(constGet);
constArg.setType(ArgType.STRING);
}
} else {
return;
}
if (checkForFinallyBlock(sVar)) {
return;
}
// all check passed, run replace
replaceConst(mth, insn, lit, toRemove);
replaceConst(mth, insn, constArg, toRemove);
}
private static boolean checkForFinallyBlock(SSAVar sVar) {
@@ -131,12 +156,12 @@ public class ConstInlineVisitor extends AbstractVisitor {
return false;
}
private static int replaceConst(MethodNode mth, InsnNode constInsn, long literal, List<InsnNode> toRemove) {
private static int replaceConst(MethodNode mth, InsnNode constInsn, InsnArg constArg, List<InsnNode> toRemove) {
SSAVar ssaVar = constInsn.getResult().getSVar();
List<RegisterArg> useList = new ArrayList<>(ssaVar.getUseList());
int replaceCount = 0;
for (RegisterArg arg : useList) {
if (replaceArg(mth, arg, literal, constInsn, toRemove)) {
if (replaceArg(mth, arg, constArg, constInsn, toRemove)) {
replaceCount++;
}
}
@@ -146,7 +171,7 @@ public class ConstInlineVisitor extends AbstractVisitor {
return replaceCount;
}
private static boolean replaceArg(MethodNode mth, RegisterArg arg, long literal, InsnNode constInsn, List<InsnNode> toRemove) {
private static boolean replaceArg(MethodNode mth, RegisterArg arg, InsnArg constArg, InsnNode constInsn, List<InsnNode> toRemove) {
InsnNode useInsn = arg.getParentInsn();
if (useInsn == null) {
return false;
@@ -155,37 +180,59 @@ public class ConstInlineVisitor extends AbstractVisitor {
if (insnType == InsnType.PHI) {
return false;
}
ArgType argType = arg.getInitType();
if (argType.isObject() && literal != 0) {
argType = ArgType.NARROW_NUMBERS;
}
LiteralArg litArg = InsnArg.lit(literal, argType);
if (!useInsn.replaceArg(arg, litArg)) {
return false;
}
// arg replaced, made some optimizations
litArg.setType(arg.getInitType());
FieldNode fieldNode = null;
ArgType litArgType = litArg.getType();
if (litArgType.isTypeKnown()) {
fieldNode = mth.getParentClass().getConstFieldByLiteralArg(litArg);
} else if (litArgType.contains(PrimitiveType.INT)) {
fieldNode = mth.getParentClass().getConstField((int) literal, false);
if (constArg.isLiteral()) {
long literal = ((LiteralArg) constArg).getLiteral();
ArgType argType = arg.getType();
if (argType == ArgType.UNKNOWN) {
argType = arg.getInitType();
}
if (argType.isObject() && literal != 0) {
argType = ArgType.NARROW_NUMBERS;
}
LiteralArg litArg = InsnArg.lit(literal, argType);
litArg.copyAttributesFrom(constArg);
if (!useInsn.replaceArg(arg, litArg)) {
return false;
}
// arg replaced, made some optimizations
FieldNode fieldNode = null;
ArgType litArgType = litArg.getType();
if (litArgType.isTypeKnown()) {
fieldNode = mth.getParentClass().getConstFieldByLiteralArg(litArg);
} else if (litArgType.contains(PrimitiveType.INT)) {
fieldNode = mth.getParentClass().getConstField((int) literal, false);
}
if (fieldNode != null) {
litArg.wrapInstruction(mth, new IndexInsnNode(InsnType.SGET, fieldNode.getFieldInfo(), 0));
} else {
if (needExplicitCast(useInsn, litArg)) {
litArg.add(AFlag.EXPLICIT_PRIMITIVE_TYPE);
}
}
} else {
if (!useInsn.replaceArg(arg, constArg.duplicate())) {
return false;
}
}
if (fieldNode != null) {
litArg.wrapInstruction(new IndexInsnNode(InsnType.SGET, fieldNode.getFieldInfo(), 0));
}
if (insnType == InsnType.RETURN) {
useInsn.setSourceLine(constInsn.getSourceLine());
} else if (insnType == InsnType.MOVE) {
try {
replaceConst(mth, useInsn, literal, toRemove);
} catch (StackOverflowError e) {
throw new JadxOverflowException("Stack overflow at const inline visitor");
}
}
return true;
}
private static boolean needExplicitCast(InsnNode insn, LiteralArg arg) {
if (insn instanceof CallMthInterface) {
CallMthInterface callInsn = (CallMthInterface) insn;
MethodInfo callMth = callInsn.getCallMth();
int offset = callInsn.getFirstArgOffset();
int argIndex = insn.getArgIndex(arg);
ArgType argType = callMth.getArgumentsTypes().get(argIndex - offset);
if (argType.isPrimitive()) {
arg.setType(argType);
return argType.equals(ArgType.BYTE);
}
}
return false;
}
}
@@ -60,6 +60,7 @@ public class ConstructorVisitor extends AbstractVisitor {
}
InsnNode instArgAssignInsn = ((RegisterArg) inv.getArg(0)).getAssignInsn();
ConstructorInsn co = new ConstructorInsn(mth, inv);
co.rebindArgs();
boolean remove = false;
if (co.isSuper() && (co.getArgsCount() == 0 || parentClass.isEnum())) {
remove = true;
@@ -97,9 +98,10 @@ public class ConstructorVisitor extends AbstractVisitor {
}
ConstructorInsn replace = processConstructor(mth, co);
if (replace != null) {
remover.addAndUnbind(co);
co = replace;
}
BlockUtils.replaceInsn(block, indexInBlock, co);
BlockUtils.replaceInsn(mth, block, indexInBlock, co);
}
/**
@@ -116,14 +118,17 @@ public class ConstructorVisitor extends AbstractVisitor {
if (classNode == null) {
return null;
}
RegisterArg instanceArg = co.getInstanceArg();
RegisterArg instanceArg = co.getResult();
if (instanceArg == null) {
return null;
}
boolean passThis = instanceArg.isThis();
String ctrId = "<init>(" + (passThis ? TypeGen.signature(instanceArg.getInitType()) : "") + ")V";
MethodNode defCtr = classNode.searchMethodByShortId(ctrId);
if (defCtr == null) {
return null;
}
ConstructorInsn newInsn = new ConstructorInsn(defCtr.getMethodInfo(), co.getCallType(), instanceArg);
ConstructorInsn newInsn = new ConstructorInsn(defCtr.getMethodInfo(), co.getCallType());
newInsn.setResult(co.getResult());
return newInsn;
}

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