Compare commits
95 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cc29da8e81 | |||
| d1a6841c20 | |||
| 600842a1a6 | |||
| 8ba3e935a5 | |||
| 87504dd2cc | |||
| e4e6f37949 | |||
| 4b314e9d99 | |||
| a48ce296b8 | |||
| cf3e17c4b8 | |||
| bae36f9720 | |||
| 11db454b84 | |||
| 1b60c1d1a8 | |||
| 8321d5e380 | |||
| 64c9ce2ab0 | |||
| 08f9a90c95 | |||
| 9f06d6744e | |||
| f228a72118 | |||
| 3249a5e0bc | |||
| d1ac43de33 | |||
| 00f5e83506 | |||
| d3ecc1f640 | |||
| 902247fcdb | |||
| bd9e1096cc | |||
| db892adf34 | |||
| 1cbaad3ec9 | |||
| 401d08ea49 | |||
| ba17f7bc00 | |||
| db2b537380 | |||
| 06f26ef8f5 | |||
| a71bb7a532 | |||
| 99934b5100 | |||
| ff5f6fca3c | |||
| 3578f7d68f | |||
| 7bc01dcfa8 | |||
| bc7a748420 | |||
| c0194d025d | |||
| 19ca8a096b | |||
| cf5bfc297b | |||
| a17f9136dd | |||
| 7d07fb0b77 | |||
| 99935bada6 | |||
| be9dae57b9 | |||
| 4629043721 | |||
| 068234f0ca | |||
| ccb8ed1394 | |||
| 8d68d409eb | |||
| e842e022ba | |||
| 1e6b30343c | |||
| ddedb8d8a0 | |||
| 472aa52706 | |||
| ab97084058 | |||
| 0911b2dc2f | |||
| fd7d08cb10 | |||
| 3ae8359408 | |||
| 6b76a3c787 | |||
| 9fbf9ef667 | |||
| c8de7b97dd | |||
| b32dc17dd7 | |||
| 7c53b985cd | |||
| c8df26f227 | |||
| 3bc9671905 | |||
| 7fd959e6e3 | |||
| 24dc68652e | |||
| aad2d24c58 | |||
| 15d56abeb6 | |||
| d89ec67888 | |||
| f9f840fb9d | |||
| 8e8a2faa10 | |||
| 0c2784bb42 | |||
| c555cd0825 | |||
| 92e28326a4 | |||
| 2dbdd1f079 | |||
| fc58022d56 | |||
| ed9fe8a573 | |||
| 49e234d9f8 | |||
| a587ce88ea | |||
| a530371b6f | |||
| 0c5a83c021 | |||
| 12bb632371 | |||
| e4fc6774b1 | |||
| f57dfb3f2e | |||
| c3f7a049d8 | |||
| 3eee83c2f2 | |||
| ed8c662631 | |||
| 850df18d7c | |||
| 7f4da306c9 | |||
| 424a8ffaf4 | |||
| 8410e62531 | |||
| 533b686e0b | |||
| c6c54f90dc | |||
| 0f5fd4e48a | |||
| a7247e8a88 | |||
| c10a30346b | |||
| 436e86fdf2 | |||
| 29a137bde3 |
@@ -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
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: Suggest an idea for jadx
|
||||
title: "[feature]"
|
||||
labels: new feature
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
*Describe your idea:*
|
||||
@@ -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
@@ -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:
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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`
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
[](https://travis-ci.org/skylot/jadx)
|
||||
[](https://codecov.io/gh/skylot/jadx)
|
||||
[](https://lgtm.com/projects/g/skylot/jadx/alerts/)
|
||||
[](https://sonarcloud.io/dashboard?id=jadx)
|
||||
[](http://www.apache.org/licenses/LICENSE-2.0.html)
|
||||
[](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)
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
### Downloads
|
||||
### Download
|
||||
- latest [unstable build:  ](https://bintray.com/skylot/jadx/unstable/_latestVersion#files)
|
||||
- release from [github: ](https://github.com/skylot/jadx/releases/latest)
|
||||
- release from [bintray:  ](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
@@ -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
|
||||
|
||||
@@ -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"/> -->
|
||||
|
||||
|
||||
Vendored
BIN
Binary file not shown.
+1
-1
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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,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
@@ -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.
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() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user