Compare commits
162 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1ec127c3cb | |||
| 7a3b7c55c9 | |||
| b66293a2f7 | |||
| abcaafa89a | |||
| cf25cc4faa | |||
| b57001d4a7 | |||
| 83decc2473 | |||
| 92faa569be | |||
| c5b731169d | |||
| f0a8ef81d3 | |||
| 994973ac01 | |||
| c9622c0771 | |||
| 8551c6c903 | |||
| 9a9ac4308e | |||
| e784cbdd09 | |||
| 2744c4bfb6 | |||
| e4f4c1b84a | |||
| e5fa818b5c | |||
| b22b554a69 | |||
| e9b8060889 | |||
| 1c2b2c072c | |||
| 3d451912ee | |||
| fe91d774fa | |||
| d8306cb1c0 | |||
| 909cf0a576 | |||
| 8fe1ee11e4 | |||
| d2bef108f5 | |||
| ba8ba504b1 | |||
| 481b5abf85 | |||
| c4e1d9445a | |||
| cb03532b76 | |||
| c93e9eea14 | |||
| 9a67b19973 | |||
| 95c75bed1e | |||
| b008568a5c | |||
| 94fb91cec6 | |||
| c54dd77f35 | |||
| 17fbc99f29 | |||
| 21dd17290b | |||
| dc73fc92be | |||
| 592215db66 | |||
| fb318e3bd9 | |||
| 5f3c8816a3 | |||
| 6016b902c7 | |||
| 5852da1e3d | |||
| 502fd069be | |||
| fad9e7b827 | |||
| 35116d0b1a | |||
| 3b781e41ad | |||
| a3e9744364 | |||
| 7030daeccd | |||
| e7151ad7b2 | |||
| ed2a3c8458 | |||
| 779f75cd52 | |||
| 54683e3198 | |||
| 09335395f5 | |||
| 57e3dd8f15 | |||
| a9bbadd602 | |||
| 2c570681f7 | |||
| 25166970cc | |||
| d3a0a56b8b | |||
| 3c2c198a0e | |||
| 4d4d67f0b4 | |||
| 97e8a34906 | |||
| 82f3b57e83 | |||
| af2f14f807 | |||
| fe248d7098 | |||
| 1a2e702b25 | |||
| 1da20b8e7d | |||
| 01f74ff706 | |||
| 89e95eb9ee | |||
| a61ebaaa00 | |||
| 7a5a2fcd84 | |||
| 8d5554f1b5 | |||
| 873aabb471 | |||
| 4bed9dc358 | |||
| e229874195 | |||
| 473b6e31e9 | |||
| b5ce460618 | |||
| 3c05b05196 | |||
| bdb2efdb6b | |||
| a27ba3ff4b | |||
| 4684207b54 | |||
| dd1be3039b | |||
| 8b30b770cd | |||
| 47caa91e85 | |||
| d71f3e09df | |||
| 06c7415827 | |||
| bd3e62617e | |||
| 00b48473a0 | |||
| 84facb13d0 | |||
| 96f90e18e8 | |||
| 8ff18e63ee | |||
| 381405ea99 | |||
| ae5c00397a | |||
| bd4509f1a7 | |||
| b8c84886a8 | |||
| 45021389bc | |||
| f674a29a64 | |||
| 0c9e3227d0 | |||
| be7e1479a1 | |||
| 19827fca20 | |||
| 5eb7cc40ed | |||
| d22db30166 | |||
| 6db61e7a59 | |||
| 86582de521 | |||
| a7c63c2eb3 | |||
| 081a0e21ee | |||
| 9ac9c05265 | |||
| b7daf79b26 | |||
| b67a3561a4 | |||
| 52ac6dbbaf | |||
| 72381ad8f3 | |||
| 6a065c46f4 | |||
| 092d0d7e67 | |||
| 5ca7285558 | |||
| 7576f9cd5e | |||
| 46b5725d98 | |||
| 72542fa6f9 | |||
| a250d0461b | |||
| c7795bfc48 | |||
| 5de46b7e40 | |||
| 99c70872c1 | |||
| 3566669303 | |||
| 4557d05256 | |||
| fa421d165e | |||
| ecf20020d7 | |||
| ae85af61c7 | |||
| 659bbbf4fb | |||
| 427e2dddc4 | |||
| d47483f957 | |||
| 4bd8e26ae7 | |||
| 01f47282ed | |||
| afdd37cd97 | |||
| addaffcd1d | |||
| 63f7ce20a4 | |||
| f37c23db7a | |||
| d2bde0be21 | |||
| 9c446ebbd6 | |||
| 0f00fb9a27 | |||
| 2d6f819c86 | |||
| 56683ac409 | |||
| a72523c7df | |||
| 46eeb0bc22 | |||
| 6e8baef9b2 | |||
| 947b621733 | |||
| 4cc00bdaf2 | |||
| 59ef569a63 | |||
| abae225915 | |||
| 05bdf9daae | |||
| 0a8192168a | |||
| 88fd5a517e | |||
| 74c5b616a4 | |||
| 22a61d715b | |||
| a90ec7c64a | |||
| b22812b43a | |||
| 4c0da8c3d5 | |||
| 9aa30f77b7 | |||
| 2dbef83fa6 | |||
| 6ec7f789ef | |||
| 31c0afe29e | |||
| 46b07863c1 |
@@ -0,0 +1,41 @@
|
|||||||
|
name: "CodeQL"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [master]
|
||||||
|
pull_request:
|
||||||
|
# The branches below must be a subset of the branches above
|
||||||
|
branches: [master]
|
||||||
|
schedule:
|
||||||
|
- cron: '0 9 * * 5'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
analyze:
|
||||||
|
name: Analyze
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
actions: read
|
||||||
|
contents: read
|
||||||
|
security-events: write
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
language: ['java']
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Initialize CodeQL
|
||||||
|
uses: github/codeql-action/init@v1
|
||||||
|
with:
|
||||||
|
queries: +security-extended
|
||||||
|
languages: ${{ matrix.language }}
|
||||||
|
|
||||||
|
# Don't build tests in jadx-core also skip tests execution and checkstyle tasks
|
||||||
|
- run: |
|
||||||
|
./gradlew clean build -x checkstyleTest -x checkstyleMain -x test -x ':jadx-core:testClasses'
|
||||||
|
|
||||||
|
- name: Perform CodeQL Analysis
|
||||||
|
uses: github/codeql-action/analyze@v1
|
||||||
@@ -34,3 +34,5 @@ jadx-output/
|
|||||||
*.cfg
|
*.cfg
|
||||||
*.orig
|
*.orig
|
||||||
quark.json
|
quark.json
|
||||||
|
|
||||||
|
cliff.toml
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
jdk:
|
||||||
|
- openjdk11
|
||||||
|
install:
|
||||||
|
- echo "Jitpack is not supported. Use artifacts from Maven Central (https://search.maven.org/search?q=jadx), check usage help at https://github.com/skylot/jadx/wiki/Use-jadx-as-a-library"
|
||||||
|
- ./gradlew intentional-fail
|
||||||
@@ -5,12 +5,15 @@
|
|||||||
[](https://github.com/skylot/jadx/actions?query=workflow%3ABuild)
|
[](https://github.com/skylot/jadx/actions?query=workflow%3ABuild)
|
||||||
[](https://lgtm.com/projects/g/skylot/jadx/alerts/)
|
[](https://lgtm.com/projects/g/skylot/jadx/alerts/)
|
||||||
[](https://github.com/semantic-release/semantic-release)
|
[](https://github.com/semantic-release/semantic-release)
|
||||||
|
[](https://search.maven.org/search?q=g:io.github.skylot%20AND%20jadx)
|
||||||
[](http://www.apache.org/licenses/LICENSE-2.0.html)
|
[](http://www.apache.org/licenses/LICENSE-2.0.html)
|
||||||
|
|
||||||
**jadx** - Dex to Java decompiler
|
**jadx** - Dex to Java decompiler
|
||||||
|
|
||||||
Command line and GUI tools for producing Java source code from Android Dex and Apk files
|
Command line and GUI tools for producing Java source code from Android Dex and Apk files
|
||||||
|
|
||||||
|
:exclamation::exclamation::exclamation: Please note that in most cases **jadx** can't decompile all 100% of the code, so errors will occur. Check [Troubleshooting guide](https://github.com/skylot/jadx/wiki/Troubleshooting-Q&A#decompilation-issues) for workarounds
|
||||||
|
|
||||||
**Main features:**
|
**Main features:**
|
||||||
- decompile Dalvik bytecode to java classes from APK, dex, aar, aab and zip files
|
- decompile Dalvik bytecode to java classes from APK, dex, aar, aab and zip files
|
||||||
- decode `AndroidManifest.xml` and other resources from `resources.arsc`
|
- decode `AndroidManifest.xml` and other resources from `resources.arsc`
|
||||||
@@ -21,7 +24,9 @@ Command line and GUI tools for producing Java source code from Android Dex and A
|
|||||||
- jump to declaration
|
- jump to declaration
|
||||||
- find usage
|
- find usage
|
||||||
- full text search
|
- full text search
|
||||||
- smali debugger (thanks to [@LBJ-the-GOAT](https://github.com/LBJ-the-GOAT)), check [wiki page](https://github.com/skylot/jadx/wiki/Smali-debugger) for setup and usage
|
- smali debugger, check [wiki page](https://github.com/skylot/jadx/wiki/Smali-debugger) for setup and usage
|
||||||
|
|
||||||
|
Jadx-gui key bindings can be found [here](https://github.com/skylot/jadx/wiki/JADX-GUI-Key-bindings)
|
||||||
|
|
||||||
See these features in action here: [jadx-gui features overview](https://github.com/skylot/jadx/wiki/jadx-gui-features-overview)
|
See these features in action here: [jadx-gui features overview](https://github.com/skylot/jadx/wiki/jadx-gui-features-overview)
|
||||||
|
|
||||||
@@ -37,7 +42,7 @@ After download unpack zip file go to `bin` directory and run:
|
|||||||
|
|
||||||
On Windows run `.bat` files with double-click\
|
On Windows run `.bat` files with double-click\
|
||||||
**Note:** ensure you have installed Java 11 or later 64-bit version.
|
**Note:** ensure you have installed Java 11 or later 64-bit version.
|
||||||
For windows you can download it from [oracle.com](https://www.oracle.com/java/technologies/downloads/#jdk17-windows) (select x64 Installer).
|
For Windows, you can download it from [oracle.com](https://www.oracle.com/java/technologies/downloads/#jdk17-windows) (select x64 Installer).
|
||||||
|
|
||||||
### Install
|
### Install
|
||||||
1. Arch linux
|
1. Arch linux
|
||||||
@@ -49,6 +54,9 @@ For windows you can download it from [oracle.com](https://www.oracle.com/java/te
|
|||||||
brew install jadx
|
brew install jadx
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Use jadx as a library
|
||||||
|
You can use jadx in your java projects, check details on [wiki page](https://github.com/skylot/jadx/wiki/Use-jadx-as-a-library)
|
||||||
|
|
||||||
### Build from source
|
### Build from source
|
||||||
JDK 8 or higher must be installed:
|
JDK 8 or higher must be installed:
|
||||||
```
|
```
|
||||||
@@ -71,10 +79,16 @@ options:
|
|||||||
-dr, --output-dir-res - output directory for resources
|
-dr, --output-dir-res - output directory for resources
|
||||||
-r, --no-res - do not decode resources
|
-r, --no-res - do not decode resources
|
||||||
-s, --no-src - do not decompile source code
|
-s, --no-src - do not decompile source code
|
||||||
--single-class - decompile a single class
|
--single-class - decompile a single class, full name, raw or alias
|
||||||
|
--single-class-output - file or dir for write if 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
|
-e, --export-gradle - save as android gradle project
|
||||||
-j, --threads-count - processing threads count, default: 4
|
-j, --threads-count - processing threads count, default: 4
|
||||||
|
-m, --decompilation-mode - code output mode:
|
||||||
|
'auto' - trying best options (default)
|
||||||
|
'restructure' - restore code structure (normal java code)
|
||||||
|
'simple' - simplified instructions (linear, with goto's)
|
||||||
|
'fallback' - raw instructions without modifications
|
||||||
--show-bad-code - show inconsistent code (incorrectly decompiled)
|
--show-bad-code - show inconsistent code (incorrectly decompiled)
|
||||||
--no-imports - disable use of imports, always write entire package name
|
--no-imports - disable use of imports, always write entire package name
|
||||||
--no-debug-info - disable debug info
|
--no-debug-info - disable debug info
|
||||||
@@ -88,9 +102,15 @@ options:
|
|||||||
--deobf-min - min length of name, renamed if shorter, default: 3
|
--deobf-min - min length of name, renamed if shorter, default: 3
|
||||||
--deobf-max - max length of name, renamed if longer, default: 64
|
--deobf-max - max length of name, renamed if longer, default: 64
|
||||||
--deobf-cfg-file - deobfuscation map file, default: same dir and name as input file with '.jobf' extension
|
--deobf-cfg-file - deobfuscation map file, default: same dir and name as input file with '.jobf' extension
|
||||||
--deobf-rewrite-cfg - force to ignore and overwrite deobfuscation map file
|
--deobf-cfg-file-mode - set mode for handle deobfuscation map file:
|
||||||
|
'read' - read if found, don't save (default)
|
||||||
|
'read-or-save' - read if found, save otherwise (don't overwrite)
|
||||||
|
'overwrite' - don't read, always save
|
||||||
|
'ignore' - don't read and don't save
|
||||||
|
--deobf-rewrite-cfg - set '--deobf-cfg-file-mode' to 'overwrite' (deprecated)
|
||||||
--deobf-use-sourcename - use source file name as class name alias
|
--deobf-use-sourcename - use source file name as class name alias
|
||||||
--deobf-parse-kotlin-metadata - parse kotlin metadata to class and package names
|
--deobf-parse-kotlin-metadata - parse kotlin metadata to class and package names
|
||||||
|
--use-kotlin-methods-for-var-names - use kotlin intrinsic methods to rename variables, values: disable, apply, apply-and-hide, default: apply
|
||||||
--rename-flags - fix options (comma-separated list of):
|
--rename-flags - fix options (comma-separated list of):
|
||||||
'case' - fix case sensitivity issues (according to --fs-case-sensitive option),
|
'case' - fix case sensitivity issues (according to --fs-case-sensitive option),
|
||||||
'valid' - rename java identifiers to make them valid,
|
'valid' - rename java identifiers to make them valid,
|
||||||
@@ -100,18 +120,28 @@ options:
|
|||||||
--fs-case-sensitive - treat filesystem as case sensitive, false by default
|
--fs-case-sensitive - treat filesystem as case sensitive, false by default
|
||||||
--cfg - save methods control flow graph to dot file
|
--cfg - save methods control flow graph to dot file
|
||||||
--raw-cfg - save methods control flow graph (use raw instructions)
|
--raw-cfg - save methods control flow graph (use raw instructions)
|
||||||
-f, --fallback - make simple dump (using goto instead of 'if', 'for', etc)
|
-f, --fallback - set '--decompilation-mode' to 'fallback' (deprecated)
|
||||||
--comments-level - set code comments level, values: none, user_only, error, warn, info, debug, default: info
|
--use-dx - use dx/d8 to convert java bytecode
|
||||||
|
--comments-level - set code comments level, values: error, warn, info, debug, user-only, none, default: info
|
||||||
--log-level - set log level, values: quiet, progress, error, warn, info, debug, default: progress
|
--log-level - set log level, values: quiet, progress, error, warn, info, debug, default: progress
|
||||||
-v, --verbose - verbose output (set --log-level to DEBUG)
|
-v, --verbose - verbose output (set --log-level to DEBUG)
|
||||||
-q, --quiet - turn off output (set --log-level to QUIET)
|
-q, --quiet - turn off output (set --log-level to QUIET)
|
||||||
--version - print jadx version
|
--version - print jadx version
|
||||||
-h, --help - print this help
|
-h, --help - print this help
|
||||||
|
|
||||||
|
Plugin options (-P<name>=<value>):
|
||||||
|
1) dex-input (Load .dex and .apk files)
|
||||||
|
-Pdex-input.verify-checksum - Verify dex file checksum before load, values: [yes, no], default: yes
|
||||||
|
2) java-convert (Convert .jar and .class files to dex)
|
||||||
|
-Pjava-convert.mode - Convert mode, values: [dx, d8, both], default: both
|
||||||
|
-Pjava-convert.d8-desugar - Use desugar in d8, values: [yes, no], default: no
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
jadx -d out classes.dex
|
jadx -d out classes.dex
|
||||||
jadx --rename-flags "none" classes.dex
|
jadx --rename-flags "none" classes.dex
|
||||||
jadx --rename-flags "valid, printable" classes.dex
|
jadx --rename-flags "valid, printable" classes.dex
|
||||||
jadx --log-level ERROR app.apk
|
jadx --log-level ERROR app.apk
|
||||||
|
jadx -Pdex-input.verify-checksum=no app.apk
|
||||||
```
|
```
|
||||||
These options also worked on jadx-gui running from command line and override options from preferences dialog
|
These options also worked on jadx-gui running from command line and override options from preferences dialog
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# Security Policy
|
||||||
|
|
||||||
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
|
To report a security issue, please email `skylot@gmail.com` with a description of the issue, the steps you took to create the issue, affected versions, and, if known, mitigations for the issue.
|
||||||
|
We will check and respond within 3 working days. If the issue is confirmed as a vulnerability, we will apply required mitigations at the next release.
|
||||||
|
This project follows a 90 day disclosure timeline.
|
||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<component type="desktop">
|
||||||
|
<id>com.github.skylot.jadx</id>
|
||||||
|
<metadata_license>CC0-1.0</metadata_license>
|
||||||
|
<project_license>Apache-2.0</project_license>
|
||||||
|
<name>JADX</name>
|
||||||
|
<summary>Dex to Java decompiler</summary>
|
||||||
|
<description>
|
||||||
|
<p>Command line and GUI tools for producing Java source code from Android Dex and Apk files</p>
|
||||||
|
<ul>
|
||||||
|
<li>decompile Dalvik bytecode to java classes from APK, dex, aar, aab and zip files</li>
|
||||||
|
<li>decode AndroidManifest.xml and other resources from resources.arsc</li>
|
||||||
|
<li>deobfuscator included</li>
|
||||||
|
<li>view decompiled code with highlighted syntax</li>
|
||||||
|
<li>jump to declaration</li>
|
||||||
|
<li>find usage</li>
|
||||||
|
<li>full text search</li>
|
||||||
|
<li>smali debugger</li>
|
||||||
|
</ul>
|
||||||
|
</description>
|
||||||
|
<screenshots>
|
||||||
|
<screenshot type="default">
|
||||||
|
<image>https://user-images.githubusercontent.com/118523/142730720-839f017e-38db-423e-b53f-39f5f0a0316f.png</image>
|
||||||
|
</screenshot>
|
||||||
|
</screenshots>
|
||||||
|
<content_rating type="oars-1.1" />
|
||||||
|
<launchable type="desktop-id">com.github.skylot.jadx.desktop</launchable>
|
||||||
|
<url type="homepage">https://github.com/skylot/jadx</url>
|
||||||
|
<url type="bugtracker">https://github.com/skylot/jadx/issues</url>
|
||||||
|
<releases>
|
||||||
|
<release version="1.3.4" date="2022-03-20" />
|
||||||
|
</releases>
|
||||||
|
</component>
|
||||||
+30
-33
@@ -1,6 +1,6 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'com.github.ben-manes.versions' version '0.39.0'
|
id 'com.github.ben-manes.versions' version '0.42.0'
|
||||||
id 'com.diffplug.spotless' version '6.0.0'
|
id 'com.diffplug.spotless' version '6.4.2'
|
||||||
}
|
}
|
||||||
|
|
||||||
ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev"
|
ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev"
|
||||||
@@ -10,7 +10,6 @@ println("jadx version: ${jadxVersion}")
|
|||||||
allprojects {
|
allprojects {
|
||||||
apply plugin: 'java'
|
apply plugin: 'java'
|
||||||
apply plugin: 'checkstyle'
|
apply plugin: 'checkstyle'
|
||||||
apply plugin: 'maven-publish'
|
|
||||||
|
|
||||||
version = jadxVersion
|
version = jadxVersion
|
||||||
|
|
||||||
@@ -27,32 +26,24 @@ allprojects {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
publishing {
|
|
||||||
publications {
|
|
||||||
mavenJava(MavenPublication) {
|
|
||||||
from components.java
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'org.slf4j:slf4j-api:1.7.32'
|
implementation 'org.slf4j:slf4j-api:1.7.36'
|
||||||
compileOnly 'org.jetbrains:annotations:23.0.0'
|
compileOnly 'org.jetbrains:annotations:23.0.0'
|
||||||
|
|
||||||
testImplementation 'ch.qos.logback:logback-classic:1.2.7'
|
testImplementation 'ch.qos.logback:logback-classic:1.2.11'
|
||||||
testImplementation 'org.hamcrest:hamcrest-library:2.2'
|
testImplementation 'org.hamcrest:hamcrest-library:2.2'
|
||||||
testImplementation 'org.mockito:mockito-core:4.1.0'
|
testImplementation 'org.mockito:mockito-core:4.4.0'
|
||||||
testImplementation 'org.assertj:assertj-core:3.21.0'
|
testImplementation 'org.assertj:assertj-core:3.22.0'
|
||||||
|
|
||||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
|
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
|
||||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
|
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2'
|
||||||
|
|
||||||
testImplementation 'org.eclipse.jdt.core.compiler:ecj:4.6.1'
|
|
||||||
testCompileOnly 'org.jetbrains:annotations:23.0.0'
|
testCompileOnly 'org.jetbrains:annotations:23.0.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
test {
|
test {
|
||||||
useJUnitPlatform()
|
useJUnitPlatform()
|
||||||
|
maxParallelForks = Runtime.runtime.availableProcessors()
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
@@ -76,8 +67,9 @@ spotless {
|
|||||||
if (JavaVersion.current() < JavaVersion.VERSION_16) {
|
if (JavaVersion.current() < JavaVersion.VERSION_16) {
|
||||||
removeUnusedImports()
|
removeUnusedImports()
|
||||||
} else {
|
} else {
|
||||||
// google-format broken on java 16 (https://github.com/diffplug/spotless/issues/834)
|
// google-format on Java 16+ issue: https://github.com/diffplug/spotless/issues/834
|
||||||
println('Warning! Unused imports remove is disabled for Java 16')
|
println('Warning! Unused imports remove is disabled for Java 16+'
|
||||||
|
+ ' (use workaround from https://github.com/diffplug/spotless/tree/main/plugin-gradle#google-java-format)')
|
||||||
}
|
}
|
||||||
|
|
||||||
lineEndings(com.diffplug.spotless.LineEnding.UNIX)
|
lineEndings(com.diffplug.spotless.LineEnding.UNIX)
|
||||||
@@ -135,19 +127,7 @@ task copyExe(type: Copy) {
|
|||||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||||
}
|
}
|
||||||
|
|
||||||
task dist {
|
task distWinBundle(type: Copy, dependsOn: 'jadx-gui:distWinWithJre') {
|
||||||
group 'jadx'
|
|
||||||
description = 'Build jadx distribution zip'
|
|
||||||
|
|
||||||
dependsOn(pack)
|
|
||||||
|
|
||||||
OperatingSystem os = org.gradle.nativeplatform.platform.internal.DefaultNativePlatform.currentOperatingSystem;
|
|
||||||
if (os.isWindows()) {
|
|
||||||
dependsOn('copyExe')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
task distWin(type: Copy, dependsOn: 'jadx-gui:distWinWithJre') {
|
|
||||||
group 'jadx'
|
group 'jadx'
|
||||||
description = 'Copy bundle to build dir'
|
description = 'Copy bundle to build dir'
|
||||||
destinationDir buildDir
|
destinationDir buildDir
|
||||||
@@ -157,6 +137,23 @@ task distWin(type: Copy, dependsOn: 'jadx-gui:distWinWithJre') {
|
|||||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
task dist {
|
||||||
|
group 'jadx'
|
||||||
|
description = 'Build jadx distribution zip'
|
||||||
|
|
||||||
|
dependsOn(pack)
|
||||||
|
|
||||||
|
OperatingSystem os = org.gradle.nativeplatform.platform.internal.DefaultNativePlatform.currentOperatingSystem;
|
||||||
|
if (os.isWindows()) {
|
||||||
|
if (project.hasProperty("bundleJRE")) {
|
||||||
|
println("Build win bundle with JRE")
|
||||||
|
dependsOn('distWinBundle')
|
||||||
|
} else {
|
||||||
|
dependsOn('copyExe')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
task cleanBuildDir(type: Delete) {
|
task cleanBuildDir(type: Delete) {
|
||||||
group 'jadx'
|
group 'jadx'
|
||||||
delete buildDir
|
delete buildDir
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
plugins {
|
||||||
|
id 'groovy-gradle-plugin'
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
plugins {
|
||||||
|
id 'java-library'
|
||||||
|
id 'maven-publish'
|
||||||
|
id 'signing'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
group = 'io.github.skylot'
|
||||||
|
version = jadxVersion
|
||||||
|
|
||||||
|
java {
|
||||||
|
withJavadocJar()
|
||||||
|
withSourcesJar()
|
||||||
|
}
|
||||||
|
|
||||||
|
publishing {
|
||||||
|
publications {
|
||||||
|
mavenJava(MavenPublication) {
|
||||||
|
artifactId = project.name
|
||||||
|
from components.java
|
||||||
|
versionMapping {
|
||||||
|
usage('java-api') {
|
||||||
|
fromResolutionOf('runtimeClasspath')
|
||||||
|
}
|
||||||
|
usage('java-runtime') {
|
||||||
|
fromResolutionResult()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pom {
|
||||||
|
name = project.name
|
||||||
|
description = 'Dex to Java decompiler'
|
||||||
|
url = 'https://github.com/skylot/jadx'
|
||||||
|
licenses {
|
||||||
|
license {
|
||||||
|
name = 'The Apache License, Version 2.0'
|
||||||
|
url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
developers {
|
||||||
|
developer {
|
||||||
|
id = 'skylot'
|
||||||
|
name = 'Skylot'
|
||||||
|
email = 'skylot@gmail.com'
|
||||||
|
url = 'https://github.com/skylot'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scm {
|
||||||
|
connection = 'scm:git:git://github.com/skylot/jadx.git'
|
||||||
|
developerConnection = 'scm:git:ssh://github.com:skylot/jadx.git'
|
||||||
|
url = 'https://github.com/skylot/jadx'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
repositories {
|
||||||
|
maven {
|
||||||
|
def releasesRepoUrl = uri('https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/')
|
||||||
|
def snapshotsRepoUrl = uri('https://s01.oss.sonatype.org/content/repositories/snapshots/')
|
||||||
|
url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl
|
||||||
|
credentials {
|
||||||
|
username = project.properties['ossrhUser'].toString()
|
||||||
|
password = project.properties['ossrhPassword'].toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
signing {
|
||||||
|
required { gradle.taskGraph.hasTask("publish") }
|
||||||
|
sign publishing.publications.mavenJava
|
||||||
|
}
|
||||||
|
|
||||||
|
javadoc {
|
||||||
|
if (JavaVersion.current().isJava9Compatible()) {
|
||||||
|
options.addBooleanOption('html5', true)
|
||||||
|
}
|
||||||
|
// disable 'missing' warnings
|
||||||
|
options.addStringOption('Xdoclint:all,-missing', '-quiet')
|
||||||
|
}
|
||||||
@@ -124,6 +124,12 @@
|
|||||||
<module name="IllegalImport">
|
<module name="IllegalImport">
|
||||||
<property name="illegalClasses" value="jadx.core.utils.DebugUtils"/>
|
<property name="illegalClasses" value="jadx.core.utils.DebugUtils"/>
|
||||||
</module>
|
</module>
|
||||||
|
<module name="RegexpSinglelineJava">
|
||||||
|
<property name="id" value="printstacktrace"/>
|
||||||
|
<property name="format" value="\.printStackTrace\(\)"/>
|
||||||
|
<property name="ignoreComments" value="true"/>
|
||||||
|
<property name="message" value="Using Throwable.printStackTrace() is forbidden. Use logger to print exception"/>
|
||||||
|
</module>
|
||||||
</module>
|
</module>
|
||||||
|
|
||||||
<module name="NewlineAtEndOfFile"/>
|
<module name="NewlineAtEndOfFile"/>
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
SmaliTokenMaker.java
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
Refer to the following instructions to modify and use to generate SmaliTokenMarker
|
||||||
|
|
||||||
|
```shell
|
||||||
|
jflex SmaliTokenMaker.flex --skel skeleton.default
|
||||||
|
```
|
||||||
@@ -0,0 +1,681 @@
|
|||||||
|
/*
|
||||||
|
* Generated on 11/22/21, 8:58 PM
|
||||||
|
*/
|
||||||
|
package jadx.gui.ui.codeearea;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import javax.swing.text.Segment;
|
||||||
|
|
||||||
|
import org.fife.ui.rsyntaxtextarea.*;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用于Smali代码高亮
|
||||||
|
* MartinKay@qq.com
|
||||||
|
*/
|
||||||
|
%%
|
||||||
|
|
||||||
|
%public
|
||||||
|
%class SmaliTokenMaker
|
||||||
|
%extends AbstractJFlexCTokenMaker
|
||||||
|
%unicode
|
||||||
|
/* Case sensitive */
|
||||||
|
%type org.fife.ui.rsyntaxtextarea.Token
|
||||||
|
|
||||||
|
|
||||||
|
%{
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor. This must be here because JFlex does not generate a
|
||||||
|
* no-parameter constructor.
|
||||||
|
*/
|
||||||
|
public SmaliTokenMaker() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the token specified to the current linked list of tokens.
|
||||||
|
*
|
||||||
|
* @param tokenType The token's type.
|
||||||
|
* @see #addToken(int, int, int)
|
||||||
|
*/
|
||||||
|
private void addHyperlinkToken(int start, int end, int tokenType) {
|
||||||
|
int so = start + offsetShift;
|
||||||
|
addToken(zzBuffer, start,end, tokenType, so, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the token specified to the current linked list of tokens.
|
||||||
|
*
|
||||||
|
* @param tokenType The token's type.
|
||||||
|
*/
|
||||||
|
private void addToken(int tokenType) {
|
||||||
|
addToken(zzStartRead, zzMarkedPos-1, tokenType);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the token specified to the current linked list of tokens.
|
||||||
|
*
|
||||||
|
* @param tokenType The token's type.
|
||||||
|
* @see #addHyperlinkToken(int, int, int)
|
||||||
|
*/
|
||||||
|
private void addToken(int start, int end, int tokenType) {
|
||||||
|
int so = start + offsetShift;
|
||||||
|
addToken(zzBuffer, start,end, tokenType, so, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the token specified to the current linked list of tokens.
|
||||||
|
*
|
||||||
|
* @param array The character array.
|
||||||
|
* @param start The starting offset in the array.
|
||||||
|
* @param end The ending offset in the array.
|
||||||
|
* @param tokenType The token's type.
|
||||||
|
* @param startOffset The offset in the document at which this token
|
||||||
|
* occurs.
|
||||||
|
* @param hyperlink Whether this token is a hyperlink.
|
||||||
|
*/
|
||||||
|
public void addToken(char[] array, int start, int end, int tokenType,
|
||||||
|
int startOffset, boolean hyperlink) {
|
||||||
|
super.addToken(array, start,end, tokenType, startOffset, hyperlink);
|
||||||
|
zzStartRead = zzMarkedPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public String[] getLineCommentStartAndEnd(int languageIndex) {
|
||||||
|
return new String[] { "#", null };
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the first token in the linked list of tokens generated
|
||||||
|
* from <code>text</code>. This method must be implemented by
|
||||||
|
* subclasses so they can correctly implement syntax highlighting.
|
||||||
|
*
|
||||||
|
* @param text The text from which to get tokens.
|
||||||
|
* @param initialTokenType The token type we should start with.
|
||||||
|
* @param startOffset The offset into the document at which
|
||||||
|
* <code>text</code> starts.
|
||||||
|
* @return The first <code>Token</code> in a linked list representing
|
||||||
|
* the syntax highlighted text.
|
||||||
|
*/
|
||||||
|
public Token getTokenList(Segment text, int initialTokenType, int startOffset) {
|
||||||
|
|
||||||
|
resetTokenList();
|
||||||
|
this.offsetShift = -text.offset + startOffset;
|
||||||
|
|
||||||
|
// Start off in the proper state.
|
||||||
|
int state = Token.NULL;
|
||||||
|
switch (initialTokenType) {
|
||||||
|
/* No multi-line comments */
|
||||||
|
/* No documentation comments */
|
||||||
|
default:
|
||||||
|
state = Token.NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
s = text;
|
||||||
|
try {
|
||||||
|
yyreset(zzReader);
|
||||||
|
yybegin(state);
|
||||||
|
return yylex();
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
ioe.printStackTrace();
|
||||||
|
return new TokenImpl();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refills the input buffer.
|
||||||
|
*
|
||||||
|
* @return <code>true</code> if EOF was reached, otherwise
|
||||||
|
* <code>false</code>.
|
||||||
|
*/
|
||||||
|
private boolean zzRefill() {
|
||||||
|
return zzCurrentPos>=s.offset+s.count;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the scanner to read from a new input stream.
|
||||||
|
* Does not close the old reader.
|
||||||
|
*
|
||||||
|
* All internal variables are reset, the old input stream
|
||||||
|
* <b>cannot</b> be reused (internal buffer is discarded and lost).
|
||||||
|
* Lexical state is set to <tt>YY_INITIAL</tt>.
|
||||||
|
*
|
||||||
|
* @param reader the new input stream
|
||||||
|
*/
|
||||||
|
public final void yyreset(Reader reader) {
|
||||||
|
// 's' has been updated.
|
||||||
|
zzBuffer = s.array;
|
||||||
|
/*
|
||||||
|
* We replaced the line below with the two below it because zzRefill
|
||||||
|
* no longer "refills" the buffer (since the way we do it, it's always
|
||||||
|
* "full" the first time through, since it points to the segment's
|
||||||
|
* array). So, we assign zzEndRead here.
|
||||||
|
*/
|
||||||
|
//zzStartRead = zzEndRead = s.offset;
|
||||||
|
zzStartRead = s.offset;
|
||||||
|
zzEndRead = zzStartRead + s.count - 1;
|
||||||
|
zzCurrentPos = zzMarkedPos = zzPushbackPos = s.offset;
|
||||||
|
zzLexicalState = YYINITIAL;
|
||||||
|
zzReader = reader;
|
||||||
|
zzAtBOL = true;
|
||||||
|
zzAtEOF = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
%}
|
||||||
|
|
||||||
|
Letter = [A-Za-z]
|
||||||
|
LetterOrUnderscore = ({Letter}|"_")
|
||||||
|
NonzeroDigit = [1-9]
|
||||||
|
Digit = ("0"|{NonzeroDigit})
|
||||||
|
HexDigit = ({Digit}|[A-Fa-f])
|
||||||
|
OctalDigit = ([0-7])
|
||||||
|
AnyCharacterButApostropheOrBackSlash = ([^\\'])
|
||||||
|
AnyCharacterButDoubleQuoteOrBackSlash = ([^\\\"\n])
|
||||||
|
EscapedSourceCharacter = ("u"{HexDigit}{HexDigit}{HexDigit}{HexDigit})
|
||||||
|
Escape = ("\\"(([btnfr\"'\\])|([0123]{OctalDigit}?{OctalDigit}?)|({OctalDigit}{OctalDigit}?)|{EscapedSourceCharacter}))
|
||||||
|
NonSeparator = ([^\t\f\r\n\ \(\)\{\}\[\]\;\,\.\=\>\<\!\~\?\:\+\-\*\/\&\|\^\%\"\']|"#"|"\\")
|
||||||
|
IdentifierStart = ({LetterOrUnderscore}|"$")
|
||||||
|
IdentifierPart = ({IdentifierStart}|{Digit}|("\\"{EscapedSourceCharacter}))
|
||||||
|
|
||||||
|
LineTerminator = (\n)
|
||||||
|
WhiteSpace = ([ \t\f]+)
|
||||||
|
|
||||||
|
CharLiteral = ([\']({AnyCharacterButApostropheOrBackSlash}|{Escape})[\'])
|
||||||
|
UnclosedCharLiteral = ([\'][^\'\n]*)
|
||||||
|
ErrorCharLiteral = ({UnclosedCharLiteral}[\'])
|
||||||
|
StringLiteral = ([\"]({AnyCharacterButDoubleQuoteOrBackSlash}|{Escape})*[\"])
|
||||||
|
UnclosedStringLiteral = ([\"]([\\].|[^\\\"])*[^\"]?)
|
||||||
|
ErrorStringLiteral = ({UnclosedStringLiteral}[\"])
|
||||||
|
|
||||||
|
/* No multi-line comments */
|
||||||
|
/* No documentation comments */
|
||||||
|
LineCommentBegin = "#"
|
||||||
|
|
||||||
|
IntegerLiteral = ({Digit}+)
|
||||||
|
HexLiteral = (0x{HexDigit}+)
|
||||||
|
FloatLiteral = (({Digit}+)("."{Digit}+)?(e[+-]?{Digit}+)? | ({Digit}+)?("."{Digit}+)(e[+-]?{Digit}+)?)
|
||||||
|
ErrorNumberFormat = (({IntegerLiteral}|{HexLiteral}|{FloatLiteral}){NonSeparator}+)
|
||||||
|
BooleanLiteral = ("true"|"false")
|
||||||
|
|
||||||
|
Separator = ([\(\)\{\}\[\]])
|
||||||
|
Separator2 = ([\;,.])
|
||||||
|
|
||||||
|
Identifier = ({IdentifierStart}{IdentifierPart}*)
|
||||||
|
|
||||||
|
URLGenDelim = ([:\/\?#\[\]@])
|
||||||
|
URLSubDelim = ([\!\$&'\(\)\*\+,;=])
|
||||||
|
URLUnreserved = ({LetterOrUnderscore}|{Digit}|[\-\.\~])
|
||||||
|
URLCharacter = ({URLGenDelim}|{URLSubDelim}|{URLUnreserved}|[%])
|
||||||
|
URLCharacters = ({URLCharacter}*)
|
||||||
|
URLEndCharacter = ([\/\$]|{Letter}|{Digit})
|
||||||
|
URL = (((https?|f(tp|ile))"://"|"www.")({URLCharacters}{URLEndCharacter})?)
|
||||||
|
|
||||||
|
|
||||||
|
/* Custom Regex */
|
||||||
|
/* fully-qualified name Rules */
|
||||||
|
SimpleName = ([a-zA-Z0-9_$]*)
|
||||||
|
QUALIFIED_TYPE_NAME = ("L"({SimpleName}{SLASH})*{SimpleName}*";")
|
||||||
|
/* Types */
|
||||||
|
VOID_TYPE = ("V")
|
||||||
|
BOOLEAN_TYPE = ("Z")
|
||||||
|
BYTE_TYPE = ("B")
|
||||||
|
SHORT_TYPE = ("S")
|
||||||
|
CHAR_TYPE = ("C")
|
||||||
|
INT_TYPE = ("I")
|
||||||
|
LONG_TYPE = ("J")
|
||||||
|
FLOAT_TYPE = ("F")
|
||||||
|
DOUBLE_TYPE = ("D")
|
||||||
|
/* Multi Args Types Highlight */
|
||||||
|
MULTI_ARGS_TYPES = (({BOOLEAN_TYPE}|{BYTE_TYPE}|{SHORT_TYPE}|{CHAR_TYPE}|{INT_TYPE}|{LONG_TYPE}|{FLOAT_TYPE}|{DOUBLE_TYPE})+);
|
||||||
|
|
||||||
|
/* Types fully-qualified name */
|
||||||
|
COMPOUND_METHOD_ARG_LITERAL = (({BOOLEAN_TYPE}|{BYTE_TYPE}|{SHORT_TYPE}|{CHAR_TYPE}|{INT_TYPE}|{LONG_TYPE}|{FLOAT_TYPE}|{DOUBLE_TYPE})+{QUALIFIED_TYPE_NAME})
|
||||||
|
|
||||||
|
|
||||||
|
LBRACK = ("[")
|
||||||
|
RBRACK = ("]")
|
||||||
|
LPAREN = ("(")
|
||||||
|
RPAREN = (")")
|
||||||
|
LBRACE = ("{")
|
||||||
|
RBRACE = ("}")
|
||||||
|
COLON = (":")
|
||||||
|
ASSIGN = ("=")
|
||||||
|
DOT = (".")
|
||||||
|
SUB = ("-")
|
||||||
|
COMMA = (",")
|
||||||
|
SLASH = ("/")
|
||||||
|
LT = ("<")
|
||||||
|
GT = (">")
|
||||||
|
ARROW = ("->")
|
||||||
|
SEMI = (";")
|
||||||
|
ARROW_FUNCTION = ({ARROW}[a-zA-Z_$<>]*)
|
||||||
|
CustomSeparator = ({Separator}|{ARROW_FUNCTION})
|
||||||
|
|
||||||
|
/* Register */
|
||||||
|
VREGISTER = ("v"("0"|[1-9])*)
|
||||||
|
PREGISTER = ("p"("0"|[1-9])*)
|
||||||
|
|
||||||
|
/* Flags */
|
||||||
|
FLAG_PSWITCH = (":pswitch_"{SimpleName})
|
||||||
|
FLAG_PSWITCH_DATA = (":pswitch_data_"{SimpleName})
|
||||||
|
FLAG_GOTO = (":goto_"{SimpleName})
|
||||||
|
FLAG_COND = (":cond_"{SimpleName})
|
||||||
|
FLAG_TRY_START = (":try_start_"{SimpleName})
|
||||||
|
FLAG_TRY_END = (":try_end_"{SimpleName})
|
||||||
|
FLAG_CATCH = (":catch_"{SimpleName})
|
||||||
|
FLAG_CATCHALL = (":catchall_"{SimpleName})
|
||||||
|
FLAG_ARRAY = (":array_"{SimpleName})
|
||||||
|
|
||||||
|
/* No string state */
|
||||||
|
/* No char state */
|
||||||
|
/* No MLC state */
|
||||||
|
/* No documentation comment state */
|
||||||
|
%state EOL_COMMENT
|
||||||
|
|
||||||
|
%%
|
||||||
|
|
||||||
|
<YYINITIAL> {
|
||||||
|
|
||||||
|
/* Keywords Instructions Highlight */
|
||||||
|
"nop" |
|
||||||
|
"move" |
|
||||||
|
"move/from16" |
|
||||||
|
"move/16" |
|
||||||
|
"move-wide" |
|
||||||
|
"move-wide/from16" |
|
||||||
|
"move-wide/16" |
|
||||||
|
"move-object" |
|
||||||
|
"move-object/from16" |
|
||||||
|
"move-object/16" |
|
||||||
|
"move-result" |
|
||||||
|
"move-result-wide" |
|
||||||
|
"move-result-object" |
|
||||||
|
"move-exception" |
|
||||||
|
"return-void" |
|
||||||
|
"return" |
|
||||||
|
"return-wide" |
|
||||||
|
"return-object" |
|
||||||
|
"const/4" |
|
||||||
|
"const/16" |
|
||||||
|
"const" |
|
||||||
|
"const/high16" |
|
||||||
|
"const-wide/16" |
|
||||||
|
"const-wide/32" |
|
||||||
|
"const-wide" |
|
||||||
|
"const-wide/high16" |
|
||||||
|
"const-string" |
|
||||||
|
"const-string/jumbo" |
|
||||||
|
"const-class" |
|
||||||
|
"monitor-enter" |
|
||||||
|
"monitor-exit" |
|
||||||
|
"check-cast" |
|
||||||
|
"instance-of" |
|
||||||
|
"array-length" |
|
||||||
|
"new-instance" |
|
||||||
|
"new-array" |
|
||||||
|
"filled-new-array" |
|
||||||
|
"filled-new-array/range" |
|
||||||
|
"fill-array-data" |
|
||||||
|
"throw" |
|
||||||
|
"goto" |
|
||||||
|
"goto/16" |
|
||||||
|
"goto/32" |
|
||||||
|
"cmpl-float" |
|
||||||
|
"cmpg-float" |
|
||||||
|
"cmpl-double" |
|
||||||
|
"cmpg-double" |
|
||||||
|
"cmp-long" |
|
||||||
|
"if-eq" |
|
||||||
|
"if-ne" |
|
||||||
|
"if-lt" |
|
||||||
|
"if-ge" |
|
||||||
|
"if-gt" |
|
||||||
|
"if-le" |
|
||||||
|
"if-eqz" |
|
||||||
|
"if-nez" |
|
||||||
|
"if-ltz" |
|
||||||
|
"if-gez" |
|
||||||
|
"if-gtz" |
|
||||||
|
"if-lez" |
|
||||||
|
"aget" |
|
||||||
|
"aget-wide" |
|
||||||
|
"aget-object" |
|
||||||
|
"aget-boolean" |
|
||||||
|
"aget-byte" |
|
||||||
|
"aget-char" |
|
||||||
|
"aget-short" |
|
||||||
|
"aput" |
|
||||||
|
"aput-wide" |
|
||||||
|
"aput-object" |
|
||||||
|
"aput-boolean" |
|
||||||
|
"aput-byte" |
|
||||||
|
"aput-char" |
|
||||||
|
"aput-short" |
|
||||||
|
"iget" |
|
||||||
|
"iget-wide" |
|
||||||
|
"iget-object" |
|
||||||
|
"iget-boolean" |
|
||||||
|
"iget-byte" |
|
||||||
|
"iget-char" |
|
||||||
|
"iget-short" |
|
||||||
|
"iput" |
|
||||||
|
"iput-wide" |
|
||||||
|
"iput-object" |
|
||||||
|
"iput-boolean" |
|
||||||
|
"iput-byte" |
|
||||||
|
"iput-char" |
|
||||||
|
"iput-short" |
|
||||||
|
"sget" |
|
||||||
|
"sget-wide" |
|
||||||
|
"sget-object" |
|
||||||
|
"sget-boolean" |
|
||||||
|
"sget-byte" |
|
||||||
|
"sget-char" |
|
||||||
|
"sget-short" |
|
||||||
|
"sput" |
|
||||||
|
"sput-wide" |
|
||||||
|
"sput-object" |
|
||||||
|
"sput-boolean" |
|
||||||
|
"sput-byte" |
|
||||||
|
"sput-char" |
|
||||||
|
"sput-short" |
|
||||||
|
"invoke-virtual" |
|
||||||
|
"invoke-super" |
|
||||||
|
"invoke-direct" |
|
||||||
|
"invoke-static" |
|
||||||
|
"invoke-interface" |
|
||||||
|
"invoke-virtual/range" |
|
||||||
|
"invoke-super/range" |
|
||||||
|
"invoke-direct/range" |
|
||||||
|
"invoke-static/range" |
|
||||||
|
"invoke-interface/range" |
|
||||||
|
"neg-int" |
|
||||||
|
"not-int" |
|
||||||
|
"neg-long" |
|
||||||
|
"not-long" |
|
||||||
|
"neg-float" |
|
||||||
|
"neg-double" |
|
||||||
|
"int-to-long" |
|
||||||
|
"int-to-float" |
|
||||||
|
"int-to-double" |
|
||||||
|
"long-to-int" |
|
||||||
|
"long-to-float" |
|
||||||
|
"long-to-double" |
|
||||||
|
"float-to-int" |
|
||||||
|
"float-to-long" |
|
||||||
|
"float-to-double" |
|
||||||
|
"double-to-int" |
|
||||||
|
"double-to-long" |
|
||||||
|
"double-to-float" |
|
||||||
|
"int-to-byte" |
|
||||||
|
"int-to-char" |
|
||||||
|
"int-to-short" |
|
||||||
|
"add-int" |
|
||||||
|
"sub-int" |
|
||||||
|
"mul-int" |
|
||||||
|
"div-int" |
|
||||||
|
"rem-int" |
|
||||||
|
"and-int" |
|
||||||
|
"or-int" |
|
||||||
|
"xor-int" |
|
||||||
|
"shl-int" |
|
||||||
|
"shr-int" |
|
||||||
|
"ushr-int" |
|
||||||
|
"add-long" |
|
||||||
|
"sub-long" |
|
||||||
|
"mul-long" |
|
||||||
|
"div-long" |
|
||||||
|
"rem-long" |
|
||||||
|
"and-long" |
|
||||||
|
"or-long" |
|
||||||
|
"xor-long" |
|
||||||
|
"shl-long" |
|
||||||
|
"shr-long" |
|
||||||
|
"ushr-long" |
|
||||||
|
"add-float" |
|
||||||
|
"sub-float" |
|
||||||
|
"mul-float" |
|
||||||
|
"div-float" |
|
||||||
|
"rem-float" |
|
||||||
|
"add-double" |
|
||||||
|
"sub-double" |
|
||||||
|
"mul-double" |
|
||||||
|
"div-double" |
|
||||||
|
"rem-double" |
|
||||||
|
"add-int/2addr" |
|
||||||
|
"sub-int/2addr" |
|
||||||
|
"mul-int/2addr" |
|
||||||
|
"div-int/2addr" |
|
||||||
|
"rem-int/2addr" |
|
||||||
|
"and-int/2addr" |
|
||||||
|
"or-int/2addr" |
|
||||||
|
"xor-int/2addr" |
|
||||||
|
"shl-int/2addr" |
|
||||||
|
"shr-int/2addr" |
|
||||||
|
"ushr-int/2addr" |
|
||||||
|
"add-long/2addr" |
|
||||||
|
"sub-long/2addr" |
|
||||||
|
"mul-long/2addr" |
|
||||||
|
"div-long/2addr" |
|
||||||
|
"rem-long/2addr" |
|
||||||
|
"and-long/2addr" |
|
||||||
|
"or-long/2addr" |
|
||||||
|
"xor-long/2addr" |
|
||||||
|
"shl-long/2addr" |
|
||||||
|
"shr-long/2addr" |
|
||||||
|
"ushr-long/2addr" |
|
||||||
|
"add-float/2addr" |
|
||||||
|
"sub-float/2addr" |
|
||||||
|
"mul-float/2addr" |
|
||||||
|
"div-float/2addr" |
|
||||||
|
"rem-float/2addr" |
|
||||||
|
"add-double/2addr" |
|
||||||
|
"sub-double/2addr" |
|
||||||
|
"mul-double/2addr" |
|
||||||
|
"div-double/2addr" |
|
||||||
|
"rem-double/2addr" |
|
||||||
|
"add-int/lit16" |
|
||||||
|
"rsub-int" |
|
||||||
|
"mul-int/lit16" |
|
||||||
|
"div-int/lit16" |
|
||||||
|
"rem-int/lit16" |
|
||||||
|
"and-int/lit16" |
|
||||||
|
"or-int/lit16" |
|
||||||
|
"xor-int/lit16" |
|
||||||
|
"add-int/lit8" |
|
||||||
|
"rsub-int/lit8" |
|
||||||
|
"mul-int/lit8" |
|
||||||
|
"div-int/lit8" |
|
||||||
|
"rem-int/lit8" |
|
||||||
|
"and-int/lit8" |
|
||||||
|
"or-int/lit8" |
|
||||||
|
"xor-int/lit8" |
|
||||||
|
"shl-int/lit8" |
|
||||||
|
"shr-int/lit8" |
|
||||||
|
"ushr-int/lit8" |
|
||||||
|
"invoke-polymorphic" |
|
||||||
|
"invoke-polymorphic/range" |
|
||||||
|
"invoke-custom" |
|
||||||
|
"invoke-custom/range" |
|
||||||
|
"const-method-handle" |
|
||||||
|
"const-method-type" |
|
||||||
|
"packed-switch" |
|
||||||
|
"sparse-switch" { addToken(Token.FUNCTION); }
|
||||||
|
|
||||||
|
/* Keywords Modifiers(IDENTIFIER标识符、修饰符) Highlight */
|
||||||
|
"public" |
|
||||||
|
"private" |
|
||||||
|
"protected" |
|
||||||
|
"final" |
|
||||||
|
"annotation" |
|
||||||
|
"static" |
|
||||||
|
"synthetic" |
|
||||||
|
"constructor" |
|
||||||
|
"abstract" |
|
||||||
|
"enum" |
|
||||||
|
"interface" |
|
||||||
|
"transient" |
|
||||||
|
"bridge" |
|
||||||
|
"declared-synchronized" |
|
||||||
|
"volatile" |
|
||||||
|
"strictfp" |
|
||||||
|
"varargs" |
|
||||||
|
"native" { addToken(Token.RESERVED_WORD); }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Keywords Directives Highlight */
|
||||||
|
".method" |
|
||||||
|
".end method" |
|
||||||
|
".implements" |
|
||||||
|
".class" |
|
||||||
|
".prologue" |
|
||||||
|
".source" |
|
||||||
|
".super" |
|
||||||
|
".field" |
|
||||||
|
".end field" |
|
||||||
|
".registers" |
|
||||||
|
".locals" |
|
||||||
|
".param" |
|
||||||
|
".line" |
|
||||||
|
".catch" |
|
||||||
|
".catchall" |
|
||||||
|
".annotation" |
|
||||||
|
".end annotation" |
|
||||||
|
".local" |
|
||||||
|
".end local" |
|
||||||
|
".restart local" |
|
||||||
|
".packed-switch" |
|
||||||
|
".end packed-switch" |
|
||||||
|
".array-data" |
|
||||||
|
".end array-data" |
|
||||||
|
".sparse-switch" |
|
||||||
|
".end sparse-switch" |
|
||||||
|
".end param" { addToken(Token.RESERVED_WORD_2); }
|
||||||
|
|
||||||
|
|
||||||
|
/* VARIABLE Register Highlight */
|
||||||
|
{VREGISTER} |
|
||||||
|
{PREGISTER} { addToken(Token.VARIABLE); }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Data types Highlight */
|
||||||
|
{QUALIFIED_TYPE_NAME} |
|
||||||
|
{COMPOUND_METHOD_ARG_LITERAL} |
|
||||||
|
{MULTI_ARGS_TYPES} |
|
||||||
|
{QUALIFIED_TYPE_NAME} |
|
||||||
|
{VOID_TYPE} |
|
||||||
|
{BOOLEAN_TYPE} |
|
||||||
|
{BYTE_TYPE} |
|
||||||
|
{SHORT_TYPE} |
|
||||||
|
{CHAR_TYPE} |
|
||||||
|
{INT_TYPE} |
|
||||||
|
{LONG_TYPE} |
|
||||||
|
{FLOAT_TYPE} |
|
||||||
|
{DOUBLE_TYPE} { addToken(Token.DATA_TYPE); }
|
||||||
|
|
||||||
|
/* FLAGS */
|
||||||
|
{FLAG_PSWITCH} |
|
||||||
|
{FLAG_PSWITCH_DATA} |
|
||||||
|
{FLAG_GOTO} |
|
||||||
|
{FLAG_COND} |
|
||||||
|
{FLAG_TRY_START} |
|
||||||
|
{FLAG_TRY_END} |
|
||||||
|
{FLAG_CATCHALL} |
|
||||||
|
{FLAG_ARRAY} |
|
||||||
|
{FLAG_CATCH} { addToken(Token.MARKUP_TAG_NAME); }
|
||||||
|
|
||||||
|
/* Functions */
|
||||||
|
/* No functions */
|
||||||
|
|
||||||
|
{BooleanLiteral} { addToken(Token.LITERAL_BOOLEAN); }
|
||||||
|
|
||||||
|
{LineTerminator} { addNullToken(); return firstToken; }
|
||||||
|
|
||||||
|
{Identifier} { addToken(Token.IDENTIFIER); }
|
||||||
|
|
||||||
|
{WhiteSpace} { addToken(Token.WHITESPACE); }
|
||||||
|
|
||||||
|
/* String/Character literals. */
|
||||||
|
{CharLiteral} { addToken(Token.LITERAL_CHAR); }
|
||||||
|
{UnclosedCharLiteral} { addToken(Token.ERROR_CHAR); addNullToken(); return firstToken; }
|
||||||
|
{ErrorCharLiteral} { addToken(Token.ERROR_CHAR); }
|
||||||
|
{StringLiteral} { addToken(Token.LITERAL_STRING_DOUBLE_QUOTE); }
|
||||||
|
{UnclosedStringLiteral} { addToken(Token.ERROR_STRING_DOUBLE); addNullToken(); return firstToken; }
|
||||||
|
{ErrorStringLiteral} { addToken(Token.ERROR_STRING_DOUBLE); }
|
||||||
|
|
||||||
|
/* Comment literals. */
|
||||||
|
/* No multi-line comments */
|
||||||
|
/* No documentation comments */
|
||||||
|
{LineCommentBegin} { start = zzMarkedPos-1; yybegin(EOL_COMMENT); }
|
||||||
|
|
||||||
|
/* Separators. */
|
||||||
|
{CustomSeparator} { addToken(Token.SEPARATOR); }
|
||||||
|
{Separator2} { addToken(Token.IDENTIFIER); }
|
||||||
|
|
||||||
|
/* Operators. */
|
||||||
|
"!" |
|
||||||
|
";" |
|
||||||
|
"." |
|
||||||
|
"=" |
|
||||||
|
"/" |
|
||||||
|
"'" |
|
||||||
|
"(" |
|
||||||
|
")" |
|
||||||
|
"," |
|
||||||
|
"->" |
|
||||||
|
";->" |
|
||||||
|
"<" |
|
||||||
|
">" |
|
||||||
|
"@" |
|
||||||
|
"[" |
|
||||||
|
"]" |
|
||||||
|
"{" |
|
||||||
|
"}" { addToken(Token.OPERATOR); }
|
||||||
|
|
||||||
|
/* Numbers */
|
||||||
|
{IntegerLiteral} { addToken(Token.LITERAL_NUMBER_DECIMAL_INT); }
|
||||||
|
{HexLiteral} { addToken(Token.LITERAL_NUMBER_HEXADECIMAL); }
|
||||||
|
{FloatLiteral} { addToken(Token.LITERAL_NUMBER_FLOAT); }
|
||||||
|
{ErrorNumberFormat} { addToken(Token.ERROR_NUMBER_FORMAT); }
|
||||||
|
|
||||||
|
/* Ended with a line not in a string or comment. */
|
||||||
|
<<EOF>> { addNullToken(); return firstToken; }
|
||||||
|
|
||||||
|
/* Catch any other (unhandled) characters. */
|
||||||
|
. { addToken(Token.IDENTIFIER); }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* No char state */
|
||||||
|
|
||||||
|
/* No string state */
|
||||||
|
|
||||||
|
/* No multi-line comment state */
|
||||||
|
|
||||||
|
/* No documentation comment state */
|
||||||
|
|
||||||
|
<EOL_COMMENT> {
|
||||||
|
[^hwf\n]+ {}
|
||||||
|
{URL} { int temp=zzStartRead; addToken(start,zzStartRead-1, Token.COMMENT_EOL); addHyperlinkToken(temp,zzMarkedPos-1, Token.COMMENT_EOL); start = zzMarkedPos; }
|
||||||
|
[hwf] {}
|
||||||
|
\n { addToken(start,zzStartRead-1, Token.COMMENT_EOL); addNullToken(); return firstToken; }
|
||||||
|
<<EOF>> { addToken(start,zzStartRead-1, Token.COMMENT_EOL); addNullToken(); return firstToken; }
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,243 @@
|
|||||||
|
|
||||||
|
/** This character denotes the end of file */
|
||||||
|
public static final int YYEOF = -1;
|
||||||
|
|
||||||
|
/** initial size of the lookahead buffer */
|
||||||
|
--- private static final int ZZ_BUFFERSIZE = ...;
|
||||||
|
|
||||||
|
/** lexical states */
|
||||||
|
--- lexical states, charmap
|
||||||
|
|
||||||
|
/* error codes */
|
||||||
|
private static final int ZZ_UNKNOWN_ERROR = 0;
|
||||||
|
private static final int ZZ_NO_MATCH = 1;
|
||||||
|
private static final int ZZ_PUSHBACK_2BIG = 2;
|
||||||
|
|
||||||
|
/* error messages for the codes above */
|
||||||
|
private static final String ZZ_ERROR_MSG[] = {
|
||||||
|
"Unkown internal scanner error",
|
||||||
|
"Error: could not match input",
|
||||||
|
"Error: pushback value was too large"
|
||||||
|
};
|
||||||
|
|
||||||
|
--- isFinal list
|
||||||
|
/** the input device */
|
||||||
|
private java.io.Reader zzReader;
|
||||||
|
|
||||||
|
/** the current state of the DFA */
|
||||||
|
private int zzState;
|
||||||
|
|
||||||
|
/** the current lexical state */
|
||||||
|
private int zzLexicalState = YYINITIAL;
|
||||||
|
|
||||||
|
/** this buffer contains the current text to be matched and is
|
||||||
|
the source of the yytext() string */
|
||||||
|
private char zzBuffer[];
|
||||||
|
|
||||||
|
/** the textposition at the last accepting state */
|
||||||
|
private int zzMarkedPos;
|
||||||
|
|
||||||
|
/** the textposition at the last state to be included in yytext */
|
||||||
|
private int zzPushbackPos;
|
||||||
|
|
||||||
|
/** the current text position in the buffer */
|
||||||
|
private int zzCurrentPos;
|
||||||
|
|
||||||
|
/** startRead marks the beginning of the yytext() string in the buffer */
|
||||||
|
private int zzStartRead;
|
||||||
|
|
||||||
|
/** endRead marks the last character in the buffer, that has been read
|
||||||
|
from input */
|
||||||
|
private int zzEndRead;
|
||||||
|
|
||||||
|
/** number of newlines encountered up to the start of the matched text */
|
||||||
|
private int yyline;
|
||||||
|
|
||||||
|
/** the number of characters up to the start of the matched text */
|
||||||
|
private int yychar;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the number of characters from the last newline up to the start of the
|
||||||
|
* matched text
|
||||||
|
*/
|
||||||
|
private int yycolumn;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* zzAtBOL == true <=> the scanner is currently at the beginning of a line
|
||||||
|
*/
|
||||||
|
private boolean zzAtBOL = true;
|
||||||
|
|
||||||
|
/** zzAtEOF == true <=> the scanner is at the EOF */
|
||||||
|
private boolean zzAtEOF;
|
||||||
|
|
||||||
|
--- user class code
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new scanner
|
||||||
|
* There is also a java.io.InputStream version of this constructor.
|
||||||
|
*
|
||||||
|
* @param in the java.io.Reader to read input from.
|
||||||
|
*/
|
||||||
|
--- constructor declaration
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the input stream.
|
||||||
|
*/
|
||||||
|
public final void yyclose() throws java.io.IOException {
|
||||||
|
zzAtEOF = true; /* indicate end of file */
|
||||||
|
zzEndRead = zzStartRead; /* invalidate buffer */
|
||||||
|
|
||||||
|
if (zzReader != null)
|
||||||
|
zzReader.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enters a new lexical state
|
||||||
|
*
|
||||||
|
* @param newState the new lexical state
|
||||||
|
*/
|
||||||
|
public final void yybegin(int newState) {
|
||||||
|
zzLexicalState = newState;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the text matched by the current regular expression.
|
||||||
|
*/
|
||||||
|
public final String yytext() {
|
||||||
|
return new String( zzBuffer, zzStartRead, zzMarkedPos-zzStartRead );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the character at position <tt>pos</tt> from the
|
||||||
|
* matched text.
|
||||||
|
*
|
||||||
|
* It is equivalent to yytext().charAt(pos), but faster
|
||||||
|
*
|
||||||
|
* @param pos the position of the character to fetch.
|
||||||
|
* A value from 0 to yylength()-1.
|
||||||
|
*
|
||||||
|
* @return the character at position pos
|
||||||
|
*/
|
||||||
|
public final char yycharat(int pos) {
|
||||||
|
return zzBuffer[zzStartRead+pos];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the length of the matched text region.
|
||||||
|
*/
|
||||||
|
public final int yylength() {
|
||||||
|
return zzMarkedPos-zzStartRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reports an error that occured while scanning.
|
||||||
|
*
|
||||||
|
* In a wellformed scanner (no or only correct usage of
|
||||||
|
* yypushback(int) and a match-all fallback rule) this method
|
||||||
|
* will only be called with things that "Can't Possibly Happen".
|
||||||
|
* If this method is called, something is seriously wrong
|
||||||
|
* (e.g. a JFlex bug producing a faulty scanner etc.).
|
||||||
|
*
|
||||||
|
* Usual syntax/scanner level error handling should be done
|
||||||
|
* in error fallback rules.
|
||||||
|
*
|
||||||
|
* @param errorCode the code of the errormessage to display
|
||||||
|
*/
|
||||||
|
--- zzScanError declaration
|
||||||
|
String message;
|
||||||
|
try {
|
||||||
|
message = ZZ_ERROR_MSG[errorCode];
|
||||||
|
}
|
||||||
|
catch (ArrayIndexOutOfBoundsException e) {
|
||||||
|
message = ZZ_ERROR_MSG[ZZ_UNKNOWN_ERROR];
|
||||||
|
}
|
||||||
|
|
||||||
|
--- throws clause
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pushes the specified amount of characters back into the input stream.
|
||||||
|
*
|
||||||
|
* They will be read again by then next call of the scanning method
|
||||||
|
*
|
||||||
|
* @param number the number of characters to be read again.
|
||||||
|
* This number must not be greater than yylength()!
|
||||||
|
*/
|
||||||
|
--- yypushback decl (contains zzScanError exception)
|
||||||
|
if ( number > yylength() )
|
||||||
|
zzScanError(ZZ_PUSHBACK_2BIG);
|
||||||
|
|
||||||
|
zzMarkedPos -= number;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
--- zzDoEOF
|
||||||
|
/**
|
||||||
|
* Resumes scanning until the next regular expression is matched,
|
||||||
|
* the end of input is encountered or an I/O-Error occurs.
|
||||||
|
*
|
||||||
|
* @return the next token
|
||||||
|
* @exception java.io.IOException if any I/O-Error occurs
|
||||||
|
*/
|
||||||
|
--- yylex declaration
|
||||||
|
int zzInput;
|
||||||
|
int zzAction;
|
||||||
|
|
||||||
|
// cached fields:
|
||||||
|
int zzCurrentPosL;
|
||||||
|
int zzMarkedPosL;
|
||||||
|
int zzEndReadL = zzEndRead;
|
||||||
|
char [] zzBufferL = zzBuffer;
|
||||||
|
char [] zzCMapL = ZZ_CMAP;
|
||||||
|
|
||||||
|
--- local declarations
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
zzMarkedPosL = zzMarkedPos;
|
||||||
|
|
||||||
|
--- start admin (line, char, col count)
|
||||||
|
zzAction = -1;
|
||||||
|
|
||||||
|
zzCurrentPosL = zzCurrentPos = zzStartRead = zzMarkedPosL;
|
||||||
|
|
||||||
|
--- start admin (lexstate etc)
|
||||||
|
|
||||||
|
zzForAction: {
|
||||||
|
while (true) {
|
||||||
|
|
||||||
|
--- next input, line, col, char count, next transition, isFinal action
|
||||||
|
zzAction = zzState;
|
||||||
|
zzMarkedPosL = zzCurrentPosL;
|
||||||
|
--- line count update
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// store back cached position
|
||||||
|
zzMarkedPos = zzMarkedPosL;
|
||||||
|
--- char count update
|
||||||
|
|
||||||
|
--- actions
|
||||||
|
default:
|
||||||
|
if (zzInput == YYEOF && zzStartRead == zzCurrentPos) {
|
||||||
|
zzAtEOF = true;
|
||||||
|
--- eofvalue
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
--- no match
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
--- main
|
||||||
|
|
||||||
|
}
|
||||||
+1
-1
@@ -1,2 +1,2 @@
|
|||||||
org.gradle.daemon=false
|
|
||||||
org.gradle.warning.mode=all
|
org.gradle.warning.mode=all
|
||||||
|
org.gradle.parallel=true
|
||||||
|
|||||||
Vendored
BIN
Binary file not shown.
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionSha256Sum=de8f52ad49bdc759164f72439a3bf56ddb1589c4cde802d3cec7d6ad0e0ee410
|
distributionSha256Sum=29e49b10984e585d8118b7d0bc452f944e386458df27371b49b4ac1dec4b7fda
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
@@ -7,16 +7,17 @@ dependencies {
|
|||||||
|
|
||||||
runtimeOnly(project(':jadx-plugins:jadx-dex-input'))
|
runtimeOnly(project(':jadx-plugins:jadx-dex-input'))
|
||||||
runtimeOnly(project(':jadx-plugins:jadx-java-input'))
|
runtimeOnly(project(':jadx-plugins:jadx-java-input'))
|
||||||
|
runtimeOnly(project(':jadx-plugins:jadx-java-convert'))
|
||||||
runtimeOnly(project(':jadx-plugins:jadx-smali-input'))
|
runtimeOnly(project(':jadx-plugins:jadx-smali-input'))
|
||||||
|
|
||||||
implementation 'com.beust:jcommander:1.81'
|
implementation 'com.beust:jcommander:1.82'
|
||||||
implementation 'ch.qos.logback:logback-classic:1.2.7'
|
implementation 'ch.qos.logback:logback-classic:1.2.11'
|
||||||
}
|
}
|
||||||
|
|
||||||
application {
|
application {
|
||||||
applicationName = 'jadx'
|
applicationName = 'jadx'
|
||||||
mainClass.set('jadx.cli.JadxCLI')
|
mainClass.set('jadx.cli.JadxCLI')
|
||||||
applicationDefaultJvmArgs = ['-Xms128M', '-Xmx4g', '-XX:+UseG1GC']
|
applicationDefaultJvmArgs = ['-Xms128M', '-XX:MaxRAMPercentage=70.0', '-XX:+UseG1GC']
|
||||||
}
|
}
|
||||||
|
|
||||||
applicationDistribution.with {
|
applicationDistribution.with {
|
||||||
|
|||||||
@@ -17,6 +17,11 @@ import com.beust.jcommander.ParameterException;
|
|||||||
import com.beust.jcommander.Parameterized;
|
import com.beust.jcommander.Parameterized;
|
||||||
|
|
||||||
import jadx.api.JadxDecompiler;
|
import jadx.api.JadxDecompiler;
|
||||||
|
import jadx.api.plugins.JadxPlugin;
|
||||||
|
import jadx.api.plugins.JadxPluginInfo;
|
||||||
|
import jadx.api.plugins.JadxPluginManager;
|
||||||
|
import jadx.api.plugins.options.JadxPluginOptions;
|
||||||
|
import jadx.api.plugins.options.OptionDescription;
|
||||||
|
|
||||||
public class JCommanderWrapper<T> {
|
public class JCommanderWrapper<T> {
|
||||||
private final JCommander jc;
|
private final JCommander jc;
|
||||||
@@ -70,40 +75,44 @@ public class JCommanderWrapper<T> {
|
|||||||
maxNamesLen = len;
|
maxNamesLen = len;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
maxNamesLen += 3;
|
||||||
|
|
||||||
JadxCLIArgs args = (JadxCLIArgs) jc.getObjects().get(0);
|
JadxCLIArgs args = (JadxCLIArgs) jc.getObjects().get(0);
|
||||||
for (Field f : getFields(args.getClass())) {
|
for (Field f : getFields(args.getClass())) {
|
||||||
String name = f.getName();
|
String name = f.getName();
|
||||||
ParameterDescription p = paramsMap.get(name);
|
ParameterDescription p = paramsMap.get(name);
|
||||||
if (p == null) {
|
if (p == null || p.getParameter().hidden()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
StringBuilder opt = new StringBuilder();
|
StringBuilder opt = new StringBuilder();
|
||||||
opt.append(" ").append(p.getNames());
|
opt.append(" ").append(p.getNames());
|
||||||
String description = p.getDescription();
|
String description = p.getDescription();
|
||||||
addSpaces(opt, maxNamesLen - opt.length() + 3);
|
addSpaces(opt, maxNamesLen - opt.length());
|
||||||
if (description.contains("\n")) {
|
if (description.contains("\n")) {
|
||||||
String[] lines = description.split("\n");
|
String[] lines = description.split("\n");
|
||||||
opt.append("- ").append(lines[0]);
|
opt.append("- ").append(lines[0]);
|
||||||
for (int i = 1; i < lines.length; i++) {
|
for (int i = 1; i < lines.length; i++) {
|
||||||
opt.append('\n');
|
opt.append('\n');
|
||||||
addSpaces(opt, maxNamesLen + 5);
|
addSpaces(opt, maxNamesLen + 2);
|
||||||
opt.append(lines[i]);
|
opt.append(lines[i]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
opt.append("- ").append(description);
|
opt.append("- ").append(description);
|
||||||
}
|
}
|
||||||
String defaultValue = getDefaultValue(args, f, opt);
|
String defaultValue = getDefaultValue(args, f, opt);
|
||||||
if (defaultValue != null) {
|
if (defaultValue != null && !description.contains("(default)")) {
|
||||||
opt.append(", default: ").append(defaultValue);
|
opt.append(", default: ").append(defaultValue);
|
||||||
}
|
}
|
||||||
out.println(opt);
|
out.println(opt);
|
||||||
}
|
}
|
||||||
|
out.println(appendPluginOptions(maxNamesLen));
|
||||||
|
out.println();
|
||||||
out.println("Examples:");
|
out.println("Examples:");
|
||||||
out.println(" jadx -d out classes.dex");
|
out.println(" jadx -d out classes.dex");
|
||||||
out.println(" jadx --rename-flags \"none\" classes.dex");
|
out.println(" jadx --rename-flags \"none\" classes.dex");
|
||||||
out.println(" jadx --rename-flags \"valid, printable\" classes.dex");
|
out.println(" jadx --rename-flags \"valid, printable\" classes.dex");
|
||||||
out.println(" jadx --log-level ERROR app.apk");
|
out.println(" jadx --log-level ERROR app.apk");
|
||||||
|
out.println(" jadx -Pdex-input.verify-checksum=no app.apk");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -145,4 +154,46 @@ public class JCommanderWrapper<T> {
|
|||||||
str.append(' ');
|
str.append(' ');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String appendPluginOptions(int maxNamesLen) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
JadxPluginManager pluginManager = new JadxPluginManager();
|
||||||
|
pluginManager.load();
|
||||||
|
int k = 1;
|
||||||
|
for (JadxPlugin plugin : pluginManager.getAllPlugins()) {
|
||||||
|
if (plugin instanceof JadxPluginOptions) {
|
||||||
|
if (appendPlugin(((JadxPluginOptions) plugin), sb, maxNamesLen, k)) {
|
||||||
|
k++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sb.length() == 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return "\nPlugin options (-P<name>=<value>):" + sb;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean appendPlugin(JadxPluginOptions plugin, StringBuilder out, int maxNamesLen, int k) {
|
||||||
|
List<OptionDescription> descs = plugin.getOptionsDescriptions();
|
||||||
|
if (descs.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
JadxPluginInfo pluginInfo = plugin.getPluginInfo();
|
||||||
|
out.append("\n ").append(k).append(") ");
|
||||||
|
out.append(pluginInfo.getPluginId()).append(" (").append(pluginInfo.getDescription()).append(") ");
|
||||||
|
for (OptionDescription desc : descs) {
|
||||||
|
StringBuilder opt = new StringBuilder();
|
||||||
|
opt.append(" -P").append(desc.name());
|
||||||
|
addSpaces(opt, maxNamesLen - opt.length());
|
||||||
|
opt.append("- ").append(desc.description());
|
||||||
|
if (!desc.values().isEmpty()) {
|
||||||
|
opt.append(", values: ").append(desc.values());
|
||||||
|
}
|
||||||
|
if (desc.defaultValue() != null) {
|
||||||
|
opt.append(", default: ").append(desc.defaultValue());
|
||||||
|
}
|
||||||
|
out.append("\n").append(opt);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import jadx.api.JadxArgs;
|
|||||||
import jadx.api.JadxDecompiler;
|
import jadx.api.JadxDecompiler;
|
||||||
import jadx.api.impl.NoOpCodeCache;
|
import jadx.api.impl.NoOpCodeCache;
|
||||||
import jadx.api.impl.SimpleCodeWriter;
|
import jadx.api.impl.SimpleCodeWriter;
|
||||||
|
import jadx.cli.LogHelper.LogLevelEnum;
|
||||||
import jadx.core.utils.exceptions.JadxArgsValidateException;
|
import jadx.core.utils.exceptions.JadxArgsValidateException;
|
||||||
import jadx.core.utils.files.FileUtils;
|
import jadx.core.utils.files.FileUtils;
|
||||||
|
|
||||||
@@ -21,7 +22,7 @@ public class JadxCLI {
|
|||||||
LOG.error("Incorrect arguments: {}", e.getMessage());
|
LOG.error("Incorrect arguments: {}", e.getMessage());
|
||||||
result = 1;
|
result = 1;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error("jadx error: {}", e.getMessage(), e);
|
LOG.error("Process error:", e);
|
||||||
result = 1;
|
result = 1;
|
||||||
} finally {
|
} finally {
|
||||||
FileUtils.deleteTempRootDir();
|
FileUtils.deleteTempRootDir();
|
||||||
@@ -32,23 +33,25 @@ public class JadxCLI {
|
|||||||
public static int execute(String[] args) {
|
public static int execute(String[] args) {
|
||||||
JadxCLIArgs jadxArgs = new JadxCLIArgs();
|
JadxCLIArgs jadxArgs = new JadxCLIArgs();
|
||||||
if (jadxArgs.processArgs(args)) {
|
if (jadxArgs.processArgs(args)) {
|
||||||
return processAndSave(jadxArgs.toJadxArgs());
|
return processAndSave(jadxArgs);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int processAndSave(JadxArgs jadxArgs) {
|
private static int processAndSave(JadxCLIArgs cliArgs) {
|
||||||
|
LogHelper.initLogLevel(cliArgs);
|
||||||
|
LogHelper.setLogLevelsForLoadingStage();
|
||||||
|
JadxArgs jadxArgs = cliArgs.toJadxArgs();
|
||||||
jadxArgs.setCodeCache(new NoOpCodeCache());
|
jadxArgs.setCodeCache(new NoOpCodeCache());
|
||||||
jadxArgs.setCodeWriterProvider(SimpleCodeWriter::new);
|
jadxArgs.setCodeWriterProvider(SimpleCodeWriter::new);
|
||||||
try (JadxDecompiler jadx = new JadxDecompiler(jadxArgs)) {
|
try (JadxDecompiler jadx = new JadxDecompiler(jadxArgs)) {
|
||||||
jadx.load();
|
jadx.load();
|
||||||
if (LogHelper.getLogLevel() == LogHelper.LogLevelEnum.QUIET) {
|
if (checkForErrors(jadx)) {
|
||||||
jadx.save();
|
return 1;
|
||||||
} else {
|
}
|
||||||
jadx.save(500, (done, total) -> {
|
LogHelper.setLogLevelsForDecompileStage();
|
||||||
int progress = (int) (done * 100.0 / total);
|
if (!SingleClassMode.process(jadx, cliArgs)) {
|
||||||
System.out.printf("INFO - progress: %d of %d (%d%%)\r", done, total, progress);
|
save(jadx);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
int errorsCount = jadx.getErrorsCount();
|
int errorsCount = jadx.getErrorsCount();
|
||||||
if (errorsCount != 0) {
|
if (errorsCount != 0) {
|
||||||
@@ -60,4 +63,30 @@ public class JadxCLI {
|
|||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean checkForErrors(JadxDecompiler jadx) {
|
||||||
|
if (jadx.getRoot().getClasses().isEmpty()) {
|
||||||
|
LOG.error("Load failed! No classes for decompile!");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (jadx.getErrorsCount() > 0) {
|
||||||
|
LOG.error("Load with errors! Check log for details");
|
||||||
|
// continue processing
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void save(JadxDecompiler jadx) {
|
||||||
|
if (LogHelper.getLogLevel() == LogLevelEnum.QUIET) {
|
||||||
|
jadx.save();
|
||||||
|
} else {
|
||||||
|
jadx.save(500, (done, total) -> {
|
||||||
|
int progress = (int) (done * 100.0 / total);
|
||||||
|
System.out.printf("INFO - progress: %d of %d (%d%%)\r", done, total, progress);
|
||||||
|
});
|
||||||
|
// dumb line clear :)
|
||||||
|
System.out.print(" \r");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,19 +2,25 @@ package jadx.cli;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import com.beust.jcommander.DynamicParameter;
|
||||||
import com.beust.jcommander.IStringConverter;
|
import com.beust.jcommander.IStringConverter;
|
||||||
import com.beust.jcommander.Parameter;
|
import com.beust.jcommander.Parameter;
|
||||||
|
|
||||||
import jadx.api.CommentsLevel;
|
import jadx.api.CommentsLevel;
|
||||||
|
import jadx.api.DecompilationMode;
|
||||||
import jadx.api.JadxArgs;
|
import jadx.api.JadxArgs;
|
||||||
import jadx.api.JadxArgs.RenameEnum;
|
import jadx.api.JadxArgs.RenameEnum;
|
||||||
|
import jadx.api.JadxArgs.UseKotlinMethodsForVarNames;
|
||||||
import jadx.api.JadxDecompiler;
|
import jadx.api.JadxDecompiler;
|
||||||
|
import jadx.api.args.DeobfuscationMapFileMode;
|
||||||
import jadx.core.utils.exceptions.JadxException;
|
import jadx.core.utils.exceptions.JadxException;
|
||||||
import jadx.core.utils.files.FileUtils;
|
import jadx.core.utils.files.FileUtils;
|
||||||
|
|
||||||
@@ -38,9 +44,12 @@ public class JadxCLIArgs {
|
|||||||
@Parameter(names = { "-s", "--no-src" }, description = "do not decompile source code")
|
@Parameter(names = { "-s", "--no-src" }, description = "do not decompile source code")
|
||||||
protected boolean skipSources = false;
|
protected boolean skipSources = false;
|
||||||
|
|
||||||
@Parameter(names = { "--single-class" }, description = "decompile a single class")
|
@Parameter(names = { "--single-class" }, description = "decompile a single class, full name, raw or alias")
|
||||||
protected String singleClass = null;
|
protected String singleClass = null;
|
||||||
|
|
||||||
|
@Parameter(names = { "--single-class-output" }, description = "file or dir for write if decompile a single class")
|
||||||
|
protected String singleClassOutput = null;
|
||||||
|
|
||||||
@Parameter(names = { "--output-format" }, description = "can be 'java' or 'json'")
|
@Parameter(names = { "--output-format" }, description = "can be 'java' or 'json'")
|
||||||
protected String outputFormat = "java";
|
protected String outputFormat = "java";
|
||||||
|
|
||||||
@@ -50,6 +59,17 @@ public class JadxCLIArgs {
|
|||||||
@Parameter(names = { "-j", "--threads-count" }, description = "processing threads count")
|
@Parameter(names = { "-j", "--threads-count" }, description = "processing threads count")
|
||||||
protected int threadsCount = JadxArgs.DEFAULT_THREADS_COUNT;
|
protected int threadsCount = JadxArgs.DEFAULT_THREADS_COUNT;
|
||||||
|
|
||||||
|
@Parameter(
|
||||||
|
names = { "-m", "--decompilation-mode" },
|
||||||
|
description = "code output mode:"
|
||||||
|
+ "\n 'auto' - trying best options (default)"
|
||||||
|
+ "\n 'restructure' - restore code structure (normal java code)"
|
||||||
|
+ "\n 'simple' - simplified instructions (linear, with goto's)"
|
||||||
|
+ "\n 'fallback' - raw instructions without modifications",
|
||||||
|
converter = DecompilationModeConverter.class
|
||||||
|
)
|
||||||
|
protected DecompilationMode decompilationMode = DecompilationMode.AUTO;
|
||||||
|
|
||||||
@Parameter(names = { "--show-bad-code" }, description = "show inconsistent code (incorrectly decompiled)")
|
@Parameter(names = { "--show-bad-code" }, description = "show inconsistent code (incorrectly decompiled)")
|
||||||
protected boolean showInconsistentCode = false;
|
protected boolean showInconsistentCode = false;
|
||||||
|
|
||||||
@@ -92,7 +112,18 @@ public class JadxCLIArgs {
|
|||||||
)
|
)
|
||||||
protected String deobfuscationMapFile;
|
protected String deobfuscationMapFile;
|
||||||
|
|
||||||
@Parameter(names = { "--deobf-rewrite-cfg" }, description = "force to ignore and overwrite deobfuscation map file")
|
@Parameter(
|
||||||
|
names = { "--deobf-cfg-file-mode" },
|
||||||
|
description = "set mode for handle deobfuscation map file:"
|
||||||
|
+ "\n 'read' - read if found, don't save (default)"
|
||||||
|
+ "\n 'read-or-save' - read if found, save otherwise (don't overwrite)"
|
||||||
|
+ "\n 'overwrite' - don't read, always save"
|
||||||
|
+ "\n 'ignore' - don't read and don't save",
|
||||||
|
converter = DeobfuscationMapFileModeConverter.class
|
||||||
|
)
|
||||||
|
protected DeobfuscationMapFileMode deobfuscationMapFileMode = DeobfuscationMapFileMode.READ;
|
||||||
|
|
||||||
|
@Parameter(names = { "--deobf-rewrite-cfg" }, description = "set '--deobf-cfg-file-mode' to 'overwrite' (deprecated)")
|
||||||
protected boolean deobfuscationForceSave = false;
|
protected boolean deobfuscationForceSave = false;
|
||||||
|
|
||||||
@Parameter(names = { "--deobf-use-sourcename" }, description = "use source file name as class name alias")
|
@Parameter(names = { "--deobf-use-sourcename" }, description = "use source file name as class name alias")
|
||||||
@@ -101,6 +132,13 @@ public class JadxCLIArgs {
|
|||||||
@Parameter(names = { "--deobf-parse-kotlin-metadata" }, description = "parse kotlin metadata to class and package names")
|
@Parameter(names = { "--deobf-parse-kotlin-metadata" }, description = "parse kotlin metadata to class and package names")
|
||||||
protected boolean deobfuscationParseKotlinMetadata = false;
|
protected boolean deobfuscationParseKotlinMetadata = false;
|
||||||
|
|
||||||
|
@Parameter(
|
||||||
|
names = { "--use-kotlin-methods-for-var-names" },
|
||||||
|
description = "use kotlin intrinsic methods to rename variables, values: disable, apply, apply-and-hide",
|
||||||
|
converter = UseKotlinMethodsForVarNamesConverter.class
|
||||||
|
)
|
||||||
|
protected UseKotlinMethodsForVarNames useKotlinMethodsForVarNames = UseKotlinMethodsForVarNames.APPLY;
|
||||||
|
|
||||||
@Parameter(
|
@Parameter(
|
||||||
names = { "--rename-flags" },
|
names = { "--rename-flags" },
|
||||||
description = "fix options (comma-separated list of):"
|
description = "fix options (comma-separated list of):"
|
||||||
@@ -122,12 +160,15 @@ public class JadxCLIArgs {
|
|||||||
@Parameter(names = { "--raw-cfg" }, description = "save methods control flow graph (use raw instructions)")
|
@Parameter(names = { "--raw-cfg" }, description = "save methods control flow graph (use raw instructions)")
|
||||||
protected boolean rawCfgOutput = false;
|
protected boolean rawCfgOutput = false;
|
||||||
|
|
||||||
@Parameter(names = { "-f", "--fallback" }, description = "make simple dump (using goto instead of 'if', 'for', etc)")
|
@Parameter(names = { "-f", "--fallback" }, description = "set '--decompilation-mode' to 'fallback' (deprecated)")
|
||||||
protected boolean fallbackMode = false;
|
protected boolean fallbackMode = false;
|
||||||
|
|
||||||
|
@Parameter(names = { "--use-dx" }, description = "use dx/d8 to convert java bytecode")
|
||||||
|
protected boolean useDx = false;
|
||||||
|
|
||||||
@Parameter(
|
@Parameter(
|
||||||
names = { "--comments-level" },
|
names = { "--comments-level" },
|
||||||
description = "set code comments level, values: error, warn, info, debug, user_only, none",
|
description = "set code comments level, values: error, warn, info, debug, user-only, none",
|
||||||
converter = CommentsLevelConverter.class
|
converter = CommentsLevelConverter.class
|
||||||
)
|
)
|
||||||
protected CommentsLevel commentsLevel = CommentsLevel.INFO;
|
protected CommentsLevel commentsLevel = CommentsLevel.INFO;
|
||||||
@@ -151,6 +192,9 @@ public class JadxCLIArgs {
|
|||||||
@Parameter(names = { "-h", "--help" }, description = "print this help", help = true)
|
@Parameter(names = { "-h", "--help" }, description = "print this help", help = true)
|
||||||
protected boolean printHelp = false;
|
protected boolean printHelp = false;
|
||||||
|
|
||||||
|
@DynamicParameter(names = "-P", description = "Plugin options", hidden = true)
|
||||||
|
protected Map<String, String> pluginOptions = new HashMap<>();
|
||||||
|
|
||||||
public boolean processArgs(String[] args) {
|
public boolean processArgs(String[] args) {
|
||||||
JCommanderWrapper<JadxCLIArgs> jcw = new JCommanderWrapper<>(this);
|
JCommanderWrapper<JadxCLIArgs> jcw = new JCommanderWrapper<>(this);
|
||||||
return jcw.parse(args) && process(jcw);
|
return jcw.parse(args) && process(jcw);
|
||||||
@@ -186,7 +230,6 @@ public class JadxCLIArgs {
|
|||||||
if (threadsCount <= 0) {
|
if (threadsCount <= 0) {
|
||||||
throw new JadxException("Threads count must be positive, got: " + threadsCount);
|
throw new JadxException("Threads count must be positive, got: " + threadsCount);
|
||||||
}
|
}
|
||||||
LogHelper.setLogLevelFromArgs(this);
|
|
||||||
} catch (JadxException e) {
|
} catch (JadxException e) {
|
||||||
System.err.println("ERROR: " + e.getMessage());
|
System.err.println("ERROR: " + e.getMessage());
|
||||||
jcw.printUsage();
|
jcw.printUsage();
|
||||||
@@ -204,22 +247,28 @@ public class JadxCLIArgs {
|
|||||||
args.setOutputFormat(JadxArgs.OutputFormatEnum.valueOf(outputFormat.toUpperCase()));
|
args.setOutputFormat(JadxArgs.OutputFormatEnum.valueOf(outputFormat.toUpperCase()));
|
||||||
args.setThreadsCount(threadsCount);
|
args.setThreadsCount(threadsCount);
|
||||||
args.setSkipSources(skipSources);
|
args.setSkipSources(skipSources);
|
||||||
if (singleClass != null) {
|
|
||||||
args.setClassFilter(className -> singleClass.equals(className));
|
|
||||||
}
|
|
||||||
args.setSkipResources(skipResources);
|
args.setSkipResources(skipResources);
|
||||||
args.setFallbackMode(fallbackMode);
|
if (fallbackMode) {
|
||||||
|
args.setDecompilationMode(DecompilationMode.FALLBACK);
|
||||||
|
} else {
|
||||||
|
args.setDecompilationMode(decompilationMode);
|
||||||
|
}
|
||||||
args.setShowInconsistentCode(showInconsistentCode);
|
args.setShowInconsistentCode(showInconsistentCode);
|
||||||
args.setCfgOutput(cfgOutput);
|
args.setCfgOutput(cfgOutput);
|
||||||
args.setRawCFGOutput(rawCfgOutput);
|
args.setRawCFGOutput(rawCfgOutput);
|
||||||
args.setReplaceConsts(replaceConsts);
|
args.setReplaceConsts(replaceConsts);
|
||||||
args.setDeobfuscationOn(deobfuscationOn);
|
args.setDeobfuscationOn(deobfuscationOn);
|
||||||
args.setDeobfuscationMapFile(FileUtils.toFile(deobfuscationMapFile));
|
args.setDeobfuscationMapFile(FileUtils.toFile(deobfuscationMapFile));
|
||||||
args.setDeobfuscationForceSave(deobfuscationForceSave);
|
if (deobfuscationForceSave) {
|
||||||
|
args.setDeobfuscationMapFileMode(DeobfuscationMapFileMode.OVERWRITE);
|
||||||
|
} else {
|
||||||
|
args.setDeobfuscationMapFileMode(deobfuscationMapFileMode);
|
||||||
|
}
|
||||||
args.setDeobfuscationMinLength(deobfuscationMinLength);
|
args.setDeobfuscationMinLength(deobfuscationMinLength);
|
||||||
args.setDeobfuscationMaxLength(deobfuscationMaxLength);
|
args.setDeobfuscationMaxLength(deobfuscationMaxLength);
|
||||||
args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias);
|
args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias);
|
||||||
args.setParseKotlinMetadata(deobfuscationParseKotlinMetadata);
|
args.setParseKotlinMetadata(deobfuscationParseKotlinMetadata);
|
||||||
|
args.setUseKotlinMethodsForVarNames(useKotlinMethodsForVarNames);
|
||||||
args.setEscapeUnicode(escapeUnicode);
|
args.setEscapeUnicode(escapeUnicode);
|
||||||
args.setRespectBytecodeAccModifiers(respectBytecodeAccessModifiers);
|
args.setRespectBytecodeAccModifiers(respectBytecodeAccessModifiers);
|
||||||
args.setExportAsGradleProject(exportAsGradleProject);
|
args.setExportAsGradleProject(exportAsGradleProject);
|
||||||
@@ -231,6 +280,8 @@ public class JadxCLIArgs {
|
|||||||
args.setRenameFlags(renameFlags);
|
args.setRenameFlags(renameFlags);
|
||||||
args.setFsCaseSensitive(fsCaseSensitive);
|
args.setFsCaseSensitive(fsCaseSensitive);
|
||||||
args.setCommentsLevel(commentsLevel);
|
args.setCommentsLevel(commentsLevel);
|
||||||
|
args.setUseDxInput(useDx);
|
||||||
|
args.setPluginOptions(pluginOptions);
|
||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,6 +301,14 @@ public class JadxCLIArgs {
|
|||||||
return outDirRes;
|
return outDirRes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getSingleClass() {
|
||||||
|
return singleClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSingleClassOutput() {
|
||||||
|
return singleClassOutput;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isSkipResources() {
|
public boolean isSkipResources() {
|
||||||
return skipResources;
|
return skipResources;
|
||||||
}
|
}
|
||||||
@@ -266,6 +325,14 @@ public class JadxCLIArgs {
|
|||||||
return fallbackMode;
|
return fallbackMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isUseDx() {
|
||||||
|
return useDx;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DecompilationMode getDecompilationMode() {
|
||||||
|
return decompilationMode;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isShowInconsistentCode() {
|
public boolean isShowInconsistentCode() {
|
||||||
return showInconsistentCode;
|
return showInconsistentCode;
|
||||||
}
|
}
|
||||||
@@ -306,6 +373,10 @@ public class JadxCLIArgs {
|
|||||||
return deobfuscationMapFile;
|
return deobfuscationMapFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DeobfuscationMapFileMode getDeobfuscationMapFileMode() {
|
||||||
|
return deobfuscationMapFileMode;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isDeobfuscationForceSave() {
|
public boolean isDeobfuscationForceSave() {
|
||||||
return deobfuscationForceSave;
|
return deobfuscationForceSave;
|
||||||
}
|
}
|
||||||
@@ -318,6 +389,10 @@ public class JadxCLIArgs {
|
|||||||
return deobfuscationParseKotlinMetadata;
|
return deobfuscationParseKotlinMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public UseKotlinMethodsForVarNames getUseKotlinMethodsForVarNames() {
|
||||||
|
return useKotlinMethodsForVarNames;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isEscapeUnicode() {
|
public boolean isEscapeUnicode() {
|
||||||
return escapeUnicode;
|
return escapeUnicode;
|
||||||
}
|
}
|
||||||
@@ -362,6 +437,14 @@ public class JadxCLIArgs {
|
|||||||
return commentsLevel;
|
return commentsLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LogHelper.LogLevelEnum getLogLevel() {
|
||||||
|
return logLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getPluginOptions() {
|
||||||
|
return pluginOptions;
|
||||||
|
}
|
||||||
|
|
||||||
static class RenameConverter implements IStringConverter<Set<RenameEnum>> {
|
static class RenameConverter implements IStringConverter<Set<RenameEnum>> {
|
||||||
private final String paramName;
|
private final String paramName;
|
||||||
|
|
||||||
@@ -404,9 +487,48 @@ public class JadxCLIArgs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class UseKotlinMethodsForVarNamesConverter implements IStringConverter<UseKotlinMethodsForVarNames> {
|
||||||
|
@Override
|
||||||
|
public UseKotlinMethodsForVarNames convert(String value) {
|
||||||
|
try {
|
||||||
|
return UseKotlinMethodsForVarNames.valueOf(value.replace('-', '_').toUpperCase());
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
'\'' + value + "' is unknown, possible values are: "
|
||||||
|
+ JadxCLIArgs.enumValuesString(CommentsLevel.values()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class DeobfuscationMapFileModeConverter implements IStringConverter<DeobfuscationMapFileMode> {
|
||||||
|
@Override
|
||||||
|
public DeobfuscationMapFileMode convert(String value) {
|
||||||
|
try {
|
||||||
|
return DeobfuscationMapFileMode.valueOf(value.toUpperCase());
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
'\'' + value + "' is unknown, possible values are: "
|
||||||
|
+ JadxCLIArgs.enumValuesString(DeobfuscationMapFileMode.values()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class DecompilationModeConverter implements IStringConverter<DecompilationMode> {
|
||||||
|
@Override
|
||||||
|
public DecompilationMode convert(String value) {
|
||||||
|
try {
|
||||||
|
return DecompilationMode.valueOf(value.toUpperCase());
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
'\'' + value + "' is unknown, possible values are: "
|
||||||
|
+ JadxCLIArgs.enumValuesString(DecompilationMode.values()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static String enumValuesString(Enum<?>[] values) {
|
public static String enumValuesString(Enum<?>[] values) {
|
||||||
return Stream.of(values)
|
return Stream.of(values)
|
||||||
.map(v -> v.name().toLowerCase(Locale.ROOT))
|
.map(v -> v.name().replace('_', '-').toLowerCase(Locale.ROOT))
|
||||||
.collect(Collectors.joining(", "));
|
.collect(Collectors.joining(", "));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package jadx.cli;
|
package jadx.cli;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -32,33 +33,61 @@ public class LogHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable("For disable log level control")
|
||||||
private static LogLevelEnum logLevelValue;
|
private static LogLevelEnum logLevelValue;
|
||||||
|
|
||||||
public static void setLogLevelFromArgs(JadxCLIArgs args) {
|
public static void initLogLevel(JadxCLIArgs args) {
|
||||||
if (isCustomLogConfig()) {
|
logLevelValue = getLogLevelFromArgs(args);
|
||||||
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) {
|
private static LogLevelEnum getLogLevelFromArgs(JadxCLIArgs args) {
|
||||||
logLevelValue = logLevel;
|
if (isCustomLogConfig()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (args.quiet) {
|
||||||
|
return LogLevelEnum.QUIET;
|
||||||
|
}
|
||||||
|
if (args.verbose) {
|
||||||
|
return LogLevelEnum.DEBUG;
|
||||||
|
}
|
||||||
|
return args.logLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setLogLevelsForLoadingStage() {
|
||||||
|
if (logLevelValue == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (logLevelValue == LogLevelEnum.PROGRESS) {
|
||||||
|
// show load errors
|
||||||
|
LogHelper.applyLogLevel(LogLevelEnum.ERROR);
|
||||||
|
fixForShowProgress();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
applyLogLevel(logLevelValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setLogLevelsForDecompileStage() {
|
||||||
|
if (logLevelValue == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
applyLogLevel(logLevelValue);
|
||||||
|
if (logLevelValue == LogLevelEnum.PROGRESS) {
|
||||||
|
fixForShowProgress();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show progress: change to 'INFO' for control classes
|
||||||
|
*/
|
||||||
|
private static void fixForShowProgress() {
|
||||||
|
setLevelForClass(JadxCLI.class, Level.INFO);
|
||||||
|
setLevelForClass(JadxDecompiler.class, Level.INFO);
|
||||||
|
setLevelForClass(SingleClassMode.class, Level.INFO);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void applyLogLevel(@NotNull LogLevelEnum logLevel) {
|
||||||
Logger rootLogger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
|
Logger rootLogger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
|
||||||
rootLogger.setLevel(logLevel.getLevel());
|
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
package jadx.cli;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import jadx.api.ICodeInfo;
|
||||||
|
import jadx.api.JadxDecompiler;
|
||||||
|
import jadx.core.dex.attributes.AFlag;
|
||||||
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
|
import jadx.core.dex.visitors.SaveCode;
|
||||||
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
import jadx.core.utils.files.FileUtils;
|
||||||
|
|
||||||
|
public class SingleClassMode {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(SingleClassMode.class);
|
||||||
|
|
||||||
|
public static boolean process(JadxDecompiler jadx, JadxCLIArgs cliArgs) {
|
||||||
|
String singleClass = cliArgs.getSingleClass();
|
||||||
|
String singleClassOutput = cliArgs.getSingleClassOutput();
|
||||||
|
if (singleClass == null && singleClassOutput == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ClassNode clsForProcess;
|
||||||
|
if (singleClass != null) {
|
||||||
|
clsForProcess = jadx.getRoot().resolveClass(singleClass);
|
||||||
|
if (clsForProcess == null) {
|
||||||
|
clsForProcess = jadx.getRoot().getClasses().stream()
|
||||||
|
.filter(cls -> cls.getClassInfo().getAliasFullName().equals(singleClass))
|
||||||
|
.findFirst().orElse(null);
|
||||||
|
}
|
||||||
|
if (clsForProcess == null) {
|
||||||
|
throw new JadxRuntimeException("Input class not found: " + singleClass);
|
||||||
|
}
|
||||||
|
if (clsForProcess.contains(AFlag.DONT_GENERATE)) {
|
||||||
|
throw new JadxRuntimeException("Input class can't be saved by currect jadx settings (marked as DONT_GENERATE)");
|
||||||
|
}
|
||||||
|
if (clsForProcess.isInner()) {
|
||||||
|
clsForProcess = clsForProcess.getTopParentClass();
|
||||||
|
LOG.warn("Input class is inner, parent class will be saved: {}", clsForProcess.getFullName());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// singleClassOutput is set
|
||||||
|
// expect only one class to be loaded
|
||||||
|
List<ClassNode> classes = jadx.getRoot().getClasses().stream()
|
||||||
|
.filter(c -> !c.isInner() && !c.contains(AFlag.DONT_GENERATE))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
int size = classes.size();
|
||||||
|
if (size == 1) {
|
||||||
|
clsForProcess = classes.get(0);
|
||||||
|
} else {
|
||||||
|
throw new JadxRuntimeException("Found " + size + " classes, single class output can't be used");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ICodeInfo codeInfo;
|
||||||
|
try {
|
||||||
|
codeInfo = clsForProcess.decompile();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new JadxRuntimeException("Class decompilation failed", e);
|
||||||
|
}
|
||||||
|
String fileExt = SaveCode.getFileExtension(jadx.getRoot());
|
||||||
|
File out;
|
||||||
|
if (singleClassOutput == null) {
|
||||||
|
out = new File(jadx.getArgs().getOutDirSrc(), clsForProcess.getClassInfo().getAliasFullPath() + fileExt);
|
||||||
|
} else {
|
||||||
|
if (singleClassOutput.endsWith(fileExt)) {
|
||||||
|
// treat as file name
|
||||||
|
out = new File(singleClassOutput);
|
||||||
|
} else {
|
||||||
|
// treat as directory
|
||||||
|
out = new File(singleClassOutput, clsForProcess.getShortName() + fileExt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
File resultOut = FileUtils.prepareFile(out);
|
||||||
|
if (clsForProcess.getClassInfo().hasAlias()) {
|
||||||
|
LOG.info("Saving class '{}' (alias: '{}') to file '{}'",
|
||||||
|
clsForProcess.getClassInfo().getFullName(), clsForProcess.getFullName(), resultOut.getAbsolutePath());
|
||||||
|
} else {
|
||||||
|
LOG.info("Saving class '{}' to file '{}'", clsForProcess.getFullName(), resultOut.getAbsolutePath());
|
||||||
|
}
|
||||||
|
SaveCode.save(codeInfo.getCodeStr(), resultOut);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -39,6 +39,7 @@ public class ConvertToClsSet {
|
|||||||
Path output = inputPaths.remove(0);
|
Path output = inputPaths.remove(0);
|
||||||
|
|
||||||
JadxPluginManager pluginManager = new JadxPluginManager();
|
JadxPluginManager pluginManager = new JadxPluginManager();
|
||||||
|
pluginManager.load();
|
||||||
List<ILoadResult> loadedInputs = new ArrayList<>();
|
List<ILoadResult> loadedInputs = new ArrayList<>();
|
||||||
for (JadxInputPlugin inputPlugin : pluginManager.getInputPlugins()) {
|
for (JadxInputPlugin inputPlugin : pluginManager.getInputPlugins()) {
|
||||||
loadedInputs.add(inputPlugin.loadFiles(inputPaths));
|
loadedInputs.add(inputPlugin.loadFiles(inputPaths));
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'java-library'
|
id 'jadx-library'
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api(project(':jadx-plugins:jadx-plugins-api'))
|
api(project(':jadx-plugins:jadx-plugins-api'))
|
||||||
|
|
||||||
implementation 'com.google.code.gson:gson:2.8.9'
|
implementation 'com.google.code.gson:gson:2.9.0'
|
||||||
implementation 'com.android.tools.build:aapt2-proto:4.2.1-7147631'
|
implementation 'com.android.tools.build:aapt2-proto:4.2.1-7147631'
|
||||||
constraints {
|
constraints {
|
||||||
// Force protobuf version to prevent Java-7 issue
|
// Force protobuf version to prevent Java-7 issue
|
||||||
@@ -20,7 +20,8 @@ dependencies {
|
|||||||
testRuntimeOnly(project(':jadx-plugins:jadx-java-input'))
|
testRuntimeOnly(project(':jadx-plugins:jadx-java-input'))
|
||||||
testRuntimeOnly(project(':jadx-plugins:jadx-raung-input'))
|
testRuntimeOnly(project(':jadx-plugins:jadx-raung-input'))
|
||||||
|
|
||||||
testImplementation('tools.profiler:async-profiler:1.8.3')
|
testImplementation 'org.eclipse.jdt:ecj:3.29.0'
|
||||||
|
testImplementation 'tools.profiler:async-profiler:1.8.3'
|
||||||
}
|
}
|
||||||
|
|
||||||
test {
|
test {
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package jadx.api;
|
||||||
|
|
||||||
|
public enum DecompilationMode {
|
||||||
|
/**
|
||||||
|
* Trying best options (default)
|
||||||
|
*/
|
||||||
|
AUTO,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restore code structure (normal java code)
|
||||||
|
*/
|
||||||
|
RESTRUCTURE,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simplified instructions (linear with goto's)
|
||||||
|
*/
|
||||||
|
SIMPLE,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Raw instructions without modifications
|
||||||
|
*/
|
||||||
|
FALLBACK
|
||||||
|
}
|
||||||
@@ -4,11 +4,14 @@ import java.io.File;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
import jadx.api.args.DeobfuscationMapFileMode;
|
||||||
import jadx.api.data.ICodeData;
|
import jadx.api.data.ICodeData;
|
||||||
import jadx.api.impl.AnnotatedCodeWriter;
|
import jadx.api.impl.AnnotatedCodeWriter;
|
||||||
import jadx.api.impl.InMemoryCodeCache;
|
import jadx.api.impl.InMemoryCodeCache;
|
||||||
@@ -35,7 +38,6 @@ public class JadxArgs {
|
|||||||
private boolean cfgOutput = false;
|
private boolean cfgOutput = false;
|
||||||
private boolean rawCFGOutput = false;
|
private boolean rawCFGOutput = false;
|
||||||
|
|
||||||
private boolean fallbackMode = false;
|
|
||||||
private boolean showInconsistentCode = false;
|
private boolean showInconsistentCode = false;
|
||||||
|
|
||||||
private boolean useImports = true;
|
private boolean useImports = true;
|
||||||
@@ -54,11 +56,12 @@ public class JadxArgs {
|
|||||||
private Predicate<String> classFilter = null;
|
private Predicate<String> classFilter = null;
|
||||||
|
|
||||||
private boolean deobfuscationOn = false;
|
private boolean deobfuscationOn = false;
|
||||||
private boolean deobfuscationForceSave = false;
|
|
||||||
private boolean useSourceNameAsClassAlias = false;
|
private boolean useSourceNameAsClassAlias = false;
|
||||||
private boolean parseKotlinMetadata = false;
|
private boolean parseKotlinMetadata = false;
|
||||||
private File deobfuscationMapFile = null;
|
private File deobfuscationMapFile = null;
|
||||||
|
|
||||||
|
private DeobfuscationMapFileMode deobfuscationMapFileMode = DeobfuscationMapFileMode.READ;
|
||||||
|
|
||||||
private int deobfuscationMinLength = 0;
|
private int deobfuscationMinLength = 0;
|
||||||
private int deobfuscationMaxLength = Integer.MAX_VALUE;
|
private int deobfuscationMaxLength = Integer.MAX_VALUE;
|
||||||
|
|
||||||
@@ -81,10 +84,27 @@ public class JadxArgs {
|
|||||||
|
|
||||||
private OutputFormatEnum outputFormat = OutputFormatEnum.JAVA;
|
private OutputFormatEnum outputFormat = OutputFormatEnum.JAVA;
|
||||||
|
|
||||||
|
private DecompilationMode decompilationMode = DecompilationMode.AUTO;
|
||||||
|
|
||||||
private ICodeData codeData;
|
private ICodeData codeData;
|
||||||
|
|
||||||
private CommentsLevel commentsLevel = CommentsLevel.INFO;
|
private CommentsLevel commentsLevel = CommentsLevel.INFO;
|
||||||
|
|
||||||
|
private boolean useDxInput = false;
|
||||||
|
|
||||||
|
public enum UseKotlinMethodsForVarNames {
|
||||||
|
DISABLE, APPLY, APPLY_AND_HIDE
|
||||||
|
}
|
||||||
|
|
||||||
|
private UseKotlinMethodsForVarNames useKotlinMethodsForVarNames = UseKotlinMethodsForVarNames.APPLY;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Don't save files (can be using for performance testing)
|
||||||
|
*/
|
||||||
|
private boolean skipFilesSave = false;
|
||||||
|
|
||||||
|
private Map<String, String> pluginOptions = new HashMap<>();
|
||||||
|
|
||||||
public JadxArgs() {
|
public JadxArgs() {
|
||||||
// use default options
|
// use default options
|
||||||
}
|
}
|
||||||
@@ -136,7 +156,7 @@ public class JadxArgs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setThreadsCount(int threadsCount) {
|
public void setThreadsCount(int threadsCount) {
|
||||||
this.threadsCount = threadsCount;
|
this.threadsCount = Math.max(1, threadsCount); // make sure threadsCount >= 1
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isCfgOutput() {
|
public boolean isCfgOutput() {
|
||||||
@@ -156,11 +176,17 @@ public class JadxArgs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isFallbackMode() {
|
public boolean isFallbackMode() {
|
||||||
return fallbackMode;
|
return decompilationMode == DecompilationMode.FALLBACK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deprecated: use 'decompilation mode' property
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
public void setFallbackMode(boolean fallbackMode) {
|
public void setFallbackMode(boolean fallbackMode) {
|
||||||
this.fallbackMode = fallbackMode;
|
if (fallbackMode) {
|
||||||
|
this.decompilationMode = DecompilationMode.FALLBACK;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isShowInconsistentCode() {
|
public boolean isShowInconsistentCode() {
|
||||||
@@ -251,12 +277,24 @@ public class JadxArgs {
|
|||||||
this.deobfuscationOn = deobfuscationOn;
|
this.deobfuscationOn = deobfuscationOn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public boolean isDeobfuscationForceSave() {
|
public boolean isDeobfuscationForceSave() {
|
||||||
return deobfuscationForceSave;
|
return deobfuscationMapFileMode == DeobfuscationMapFileMode.OVERWRITE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public void setDeobfuscationForceSave(boolean deobfuscationForceSave) {
|
public void setDeobfuscationForceSave(boolean deobfuscationForceSave) {
|
||||||
this.deobfuscationForceSave = deobfuscationForceSave;
|
if (deobfuscationForceSave) {
|
||||||
|
this.deobfuscationMapFileMode = DeobfuscationMapFileMode.OVERWRITE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public DeobfuscationMapFileMode getDeobfuscationMapFileMode() {
|
||||||
|
return deobfuscationMapFileMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDeobfuscationMapFileMode(DeobfuscationMapFileMode deobfuscationMapFileMode) {
|
||||||
|
this.deobfuscationMapFileMode = deobfuscationMapFileMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isUseSourceNameAsClassAlias() {
|
public boolean isUseSourceNameAsClassAlias() {
|
||||||
@@ -391,6 +429,14 @@ public class JadxArgs {
|
|||||||
this.outputFormat = outputFormat;
|
this.outputFormat = outputFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DecompilationMode getDecompilationMode() {
|
||||||
|
return decompilationMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDecompilationMode(DecompilationMode decompilationMode) {
|
||||||
|
this.decompilationMode = decompilationMode;
|
||||||
|
}
|
||||||
|
|
||||||
public ICodeCache getCodeCache() {
|
public ICodeCache getCodeCache() {
|
||||||
return codeCache;
|
return codeCache;
|
||||||
}
|
}
|
||||||
@@ -423,6 +469,38 @@ public class JadxArgs {
|
|||||||
this.commentsLevel = commentsLevel;
|
this.commentsLevel = commentsLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isUseDxInput() {
|
||||||
|
return useDxInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUseDxInput(boolean useDxInput) {
|
||||||
|
this.useDxInput = useDxInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UseKotlinMethodsForVarNames getUseKotlinMethodsForVarNames() {
|
||||||
|
return useKotlinMethodsForVarNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUseKotlinMethodsForVarNames(UseKotlinMethodsForVarNames useKotlinMethodsForVarNames) {
|
||||||
|
this.useKotlinMethodsForVarNames = useKotlinMethodsForVarNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSkipFilesSave() {
|
||||||
|
return skipFilesSave;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSkipFilesSave(boolean skipFilesSave) {
|
||||||
|
this.skipFilesSave = skipFilesSave;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getPluginOptions() {
|
||||||
|
return pluginOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPluginOptions(Map<String, String> pluginOptions) {
|
||||||
|
this.pluginOptions = pluginOptions;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "JadxArgs{" + "inputFiles=" + inputFiles
|
return "JadxArgs{" + "inputFiles=" + inputFiles
|
||||||
@@ -430,18 +508,17 @@ public class JadxArgs {
|
|||||||
+ ", outDirSrc=" + outDirSrc
|
+ ", outDirSrc=" + outDirSrc
|
||||||
+ ", outDirRes=" + outDirRes
|
+ ", outDirRes=" + outDirRes
|
||||||
+ ", threadsCount=" + threadsCount
|
+ ", threadsCount=" + threadsCount
|
||||||
+ ", cfgOutput=" + cfgOutput
|
+ ", decompilationMode=" + decompilationMode
|
||||||
+ ", rawCFGOutput=" + rawCFGOutput
|
|
||||||
+ ", fallbackMode=" + fallbackMode
|
|
||||||
+ ", showInconsistentCode=" + showInconsistentCode
|
+ ", showInconsistentCode=" + showInconsistentCode
|
||||||
+ ", useImports=" + useImports
|
+ ", useImports=" + useImports
|
||||||
+ ", skipResources=" + skipResources
|
+ ", skipResources=" + skipResources
|
||||||
+ ", skipSources=" + skipSources
|
+ ", skipSources=" + skipSources
|
||||||
+ ", deobfuscationOn=" + deobfuscationOn
|
+ ", deobfuscationOn=" + deobfuscationOn
|
||||||
+ ", deobfuscationMapFile=" + deobfuscationMapFile
|
+ ", deobfuscationMapFile=" + deobfuscationMapFile
|
||||||
+ ", deobfuscationForceSave=" + deobfuscationForceSave
|
+ ", deobfuscationMapFileMode=" + deobfuscationMapFileMode
|
||||||
+ ", useSourceNameAsClassAlias=" + useSourceNameAsClassAlias
|
+ ", useSourceNameAsClassAlias=" + useSourceNameAsClassAlias
|
||||||
+ ", parseKotlinMetadata=" + parseKotlinMetadata
|
+ ", parseKotlinMetadata=" + parseKotlinMetadata
|
||||||
|
+ ", useKotlinMethodsForVarNames=" + useKotlinMethodsForVarNames
|
||||||
+ ", deobfuscationMinLength=" + deobfuscationMinLength
|
+ ", deobfuscationMinLength=" + deobfuscationMinLength
|
||||||
+ ", deobfuscationMaxLength=" + deobfuscationMaxLength
|
+ ", deobfuscationMaxLength=" + deobfuscationMaxLength
|
||||||
+ ", escapeUnicode=" + escapeUnicode
|
+ ", escapeUnicode=" + escapeUnicode
|
||||||
@@ -454,6 +531,10 @@ public class JadxArgs {
|
|||||||
+ ", commentsLevel=" + commentsLevel
|
+ ", commentsLevel=" + commentsLevel
|
||||||
+ ", codeCache=" + codeCache
|
+ ", codeCache=" + codeCache
|
||||||
+ ", codeWriter=" + codeWriterProvider.apply(this).getClass().getSimpleName()
|
+ ", codeWriter=" + codeWriterProvider.apply(this).getClass().getSimpleName()
|
||||||
|
+ ", useDxInput=" + useDxInput
|
||||||
|
+ ", pluginOptions=" + pluginOptions
|
||||||
|
+ ", cfgOutput=" + cfgOutput
|
||||||
|
+ ", rawCFGOutput=" + rawCFGOutput
|
||||||
+ '}';
|
+ '}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,8 +30,11 @@ import jadx.api.plugins.JadxPlugin;
|
|||||||
import jadx.api.plugins.JadxPluginManager;
|
import jadx.api.plugins.JadxPluginManager;
|
||||||
import jadx.api.plugins.input.JadxInputPlugin;
|
import jadx.api.plugins.input.JadxInputPlugin;
|
||||||
import jadx.api.plugins.input.data.ILoadResult;
|
import jadx.api.plugins.input.data.ILoadResult;
|
||||||
|
import jadx.api.plugins.options.JadxPluginOptions;
|
||||||
import jadx.core.Jadx;
|
import jadx.core.Jadx;
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
|
import jadx.core.dex.attributes.AType;
|
||||||
|
import jadx.core.dex.attributes.nodes.InlinedAttr;
|
||||||
import jadx.core.dex.attributes.nodes.LineAttrNode;
|
import jadx.core.dex.attributes.nodes.LineAttrNode;
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
import jadx.core.dex.nodes.FieldNode;
|
import jadx.core.dex.nodes.FieldNode;
|
||||||
@@ -107,6 +110,7 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
reset();
|
reset();
|
||||||
JadxArgsValidator.validate(args);
|
JadxArgsValidator.validate(args);
|
||||||
LOG.info("loading ...");
|
LOG.info("loading ...");
|
||||||
|
loadPlugins(args);
|
||||||
loadInputFiles();
|
loadInputFiles();
|
||||||
|
|
||||||
root = new RootNode(args);
|
root = new RootNode(args);
|
||||||
@@ -121,12 +125,16 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
loadedInputs.clear();
|
loadedInputs.clear();
|
||||||
List<Path> inputPaths = Utils.collectionMap(args.getInputFiles(), File::toPath);
|
List<Path> inputPaths = Utils.collectionMap(args.getInputFiles(), File::toPath);
|
||||||
List<Path> inputFiles = FileUtils.expandDirs(inputPaths);
|
List<Path> inputFiles = FileUtils.expandDirs(inputPaths);
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
for (JadxInputPlugin inputPlugin : pluginManager.getInputPlugins()) {
|
for (JadxInputPlugin inputPlugin : pluginManager.getInputPlugins()) {
|
||||||
ILoadResult loadResult = inputPlugin.loadFiles(inputFiles);
|
ILoadResult loadResult = inputPlugin.loadFiles(inputFiles);
|
||||||
if (loadResult != null && !loadResult.isEmpty()) {
|
if (loadResult != null && !loadResult.isEmpty()) {
|
||||||
loadedInputs.add(loadResult);
|
loadedInputs.add(loadResult);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (LOG.isDebugEnabled()) {
|
||||||
|
LOG.debug("Loaded using {} inputs plugin in {} ms", loadedInputs.size(), System.currentTimeMillis() - start);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void reset() {
|
private void reset() {
|
||||||
@@ -159,6 +167,27 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
reset();
|
reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void loadPlugins(JadxArgs args) {
|
||||||
|
pluginManager.providesSuggestion("java-input", args.isUseDxInput() ? "java-convert" : "java-input");
|
||||||
|
pluginManager.load();
|
||||||
|
if (LOG.isDebugEnabled()) {
|
||||||
|
LOG.debug("Resolved plugins: {}", Utils.collectionMap(pluginManager.getResolvedPlugins(),
|
||||||
|
p -> p.getPluginInfo().getPluginId()));
|
||||||
|
}
|
||||||
|
Map<String, String> pluginOptions = args.getPluginOptions();
|
||||||
|
if (!pluginOptions.isEmpty()) {
|
||||||
|
LOG.debug("Applying plugin options: {}", pluginOptions);
|
||||||
|
for (JadxPluginOptions plugin : pluginManager.getPluginsWithOptions()) {
|
||||||
|
try {
|
||||||
|
plugin.setOptions(pluginOptions);
|
||||||
|
} catch (Exception e) {
|
||||||
|
String pluginId = plugin.getPluginInfo().getPluginId();
|
||||||
|
throw new JadxRuntimeException("Failed to apply options for plugin: " + pluginId, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void registerPlugin(JadxPlugin plugin) {
|
public void registerPlugin(JadxPlugin plugin) {
|
||||||
pluginManager.register(plugin);
|
pluginManager.register(plugin);
|
||||||
}
|
}
|
||||||
@@ -272,6 +301,9 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void appendResourcesSaveTasks(List<Runnable> tasks, File outDir) {
|
private void appendResourcesSaveTasks(List<Runnable> tasks, File outDir) {
|
||||||
|
if (args.isSkipFilesSave()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
Set<String> inputFileNames = args.getInputFiles().stream().map(File::getAbsolutePath).collect(Collectors.toSet());
|
Set<String> inputFileNames = args.getInputFiles().stream().map(File::getAbsolutePath).collect(Collectors.toSet());
|
||||||
for (ResourceFile resourceFile : getResources()) {
|
for (ResourceFile resourceFile : getResources()) {
|
||||||
if (resourceFile.getType() != ResourceType.ARSC
|
if (resourceFile.getType() != ResourceType.ARSC
|
||||||
@@ -296,7 +328,13 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
}
|
}
|
||||||
processQueue.add(cls);
|
processQueue.add(cls);
|
||||||
}
|
}
|
||||||
for (List<JavaClass> decompileBatch : decompileScheduler.buildBatches(processQueue)) {
|
List<List<JavaClass>> batches;
|
||||||
|
try {
|
||||||
|
batches = decompileScheduler.buildBatches(processQueue);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new JadxRuntimeException("Decompilation batches build failed", e);
|
||||||
|
}
|
||||||
|
for (List<JavaClass> decompileBatch : batches) {
|
||||||
tasks.add(() -> {
|
tasks.add(() -> {
|
||||||
for (JavaClass cls : decompileBatch) {
|
for (JavaClass cls : decompileBatch) {
|
||||||
try {
|
try {
|
||||||
@@ -413,6 +451,10 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
classesMap.put(innerCls.getClassNode(), innerCls);
|
classesMap.put(innerCls.getClassNode(), innerCls);
|
||||||
loadJavaClass(innerCls);
|
loadJavaClass(innerCls);
|
||||||
}
|
}
|
||||||
|
for (JavaClass inlinedCls : javaClass.getInlinedClasses()) {
|
||||||
|
classesMap.put(inlinedCls.getClassNode(), inlinedCls);
|
||||||
|
loadJavaClass(inlinedCls);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -443,12 +485,12 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
if (parentClass.contains(AFlag.DONT_GENERATE)) {
|
if (parentClass.contains(AFlag.DONT_GENERATE)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (parentClass != cls) {
|
JavaClass parentJavaClass = classesMap.get(parentClass);
|
||||||
JavaClass parentJavaClass = classesMap.get(parentClass);
|
if (parentJavaClass == null) {
|
||||||
if (parentJavaClass == null) {
|
getClasses();
|
||||||
getClasses();
|
parentJavaClass = classesMap.get(parentClass);
|
||||||
parentJavaClass = classesMap.get(parentClass);
|
}
|
||||||
}
|
if (parentJavaClass != null) {
|
||||||
loadJavaClass(parentJavaClass);
|
loadJavaClass(parentJavaClass);
|
||||||
javaClass = classesMap.get(cls);
|
javaClass = classesMap.get(cls);
|
||||||
if (javaClass != null) {
|
if (javaClass != null) {
|
||||||
@@ -472,7 +514,9 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
// parent class not loaded yet
|
// parent class not loaded yet
|
||||||
JavaClass javaClass = getJavaClassByNode(mth.getParentClass().getTopParentClass());
|
ClassNode parentClass = mth.getParentClass();
|
||||||
|
ClassNode codeCls = getCodeParentClass(parentClass);
|
||||||
|
JavaClass javaClass = getJavaClassByNode(codeCls);
|
||||||
if (javaClass == null) {
|
if (javaClass == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -481,12 +525,26 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
if (javaMethod != null) {
|
if (javaMethod != null) {
|
||||||
return javaMethod;
|
return javaMethod;
|
||||||
}
|
}
|
||||||
if (mth.getParentClass().hasNotGeneratedParent()) {
|
if (parentClass.hasNotGeneratedParent()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
throw new JadxRuntimeException("JavaMethod not found by MethodNode: " + mth);
|
throw new JadxRuntimeException("JavaMethod not found by MethodNode: " + mth);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ClassNode getCodeParentClass(ClassNode cls) {
|
||||||
|
ClassNode codeCls;
|
||||||
|
InlinedAttr inlinedAttr = cls.get(AType.INLINED);
|
||||||
|
if (inlinedAttr != null) {
|
||||||
|
codeCls = inlinedAttr.getInlineCls().getTopParentClass();
|
||||||
|
} else {
|
||||||
|
codeCls = cls.getTopParentClass();
|
||||||
|
}
|
||||||
|
if (codeCls == cls) {
|
||||||
|
return codeCls;
|
||||||
|
}
|
||||||
|
return getCodeParentClass(codeCls);
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private JavaField getJavaFieldByNode(FieldNode fld) {
|
private JavaField getJavaFieldByNode(FieldNode fld) {
|
||||||
JavaField javaField = fieldsMap.get(fld);
|
JavaField javaField = fieldsMap.get(fld);
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import org.jetbrains.annotations.ApiStatus;
|
|||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
|
import jadx.core.dex.attributes.AType;
|
||||||
|
import jadx.core.dex.attributes.nodes.AnonymousClassAttr;
|
||||||
import jadx.core.dex.info.AccessInfo;
|
import jadx.core.dex.info.AccessInfo;
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
import jadx.core.dex.nodes.FieldNode;
|
import jadx.core.dex.nodes.FieldNode;
|
||||||
@@ -23,6 +25,7 @@ public final class JavaClass implements JavaNode {
|
|||||||
private final JavaClass parent;
|
private final JavaClass parent;
|
||||||
|
|
||||||
private List<JavaClass> innerClasses = Collections.emptyList();
|
private List<JavaClass> innerClasses = Collections.emptyList();
|
||||||
|
private List<JavaClass> inlinedClasses = Collections.emptyList();
|
||||||
private List<JavaField> fields = Collections.emptyList();
|
private List<JavaField> fields = Collections.emptyList();
|
||||||
private List<JavaMethod> methods = Collections.emptyList();
|
private List<JavaMethod> methods = Collections.emptyList();
|
||||||
private boolean listsLoaded;
|
private boolean listsLoaded;
|
||||||
@@ -68,6 +71,10 @@ public final class JavaClass implements JavaNode {
|
|||||||
cls.unloadCode();
|
cls.unloadCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isNoCode() {
|
||||||
|
return cls.contains(AFlag.DONT_GENERATE);
|
||||||
|
}
|
||||||
|
|
||||||
public synchronized String getSmali() {
|
public synchronized String getSmali() {
|
||||||
return cls.getDisassembledCode();
|
return cls.getDisassembledCode();
|
||||||
}
|
}
|
||||||
@@ -100,13 +107,23 @@ public final class JavaClass implements JavaNode {
|
|||||||
}
|
}
|
||||||
this.innerClasses = Collections.unmodifiableList(list);
|
this.innerClasses = Collections.unmodifiableList(list);
|
||||||
}
|
}
|
||||||
|
int inlinedClsCount = cls.getInlinedClasses().size();
|
||||||
|
if (inlinedClsCount != 0) {
|
||||||
|
List<JavaClass> list = new ArrayList<>(inlinedClsCount);
|
||||||
|
for (ClassNode inner : cls.getInlinedClasses()) {
|
||||||
|
JavaClass javaClass = rootDecompiler.convertClassNode(inner);
|
||||||
|
javaClass.loadLists();
|
||||||
|
list.add(javaClass);
|
||||||
|
}
|
||||||
|
this.inlinedClasses = Collections.unmodifiableList(list);
|
||||||
|
}
|
||||||
|
|
||||||
int fieldsCount = cls.getFields().size();
|
int fieldsCount = cls.getFields().size();
|
||||||
if (fieldsCount != 0) {
|
if (fieldsCount != 0) {
|
||||||
List<JavaField> flds = new ArrayList<>(fieldsCount);
|
List<JavaField> flds = new ArrayList<>(fieldsCount);
|
||||||
for (FieldNode f : cls.getFields()) {
|
for (FieldNode f : cls.getFields()) {
|
||||||
if (!f.contains(AFlag.DONT_GENERATE)) {
|
if (!f.contains(AFlag.DONT_GENERATE)) {
|
||||||
JavaField javaField = new JavaField(f, this);
|
JavaField javaField = new JavaField(this, f);
|
||||||
flds.add(javaField);
|
flds.add(javaField);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -226,7 +243,7 @@ public final class JavaClass implements JavaNode {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JavaClass getTopParentClass() {
|
public JavaClass getTopParentClass() {
|
||||||
if (cls.contains(AFlag.ANONYMOUS_CLASS)) {
|
if (cls.contains(AType.ANONYMOUS_CLASS)) {
|
||||||
// moved to usage class
|
// moved to usage class
|
||||||
return getParentForAnonymousClass();
|
return getParentForAnonymousClass();
|
||||||
}
|
}
|
||||||
@@ -234,15 +251,9 @@ public final class JavaClass implements JavaNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private JavaClass getParentForAnonymousClass() {
|
private JavaClass getParentForAnonymousClass() {
|
||||||
List<JavaNode> useIn = getUseIn();
|
AnonymousClassAttr attr = cls.get(AType.ANONYMOUS_CLASS);
|
||||||
if (useIn.isEmpty()) {
|
ClassNode topParentClass = attr.getOuterCls().getTopParentClass();
|
||||||
return this;
|
return getRootDecompiler().convertClassNode(topParentClass);
|
||||||
}
|
|
||||||
JavaNode useNode = useIn.get(0);
|
|
||||||
if (useNode.equals(this)) {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
return useNode.getTopParentClass();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public AccessInfo getAccessInfo() {
|
public AccessInfo getAccessInfo() {
|
||||||
@@ -254,6 +265,11 @@ public final class JavaClass implements JavaNode {
|
|||||||
return innerClasses;
|
return innerClasses;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<JavaClass> getInlinedClasses() {
|
||||||
|
loadLists();
|
||||||
|
return inlinedClasses;
|
||||||
|
}
|
||||||
|
|
||||||
public List<JavaField> getFields() {
|
public List<JavaField> getFields() {
|
||||||
loadLists();
|
loadLists();
|
||||||
return fields;
|
return fields;
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ public final class JavaField implements JavaNode {
|
|||||||
private final FieldNode field;
|
private final FieldNode field;
|
||||||
private final JavaClass parent;
|
private final JavaClass parent;
|
||||||
|
|
||||||
JavaField(FieldNode f, JavaClass cls) {
|
JavaField(JavaClass cls, FieldNode f) {
|
||||||
this.field = f;
|
this.field = f;
|
||||||
this.parent = cls;
|
this.parent = cls;
|
||||||
}
|
}
|
||||||
@@ -28,6 +28,10 @@ public final class JavaField implements JavaNode {
|
|||||||
return parent.getFullName() + '.' + getName();
|
return parent.getFullName() + '.' + getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getRawName() {
|
||||||
|
return field.getName();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JavaClass getDeclaringClass() {
|
public JavaClass getDeclaringClass() {
|
||||||
return parent;
|
return parent;
|
||||||
|
|||||||
@@ -2,9 +2,12 @@ package jadx.api;
|
|||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.jetbrains.annotations.ApiStatus;
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import jadx.core.dex.attributes.AType;
|
import jadx.core.dex.attributes.AType;
|
||||||
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
|
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
|
||||||
@@ -14,6 +17,7 @@ import jadx.core.dex.nodes.MethodNode;
|
|||||||
import jadx.core.utils.Utils;
|
import jadx.core.utils.Utils;
|
||||||
|
|
||||||
public final class JavaMethod implements JavaNode {
|
public final class JavaMethod implements JavaNode {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(JavaMethod.class);
|
||||||
private final MethodNode mth;
|
private final MethodNode mth;
|
||||||
private final JavaClass parent;
|
private final JavaClass parent;
|
||||||
|
|
||||||
@@ -73,7 +77,14 @@ public final class JavaMethod implements JavaNode {
|
|||||||
}
|
}
|
||||||
JadxDecompiler decompiler = getDeclaringClass().getRootDecompiler();
|
JadxDecompiler decompiler = getDeclaringClass().getRootDecompiler();
|
||||||
return ovrdAttr.getRelatedMthNodes().stream()
|
return ovrdAttr.getRelatedMthNodes().stream()
|
||||||
.map(m -> ((JavaMethod) decompiler.convertNode(m)))
|
.map(m -> {
|
||||||
|
JavaMethod javaMth = (JavaMethod) decompiler.convertNode(m);
|
||||||
|
if (javaMth == null) {
|
||||||
|
LOG.warn("Failed convert to java method: {}", m);
|
||||||
|
}
|
||||||
|
return javaMth;
|
||||||
|
})
|
||||||
|
.filter(Objects::nonNull)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,10 @@ public class ResourceFile {
|
|||||||
private ZipRef zipRef;
|
private ZipRef zipRef;
|
||||||
private String deobfName;
|
private String deobfName;
|
||||||
|
|
||||||
|
public static ResourceFile createResourceFile(JadxDecompiler decompiler, File file, ResourceType type) {
|
||||||
|
return new ResourceFile(decompiler, file.getAbsolutePath(), type);
|
||||||
|
}
|
||||||
|
|
||||||
public static ResourceFile createResourceFile(JadxDecompiler decompiler, String name, ResourceType type) {
|
public static ResourceFile createResourceFile(JadxDecompiler decompiler, String name, ResourceType type) {
|
||||||
if (!ZipSecurity.isValidZipEntryName(name)) {
|
if (!ZipSecurity.isValidZipEntryName(name)) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ public enum ResourceType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static ResourceType getFileType(String fileName) {
|
public static ResourceType getFileType(String fileName) {
|
||||||
if (fileName.matches("[^/]+/resources.pb")) {
|
if (fileName.endsWith("/resources.pb")) {
|
||||||
return ARSC;
|
return ARSC;
|
||||||
}
|
}
|
||||||
int dot = fileName.lastIndexOf('.');
|
int dot = fileName.lastIndexOf('.');
|
||||||
|
|||||||
@@ -126,8 +126,9 @@ public final class ResourcesLoader {
|
|||||||
if (name.endsWith(".9.png")) {
|
if (name.endsWith(".9.png")) {
|
||||||
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
|
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
|
||||||
Res9patchStreamDecoder decoder = new Res9patchStreamDecoder();
|
Res9patchStreamDecoder decoder = new Res9patchStreamDecoder();
|
||||||
decoder.decode(inputStream, os);
|
if (decoder.decode(inputStream, os)) {
|
||||||
return ResContainer.decodedData(rf.getDeobfName(), os.toByteArray());
|
return ResContainer.decodedData(rf.getDeobfName(), os.toByteArray());
|
||||||
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error("Failed to decode 9-patch png image, path: {}", name, e);
|
LOG.error("Failed to decode 9-patch png image, path: {}", name, e);
|
||||||
}
|
}
|
||||||
@@ -145,16 +146,8 @@ public final class ResourcesLoader {
|
|||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
addResourceFile(list, file);
|
ResourceType type = ResourceType.getFileType(file.getAbsolutePath());
|
||||||
}
|
list.add(ResourceFile.createResourceFile(jadxRef, file, type));
|
||||||
}
|
|
||||||
|
|
||||||
private void addResourceFile(List<ResourceFile> list, File file) {
|
|
||||||
String name = file.getAbsolutePath();
|
|
||||||
ResourceType type = ResourceType.getFileType(name);
|
|
||||||
ResourceFile rf = ResourceFile.createResourceFile(jadxRef, name, type);
|
|
||||||
if (rf != null) {
|
|
||||||
list.add(rf);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package jadx.api.args;
|
||||||
|
|
||||||
|
public enum DeobfuscationMapFileMode {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load if found, don't save (default)
|
||||||
|
*/
|
||||||
|
READ,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load if found, save only if new (don't overwrite)
|
||||||
|
*/
|
||||||
|
READ_OR_SAVE,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Don't load, always save
|
||||||
|
*/
|
||||||
|
OVERWRITE,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Don't load and don't save
|
||||||
|
*/
|
||||||
|
IGNORE;
|
||||||
|
|
||||||
|
public boolean shouldRead() {
|
||||||
|
return this == READ || this == READ_OR_SAVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean shouldWrite() {
|
||||||
|
return this == READ_OR_SAVE || this == OVERWRITE;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,6 +12,8 @@ import org.slf4j.LoggerFactory;
|
|||||||
|
|
||||||
import jadx.api.CommentsLevel;
|
import jadx.api.CommentsLevel;
|
||||||
import jadx.api.JadxArgs;
|
import jadx.api.JadxArgs;
|
||||||
|
import jadx.core.dex.attributes.AFlag;
|
||||||
|
import jadx.core.dex.visitors.AnonymousClassVisitor;
|
||||||
import jadx.core.dex.visitors.AttachCommentsVisitor;
|
import jadx.core.dex.visitors.AttachCommentsVisitor;
|
||||||
import jadx.core.dex.visitors.AttachMethodDetails;
|
import jadx.core.dex.visitors.AttachMethodDetails;
|
||||||
import jadx.core.dex.visitors.AttachTryCatchVisitor;
|
import jadx.core.dex.visitors.AttachTryCatchVisitor;
|
||||||
@@ -31,12 +33,14 @@ import jadx.core.dex.visitors.InitCodeVariables;
|
|||||||
import jadx.core.dex.visitors.InlineMethods;
|
import jadx.core.dex.visitors.InlineMethods;
|
||||||
import jadx.core.dex.visitors.MarkMethodsForInline;
|
import jadx.core.dex.visitors.MarkMethodsForInline;
|
||||||
import jadx.core.dex.visitors.MethodInvokeVisitor;
|
import jadx.core.dex.visitors.MethodInvokeVisitor;
|
||||||
|
import jadx.core.dex.visitors.MethodVisitor;
|
||||||
import jadx.core.dex.visitors.ModVisitor;
|
import jadx.core.dex.visitors.ModVisitor;
|
||||||
import jadx.core.dex.visitors.MoveInlineVisitor;
|
import jadx.core.dex.visitors.MoveInlineVisitor;
|
||||||
import jadx.core.dex.visitors.OverrideMethodVisitor;
|
import jadx.core.dex.visitors.OverrideMethodVisitor;
|
||||||
import jadx.core.dex.visitors.PrepareForCodeGen;
|
import jadx.core.dex.visitors.PrepareForCodeGen;
|
||||||
import jadx.core.dex.visitors.ProcessAnonymous;
|
import jadx.core.dex.visitors.ProcessAnonymous;
|
||||||
import jadx.core.dex.visitors.ProcessInstructionsVisitor;
|
import jadx.core.dex.visitors.ProcessInstructionsVisitor;
|
||||||
|
import jadx.core.dex.visitors.ProcessMethodsForInline;
|
||||||
import jadx.core.dex.visitors.ReSugarCode;
|
import jadx.core.dex.visitors.ReSugarCode;
|
||||||
import jadx.core.dex.visitors.ShadowFieldVisitor;
|
import jadx.core.dex.visitors.ShadowFieldVisitor;
|
||||||
import jadx.core.dex.visitors.SignatureProcessor;
|
import jadx.core.dex.visitors.SignatureProcessor;
|
||||||
@@ -46,6 +50,7 @@ import jadx.core.dex.visitors.blocks.BlockSplitter;
|
|||||||
import jadx.core.dex.visitors.debuginfo.DebugInfoApplyVisitor;
|
import jadx.core.dex.visitors.debuginfo.DebugInfoApplyVisitor;
|
||||||
import jadx.core.dex.visitors.debuginfo.DebugInfoAttachVisitor;
|
import jadx.core.dex.visitors.debuginfo.DebugInfoAttachVisitor;
|
||||||
import jadx.core.dex.visitors.finaly.MarkFinallyVisitor;
|
import jadx.core.dex.visitors.finaly.MarkFinallyVisitor;
|
||||||
|
import jadx.core.dex.visitors.kotlin.ProcessKotlinInternals;
|
||||||
import jadx.core.dex.visitors.regions.CheckRegions;
|
import jadx.core.dex.visitors.regions.CheckRegions;
|
||||||
import jadx.core.dex.visitors.regions.CleanRegions;
|
import jadx.core.dex.visitors.regions.CleanRegions;
|
||||||
import jadx.core.dex.visitors.regions.IfRegionVisitor;
|
import jadx.core.dex.visitors.regions.IfRegionVisitor;
|
||||||
@@ -57,8 +62,10 @@ import jadx.core.dex.visitors.rename.CodeRenameVisitor;
|
|||||||
import jadx.core.dex.visitors.rename.RenameVisitor;
|
import jadx.core.dex.visitors.rename.RenameVisitor;
|
||||||
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
|
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
|
||||||
import jadx.core.dex.visitors.ssa.SSATransform;
|
import jadx.core.dex.visitors.ssa.SSATransform;
|
||||||
|
import jadx.core.dex.visitors.typeinference.FinishTypeInference;
|
||||||
import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor;
|
import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor;
|
||||||
import jadx.core.dex.visitors.usage.UsageInfoVisitor;
|
import jadx.core.dex.visitors.usage.UsageInfoVisitor;
|
||||||
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
public class Jadx {
|
public class Jadx {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(Jadx.class);
|
private static final Logger LOG = LoggerFactory.getLogger(Jadx.class);
|
||||||
@@ -66,21 +73,20 @@ public class Jadx {
|
|||||||
private Jadx() {
|
private Jadx() {
|
||||||
}
|
}
|
||||||
|
|
||||||
static {
|
public static List<IDexTreeVisitor> getPassesList(JadxArgs args) {
|
||||||
if (Consts.DEBUG) {
|
switch (args.getDecompilationMode()) {
|
||||||
LOG.info("debug enabled");
|
case AUTO:
|
||||||
|
case RESTRUCTURE:
|
||||||
|
return getRegionsModePasses(args);
|
||||||
|
case SIMPLE:
|
||||||
|
return getSimpleModePasses(args);
|
||||||
|
case FALLBACK:
|
||||||
|
return getFallbackPassesList();
|
||||||
|
default:
|
||||||
|
throw new JadxRuntimeException("Unknown decompilation mode: " + args.getDecompilationMode());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<IDexTreeVisitor> getFallbackPassesList() {
|
|
||||||
List<IDexTreeVisitor> passes = new ArrayList<>();
|
|
||||||
passes.add(new AttachTryCatchVisitor());
|
|
||||||
passes.add(new AttachCommentsVisitor());
|
|
||||||
passes.add(new ProcessInstructionsVisitor());
|
|
||||||
passes.add(new FallbackModeVisitor());
|
|
||||||
return passes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<IDexTreeVisitor> getPreDecompilePassesList() {
|
public static List<IDexTreeVisitor> getPreDecompilePassesList() {
|
||||||
List<IDexTreeVisitor> passes = new ArrayList<>();
|
List<IDexTreeVisitor> passes = new ArrayList<>();
|
||||||
passes.add(new SignatureProcessor());
|
passes.add(new SignatureProcessor());
|
||||||
@@ -88,15 +94,12 @@ public class Jadx {
|
|||||||
passes.add(new RenameVisitor());
|
passes.add(new RenameVisitor());
|
||||||
passes.add(new UsageInfoVisitor());
|
passes.add(new UsageInfoVisitor());
|
||||||
passes.add(new ProcessAnonymous());
|
passes.add(new ProcessAnonymous());
|
||||||
|
passes.add(new ProcessMethodsForInline());
|
||||||
return passes;
|
return passes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<IDexTreeVisitor> getPassesList(JadxArgs args) {
|
public static List<IDexTreeVisitor> getRegionsModePasses(JadxArgs args) {
|
||||||
if (args.isFallbackMode()) {
|
|
||||||
return getFallbackPassesList();
|
|
||||||
}
|
|
||||||
List<IDexTreeVisitor> passes = new ArrayList<>();
|
List<IDexTreeVisitor> passes = new ArrayList<>();
|
||||||
|
|
||||||
// instructions IR
|
// instructions IR
|
||||||
passes.add(new CheckCode());
|
passes.add(new CheckCode());
|
||||||
if (args.isDebugInfo()) {
|
if (args.isDebugInfo()) {
|
||||||
@@ -128,6 +131,10 @@ public class Jadx {
|
|||||||
if (args.isDebugInfo()) {
|
if (args.isDebugInfo()) {
|
||||||
passes.add(new DebugInfoApplyVisitor());
|
passes.add(new DebugInfoApplyVisitor());
|
||||||
}
|
}
|
||||||
|
passes.add(new FinishTypeInference());
|
||||||
|
if (args.getUseKotlinMethodsForVarNames() != JadxArgs.UseKotlinMethodsForVarNames.DISABLE) {
|
||||||
|
passes.add(new ProcessKotlinInternals());
|
||||||
|
}
|
||||||
passes.add(new CodeRenameVisitor());
|
passes.add(new CodeRenameVisitor());
|
||||||
if (args.isInlineMethods()) {
|
if (args.isInlineMethods()) {
|
||||||
passes.add(new InlineMethods());
|
passes.add(new InlineMethods());
|
||||||
@@ -135,6 +142,7 @@ public class Jadx {
|
|||||||
passes.add(new GenericTypesVisitor());
|
passes.add(new GenericTypesVisitor());
|
||||||
passes.add(new ShadowFieldVisitor());
|
passes.add(new ShadowFieldVisitor());
|
||||||
passes.add(new DeboxingVisitor());
|
passes.add(new DeboxingVisitor());
|
||||||
|
passes.add(new AnonymousClassVisitor());
|
||||||
passes.add(new ModVisitor());
|
passes.add(new ModVisitor());
|
||||||
passes.add(new CodeShrinkVisitor());
|
passes.add(new CodeShrinkVisitor());
|
||||||
passes.add(new ReSugarCode());
|
passes.add(new ReSugarCode());
|
||||||
@@ -170,7 +178,69 @@ public class Jadx {
|
|||||||
return passes;
|
return passes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static List<IDexTreeVisitor> getSimpleModePasses(JadxArgs args) {
|
||||||
|
List<IDexTreeVisitor> passes = new ArrayList<>();
|
||||||
|
if (args.isDebugInfo()) {
|
||||||
|
passes.add(new DebugInfoAttachVisitor());
|
||||||
|
}
|
||||||
|
passes.add(new AttachTryCatchVisitor());
|
||||||
|
if (args.getCommentsLevel() != CommentsLevel.NONE) {
|
||||||
|
passes.add(new AttachCommentsVisitor());
|
||||||
|
}
|
||||||
|
passes.add(new AttachMethodDetails());
|
||||||
|
passes.add(new ProcessInstructionsVisitor());
|
||||||
|
|
||||||
|
passes.add(new BlockSplitter());
|
||||||
|
passes.add(new MethodVisitor(mth -> mth.add(AFlag.DISABLE_BLOCKS_LOCK)));
|
||||||
|
if (args.isRawCFGOutput()) {
|
||||||
|
passes.add(DotGraphVisitor.dumpRaw());
|
||||||
|
}
|
||||||
|
passes.add(new MethodVisitor(mth -> mth.add(AFlag.DISABLE_BLOCKS_LOCK)));
|
||||||
|
passes.add(new BlockProcessor());
|
||||||
|
passes.add(new SSATransform());
|
||||||
|
passes.add(new MoveInlineVisitor());
|
||||||
|
passes.add(new ConstructorVisitor());
|
||||||
|
passes.add(new InitCodeVariables());
|
||||||
|
passes.add(new ConstInlineVisitor());
|
||||||
|
passes.add(new TypeInferenceVisitor());
|
||||||
|
if (args.isDebugInfo()) {
|
||||||
|
passes.add(new DebugInfoApplyVisitor());
|
||||||
|
}
|
||||||
|
passes.add(new FinishTypeInference());
|
||||||
|
passes.add(new CodeRenameVisitor());
|
||||||
|
passes.add(new DeboxingVisitor());
|
||||||
|
passes.add(new ModVisitor());
|
||||||
|
passes.add(new CodeShrinkVisitor());
|
||||||
|
passes.add(new ReSugarCode());
|
||||||
|
passes.add(new CodeShrinkVisitor());
|
||||||
|
passes.add(new SimplifyVisitor());
|
||||||
|
passes.add(new MethodVisitor(mth -> mth.remove(AFlag.DONT_GENERATE)));
|
||||||
|
if (args.isRawCFGOutput()) {
|
||||||
|
passes.add(DotGraphVisitor.dumpRaw());
|
||||||
|
}
|
||||||
|
if (args.isCfgOutput()) {
|
||||||
|
passes.add(DotGraphVisitor.dump());
|
||||||
|
}
|
||||||
|
return passes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<IDexTreeVisitor> getFallbackPassesList() {
|
||||||
|
List<IDexTreeVisitor> passes = new ArrayList<>();
|
||||||
|
passes.add(new AttachTryCatchVisitor());
|
||||||
|
passes.add(new AttachCommentsVisitor());
|
||||||
|
passes.add(new ProcessInstructionsVisitor());
|
||||||
|
passes.add(new FallbackModeVisitor());
|
||||||
|
return passes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final String VERSION_DEV = "dev";
|
||||||
|
|
||||||
|
private static String version;
|
||||||
|
|
||||||
public static String getVersion() {
|
public static String getVersion() {
|
||||||
|
if (version != null) {
|
||||||
|
return version;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
ClassLoader classLoader = Jadx.class.getClassLoader();
|
ClassLoader classLoader = Jadx.class.getClassLoader();
|
||||||
if (classLoader != null) {
|
if (classLoader != null) {
|
||||||
@@ -180,6 +250,7 @@ public class Jadx {
|
|||||||
Manifest manifest = new Manifest(is);
|
Manifest manifest = new Manifest(is);
|
||||||
String ver = manifest.getMainAttributes().getValue("jadx-version");
|
String ver = manifest.getMainAttributes().getValue("jadx-version");
|
||||||
if (ver != null) {
|
if (ver != null) {
|
||||||
|
version = ver;
|
||||||
return ver;
|
return ver;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -188,6 +259,6 @@ public class Jadx {
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error("Can't get manifest file", e);
|
LOG.error("Can't get manifest file", e);
|
||||||
}
|
}
|
||||||
return "dev";
|
return VERSION_DEV;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
package jadx.core;
|
package jadx.core;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import jadx.api.ICodeInfo;
|
import jadx.api.ICodeInfo;
|
||||||
|
import jadx.api.JadxArgs;
|
||||||
import jadx.core.codegen.CodeGen;
|
import jadx.core.codegen.CodeGen;
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
import jadx.core.dex.nodes.LoadStage;
|
import jadx.core.dex.nodes.LoadStage;
|
||||||
|
import jadx.core.dex.nodes.RootNode;
|
||||||
import jadx.core.dex.visitors.DepthTraversal;
|
import jadx.core.dex.visitors.DepthTraversal;
|
||||||
import jadx.core.dex.visitors.IDexTreeVisitor;
|
import jadx.core.dex.visitors.IDexTreeVisitor;
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
@@ -23,13 +24,17 @@ import static jadx.core.dex.nodes.ProcessState.NOT_LOADED;
|
|||||||
import static jadx.core.dex.nodes.ProcessState.PROCESS_COMPLETE;
|
import static jadx.core.dex.nodes.ProcessState.PROCESS_COMPLETE;
|
||||||
import static jadx.core.dex.nodes.ProcessState.PROCESS_STARTED;
|
import static jadx.core.dex.nodes.ProcessState.PROCESS_STARTED;
|
||||||
|
|
||||||
public final class ProcessClass {
|
public class ProcessClass {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(ProcessClass.class);
|
||||||
|
|
||||||
private ProcessClass() {
|
private final List<IDexTreeVisitor> passes;
|
||||||
|
|
||||||
|
public ProcessClass(JadxArgs args) {
|
||||||
|
this.passes = Jadx.getPassesList(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private static ICodeInfo process(ClassNode cls, boolean codegen) {
|
private ICodeInfo process(ClassNode cls, boolean codegen) {
|
||||||
if (!codegen && cls.getState() == PROCESS_COMPLETE) {
|
if (!codegen && cls.getState() == PROCESS_COMPLETE) {
|
||||||
// nothing to do
|
// nothing to do
|
||||||
return null;
|
return null;
|
||||||
@@ -39,17 +44,17 @@ public final class ProcessClass {
|
|||||||
if (cls.contains(AFlag.CLASS_DEEP_RELOAD)) {
|
if (cls.contains(AFlag.CLASS_DEEP_RELOAD)) {
|
||||||
cls.remove(AFlag.CLASS_DEEP_RELOAD);
|
cls.remove(AFlag.CLASS_DEEP_RELOAD);
|
||||||
cls.deepUnload();
|
cls.deepUnload();
|
||||||
cls.root().runPreDecompileStageForClass(cls);
|
cls.add(AFlag.CLASS_UNLOADED);
|
||||||
}
|
}
|
||||||
if (cls.contains(AFlag.CLASS_UNLOADED)) {
|
if (cls.contains(AFlag.CLASS_UNLOADED)) {
|
||||||
cls.remove(AFlag.CLASS_UNLOADED);
|
|
||||||
cls.root().runPreDecompileStageForClass(cls);
|
cls.root().runPreDecompileStageForClass(cls);
|
||||||
|
cls.remove(AFlag.CLASS_UNLOADED);
|
||||||
|
}
|
||||||
|
if (cls.getState() == GENERATED_AND_UNLOADED) {
|
||||||
|
// force loading code again
|
||||||
|
cls.setState(NOT_LOADED);
|
||||||
}
|
}
|
||||||
if (codegen) {
|
if (codegen) {
|
||||||
if (cls.getState() == GENERATED_AND_UNLOADED) {
|
|
||||||
// allow to run code generation again
|
|
||||||
cls.setState(NOT_LOADED);
|
|
||||||
}
|
|
||||||
cls.setLoadStage(LoadStage.CODEGEN_STAGE);
|
cls.setLoadStage(LoadStage.CODEGEN_STAGE);
|
||||||
if (cls.contains(AFlag.RELOAD_AT_CODEGEN_STAGE)) {
|
if (cls.contains(AFlag.RELOAD_AT_CODEGEN_STAGE)) {
|
||||||
cls.remove(AFlag.RELOAD_AT_CODEGEN_STAGE);
|
cls.remove(AFlag.RELOAD_AT_CODEGEN_STAGE);
|
||||||
@@ -63,7 +68,7 @@ public final class ProcessClass {
|
|||||||
}
|
}
|
||||||
if (cls.getState() == LOADED) {
|
if (cls.getState() == LOADED) {
|
||||||
cls.setState(PROCESS_STARTED);
|
cls.setState(PROCESS_STARTED);
|
||||||
for (IDexTreeVisitor visitor : cls.root().getPasses()) {
|
for (IDexTreeVisitor visitor : passes) {
|
||||||
DepthTraversal.visit(visitor, cls);
|
DepthTraversal.visit(visitor, cls);
|
||||||
}
|
}
|
||||||
cls.setState(PROCESS_COMPLETE);
|
cls.setState(PROCESS_COMPLETE);
|
||||||
@@ -88,27 +93,19 @@ public final class ProcessClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public static ICodeInfo generateCode(ClassNode cls) {
|
public ICodeInfo generateCode(ClassNode cls) {
|
||||||
ClassNode topParentClass = cls.getTopParentClass();
|
ClassNode topParentClass = cls.getTopParentClass();
|
||||||
if (topParentClass != cls) {
|
if (topParentClass != cls) {
|
||||||
return generateCode(topParentClass);
|
return generateCode(topParentClass);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
Set<ClassNode> useIn = new HashSet<>(cls.getUseIn());
|
|
||||||
List<ClassNode> usedInDeps = new ArrayList<>();
|
|
||||||
for (ClassNode depCls : cls.getDependencies()) {
|
for (ClassNode depCls : cls.getDependencies()) {
|
||||||
if (useIn.contains(depCls)) {
|
process(depCls, false);
|
||||||
// postpone to resolve cross dependencies
|
|
||||||
usedInDeps.add(depCls);
|
|
||||||
} else {
|
|
||||||
process(depCls, false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (!usedInDeps.isEmpty()) {
|
if (!cls.getCodegenDeps().isEmpty()) {
|
||||||
// process current class before its usage
|
|
||||||
process(cls, false);
|
process(cls, false);
|
||||||
for (ClassNode depCls : usedInDeps) {
|
for (ClassNode codegenDep : cls.getCodegenDeps()) {
|
||||||
process(depCls, false);
|
process(codegenDep, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ICodeInfo code = process(cls, true);
|
ICodeInfo code = process(cls, true);
|
||||||
@@ -120,4 +117,19 @@ public final class ProcessClass {
|
|||||||
throw new JadxRuntimeException("Failed to generate code for class: " + cls.getFullName(), e);
|
throw new JadxRuntimeException("Failed to generate code for class: " + cls.getFullName(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void initPasses(RootNode root) {
|
||||||
|
for (IDexTreeVisitor pass : passes) {
|
||||||
|
try {
|
||||||
|
pass.init(root);
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.error("Visitor init failed: {}", pass.getClass().getSimpleName(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: make passes list private and not visible
|
||||||
|
public List<IDexTreeVisitor> getPasses() {
|
||||||
|
return passes;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ import jadx.api.plugins.input.data.annotations.EncodedValue;
|
|||||||
import jadx.api.plugins.input.data.annotations.IAnnotation;
|
import jadx.api.plugins.input.data.annotations.IAnnotation;
|
||||||
import jadx.api.plugins.input.data.attributes.JadxAttrType;
|
import jadx.api.plugins.input.data.attributes.JadxAttrType;
|
||||||
import jadx.api.plugins.input.data.attributes.types.AnnotationDefaultAttr;
|
import jadx.api.plugins.input.data.attributes.types.AnnotationDefaultAttr;
|
||||||
|
import jadx.api.plugins.input.data.attributes.types.AnnotationMethodParamsAttr;
|
||||||
import jadx.api.plugins.input.data.attributes.types.AnnotationsAttr;
|
import jadx.api.plugins.input.data.attributes.types.AnnotationsAttr;
|
||||||
import jadx.api.plugins.input.data.attributes.types.MethodParamsAttr;
|
|
||||||
import jadx.core.Consts;
|
import jadx.core.Consts;
|
||||||
import jadx.core.dex.attributes.IAttributeNode;
|
import jadx.core.dex.attributes.IAttributeNode;
|
||||||
import jadx.core.dex.info.FieldInfo;
|
import jadx.core.dex.info.FieldInfo;
|
||||||
@@ -48,7 +48,7 @@ public class AnnotationGen {
|
|||||||
add(field, code);
|
add(field, code);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addForParameter(ICodeWriter code, MethodParamsAttr paramsAnnotations, int n) {
|
public void addForParameter(ICodeWriter code, AnnotationMethodParamsAttr paramsAnnotations, int n) {
|
||||||
List<AnnotationsAttr> paramList = paramsAnnotations.getParamList();
|
List<AnnotationsAttr> paramList = paramsAnnotations.getParamList();
|
||||||
if (n >= paramList.size()) {
|
if (n >= paramList.size()) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -285,7 +285,7 @@ public class ClassGen {
|
|||||||
|
|
||||||
private boolean isInnerClassesPresents() {
|
private boolean isInnerClassesPresents() {
|
||||||
for (ClassNode innerCls : cls.getInnerClasses()) {
|
for (ClassNode innerCls : cls.getInnerClasses()) {
|
||||||
if (!innerCls.contains(AFlag.ANONYMOUS_CLASS)) {
|
if (!innerCls.contains(AType.ANONYMOUS_CLASS)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -356,7 +356,7 @@ public class ClassGen {
|
|||||||
badCode = false;
|
badCode = false;
|
||||||
}
|
}
|
||||||
MethodGen mthGen;
|
MethodGen mthGen;
|
||||||
if (badCode || fallback || mth.contains(AType.JADX_ERROR) || mth.getRegion() == null) {
|
if (badCode || fallback || mth.contains(AType.JADX_ERROR)) {
|
||||||
mthGen = MethodGen.getFallbackMethodGen(mth);
|
mthGen = MethodGen.getFallbackMethodGen(mth);
|
||||||
} else {
|
} else {
|
||||||
mthGen = new MethodGen(this, mth);
|
mthGen = new MethodGen(this, mth);
|
||||||
@@ -459,7 +459,7 @@ public class ClassGen {
|
|||||||
}
|
}
|
||||||
if (f.getCls() != null) {
|
if (f.getCls() != null) {
|
||||||
code.add(' ');
|
code.add(' ');
|
||||||
new ClassGen(f.getCls(), this).addClassBody(code);
|
new ClassGen(f.getCls(), this).addClassBody(code, true);
|
||||||
}
|
}
|
||||||
if (it.hasNext()) {
|
if (it.hasNext()) {
|
||||||
code.add(',');
|
code.add(',');
|
||||||
@@ -526,12 +526,42 @@ public class ClassGen {
|
|||||||
if (outerType != null) {
|
if (outerType != null) {
|
||||||
useClass(code, outerType);
|
useClass(code, outerType);
|
||||||
code.add('.');
|
code.add('.');
|
||||||
// import not needed, force use short name
|
addInnerType(code, type);
|
||||||
useClassShortName(code, type.getObject());
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
useClass(code, ClassInfo.fromType(cls.root(), type));
|
useClass(code, ClassInfo.fromType(cls.root(), type));
|
||||||
|
addGenerics(code, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addInnerType(ICodeWriter code, ArgType baseType) {
|
||||||
|
ArgType innerType = baseType.getInnerType();
|
||||||
|
ArgType outerType = innerType.getOuterType();
|
||||||
|
if (outerType != null) {
|
||||||
|
useClassWithShortName(code, baseType, outerType);
|
||||||
|
code.add('.');
|
||||||
|
addInnerType(code, innerType);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
useClassWithShortName(code, baseType, innerType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void useClassWithShortName(ICodeWriter code, ArgType baseType, ArgType type) {
|
||||||
|
String fullNameObj;
|
||||||
|
if (type.getObject().contains(".")) {
|
||||||
|
fullNameObj = type.getObject();
|
||||||
|
} else {
|
||||||
|
fullNameObj = baseType.getObject();
|
||||||
|
}
|
||||||
|
ClassInfo classInfo = ClassInfo.fromName(cls.root(), fullNameObj);
|
||||||
|
ClassNode classNode = cls.root().resolveClass(classInfo);
|
||||||
|
if (classNode != null) {
|
||||||
|
code.attachAnnotation(classNode);
|
||||||
|
}
|
||||||
|
code.add(classInfo.getAliasShortName());
|
||||||
|
addGenerics(code, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addGenerics(ICodeWriter code, ArgType type) {
|
||||||
List<ArgType> generics = type.getGenericTypes();
|
List<ArgType> generics = type.getGenericTypes();
|
||||||
if (generics != null) {
|
if (generics != null) {
|
||||||
code.add('<');
|
code.add('<');
|
||||||
@@ -556,15 +586,6 @@ public class ClassGen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void useClassShortName(ICodeWriter code, String object) {
|
|
||||||
ClassInfo classInfo = ClassInfo.fromName(cls.root(), object);
|
|
||||||
ClassNode classNode = cls.root().resolveClass(classInfo);
|
|
||||||
if (classNode != null) {
|
|
||||||
code.attachAnnotation(classNode);
|
|
||||||
}
|
|
||||||
code.add(classInfo.getAliasShortName());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void useClass(ICodeWriter code, ClassInfo classInfo) {
|
public void useClass(ICodeWriter code, ClassInfo classInfo) {
|
||||||
ClassNode classNode = cls.root().resolveClass(classInfo);
|
ClassNode classNode = cls.root().resolveClass(classInfo);
|
||||||
if (classNode != null) {
|
if (classNode != null) {
|
||||||
@@ -590,6 +611,9 @@ public class ClassGen {
|
|||||||
return fullName;
|
return fullName;
|
||||||
}
|
}
|
||||||
String shortName = extClsInfo.getAliasShortName();
|
String shortName = extClsInfo.getAliasShortName();
|
||||||
|
if (useCls.equals(extClsInfo)) {
|
||||||
|
return shortName;
|
||||||
|
}
|
||||||
if (extClsInfo.getPackage().equals("java.lang") && extClsInfo.getParentClass() == null) {
|
if (extClsInfo.getPackage().equals("java.lang") && extClsInfo.getParentClass() == null) {
|
||||||
return shortName;
|
return shortName;
|
||||||
}
|
}
|
||||||
@@ -599,6 +623,9 @@ public class ClassGen {
|
|||||||
if (extClsInfo.isInner()) {
|
if (extClsInfo.isInner()) {
|
||||||
return expandInnerClassName(useCls, extClsInfo);
|
return expandInnerClassName(useCls, extClsInfo);
|
||||||
}
|
}
|
||||||
|
if (searchCollision(cls.root(), useCls, extClsInfo)) {
|
||||||
|
return fullName;
|
||||||
|
}
|
||||||
if (isBothClassesInOneTopClass(useCls, extClsInfo)) {
|
if (isBothClassesInOneTopClass(useCls, extClsInfo)) {
|
||||||
return shortName;
|
return shortName;
|
||||||
}
|
}
|
||||||
@@ -606,9 +633,6 @@ public class ClassGen {
|
|||||||
if (extClsInfo.getPackage().equals(useCls.getPackage()) && !extClsInfo.isInner()) {
|
if (extClsInfo.getPackage().equals(useCls.getPackage()) && !extClsInfo.isInner()) {
|
||||||
return shortName;
|
return shortName;
|
||||||
}
|
}
|
||||||
if (searchCollision(cls.root(), useCls, extClsInfo)) {
|
|
||||||
return fullName;
|
|
||||||
}
|
|
||||||
// ignore classes from default package
|
// ignore classes from default package
|
||||||
if (extClsInfo.isDefaultPackage()) {
|
if (extClsInfo.isDefaultPackage()) {
|
||||||
return shortName;
|
return shortName;
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ public class ConditionGen extends InsnGen {
|
|||||||
super(insnGen.mgen, insnGen.fallback);
|
super(insnGen.mgen, insnGen.fallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
void add(ICodeWriter code, IfCondition condition) throws CodegenException {
|
public void add(ICodeWriter code, IfCondition condition) throws CodegenException {
|
||||||
add(code, new CondStack(), condition);
|
add(code, new CondStack(), condition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,12 +15,12 @@ import jadx.api.data.annotations.InsnCodeOffset;
|
|||||||
import jadx.api.data.annotations.VarDeclareRef;
|
import jadx.api.data.annotations.VarDeclareRef;
|
||||||
import jadx.api.data.annotations.VarRef;
|
import jadx.api.data.annotations.VarRef;
|
||||||
import jadx.api.plugins.input.data.MethodHandleType;
|
import jadx.api.plugins.input.data.MethodHandleType;
|
||||||
import jadx.core.deobf.NameMapper;
|
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
import jadx.core.dex.attributes.AType;
|
import jadx.core.dex.attributes.AType;
|
||||||
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
|
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
|
||||||
import jadx.core.dex.attributes.nodes.GenericInfoAttr;
|
import jadx.core.dex.attributes.nodes.GenericInfoAttr;
|
||||||
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
||||||
|
import jadx.core.dex.attributes.nodes.MethodReplaceAttr;
|
||||||
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
|
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
|
||||||
import jadx.core.dex.info.ClassInfo;
|
import jadx.core.dex.info.ClassInfo;
|
||||||
import jadx.core.dex.info.FieldInfo;
|
import jadx.core.dex.info.FieldInfo;
|
||||||
@@ -51,6 +51,7 @@ import jadx.core.dex.instructions.args.RegisterArg;
|
|||||||
import jadx.core.dex.instructions.args.SSAVar;
|
import jadx.core.dex.instructions.args.SSAVar;
|
||||||
import jadx.core.dex.instructions.mods.ConstructorInsn;
|
import jadx.core.dex.instructions.mods.ConstructorInsn;
|
||||||
import jadx.core.dex.instructions.mods.TernaryInsn;
|
import jadx.core.dex.instructions.mods.TernaryInsn;
|
||||||
|
import jadx.core.dex.nodes.BlockNode;
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
import jadx.core.dex.nodes.FieldNode;
|
import jadx.core.dex.nodes.FieldNode;
|
||||||
import jadx.core.dex.nodes.InsnNode;
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
@@ -112,7 +113,7 @@ public class InsnGen {
|
|||||||
}
|
}
|
||||||
code.add(mgen.getNameGen().useArg(reg));
|
code.add(mgen.getNameGen().useArg(reg));
|
||||||
} else if (arg.isLiteral()) {
|
} else if (arg.isLiteral()) {
|
||||||
code.add(lit((LiteralArg) arg));
|
addLiteralArg(code, (LiteralArg) arg, flags);
|
||||||
} else if (arg.isInsnWrap()) {
|
} else if (arg.isInsnWrap()) {
|
||||||
addWrappedArg(code, (InsnWrapArg) arg, flags);
|
addWrappedArg(code, (InsnWrapArg) arg, flags);
|
||||||
} else if (arg.isNamed()) {
|
} else if (arg.isNamed()) {
|
||||||
@@ -122,6 +123,15 @@ public class InsnGen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void addLiteralArg(ICodeWriter code, LiteralArg litArg, Set<Flags> flags) {
|
||||||
|
String literalStr = lit(litArg);
|
||||||
|
if (!flags.contains(Flags.BODY_ONLY_NOWRAP) && literalStr.startsWith("-")) {
|
||||||
|
code.add('(').add(literalStr).add(')');
|
||||||
|
} else {
|
||||||
|
code.add(literalStr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void addWrappedArg(ICodeWriter code, InsnWrapArg arg, Set<Flags> flags) throws CodegenException {
|
private void addWrappedArg(ICodeWriter code, InsnWrapArg arg, Set<Flags> flags) throws CodegenException {
|
||||||
InsnNode wrapInsn = arg.getWrapInsn();
|
InsnNode wrapInsn = arg.getWrapInsn();
|
||||||
if (wrapInsn.contains(AFlag.FORCE_ASSIGN_INLINE)) {
|
if (wrapInsn.contains(AFlag.FORCE_ASSIGN_INLINE)) {
|
||||||
@@ -164,7 +174,7 @@ public class InsnGen {
|
|||||||
|
|
||||||
private void instanceField(ICodeWriter code, FieldInfo field, InsnArg arg) throws CodegenException {
|
private void instanceField(ICodeWriter code, FieldInfo field, InsnArg arg) throws CodegenException {
|
||||||
ClassNode pCls = mth.getParentClass();
|
ClassNode pCls = mth.getParentClass();
|
||||||
FieldNode fieldNode = pCls.root().deepResolveField(field);
|
FieldNode fieldNode = pCls.root().resolveField(field);
|
||||||
if (fieldNode != null) {
|
if (fieldNode != null) {
|
||||||
FieldReplaceAttr replace = fieldNode.get(AType.FIELD_REPLACE);
|
FieldReplaceAttr replace = fieldNode.get(AType.FIELD_REPLACE);
|
||||||
if (replace != null) {
|
if (replace != null) {
|
||||||
@@ -202,7 +212,7 @@ public class InsnGen {
|
|||||||
}
|
}
|
||||||
code.add('.');
|
code.add('.');
|
||||||
}
|
}
|
||||||
FieldNode fieldNode = clsGen.getClassNode().root().deepResolveField(field);
|
FieldNode fieldNode = clsGen.getClassNode().root().resolveField(field);
|
||||||
if (fieldNode != null) {
|
if (fieldNode != null) {
|
||||||
code.attachAnnotation(fieldNode);
|
code.attachAnnotation(fieldNode);
|
||||||
}
|
}
|
||||||
@@ -509,7 +519,7 @@ public class InsnGen {
|
|||||||
code.add(' ');
|
code.add(' ');
|
||||||
code.add(ifInsn.getOp().getSymbol()).add(' ');
|
code.add(ifInsn.getOp().getSymbol()).add(' ');
|
||||||
addArg(code, insn.getArg(1));
|
addArg(code, insn.getArg(1));
|
||||||
code.add(") goto ").add(MethodGen.getLabelName(ifInsn.getTarget()));
|
code.add(") goto ").add(MethodGen.getLabelName(ifInsn));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case GOTO:
|
case GOTO:
|
||||||
@@ -530,13 +540,24 @@ public class InsnGen {
|
|||||||
code.add(") {");
|
code.add(") {");
|
||||||
code.incIndent();
|
code.incIndent();
|
||||||
int[] keys = sw.getKeys();
|
int[] keys = sw.getKeys();
|
||||||
int[] targets = sw.getTargets();
|
int size = keys.length;
|
||||||
for (int i = 0; i < keys.length; i++) {
|
BlockNode[] targetBlocks = sw.getTargetBlocks();
|
||||||
code.startLine("case ").add(Integer.toString(keys[i])).add(": goto ");
|
if (targetBlocks != null) {
|
||||||
code.add(MethodGen.getLabelName(targets[i])).add(';');
|
for (int i = 0; i < size; i++) {
|
||||||
|
code.startLine("case ").add(Integer.toString(keys[i])).add(": goto ");
|
||||||
|
code.add(MethodGen.getLabelName(targetBlocks[i])).add(';');
|
||||||
|
}
|
||||||
|
code.startLine("default: goto ");
|
||||||
|
code.add(MethodGen.getLabelName(sw.getDefTargetBlock())).add(';');
|
||||||
|
} else {
|
||||||
|
int[] targets = sw.getTargets();
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
code.startLine("case ").add(Integer.toString(keys[i])).add(": goto ");
|
||||||
|
code.add(MethodGen.getLabelName(targets[i])).add(';');
|
||||||
|
}
|
||||||
|
code.startLine("default: goto ");
|
||||||
|
code.add(MethodGen.getLabelName(sw.getDefaultCaseOffset())).add(';');
|
||||||
}
|
}
|
||||||
code.startLine("default: goto ");
|
|
||||||
code.add(MethodGen.getLabelName(sw.getDefaultCaseOffset())).add(';');
|
|
||||||
code.decIndent();
|
code.decIndent();
|
||||||
code.startLine('}');
|
code.startLine('}');
|
||||||
break;
|
break;
|
||||||
@@ -674,19 +695,27 @@ public class InsnGen {
|
|||||||
throw new JadxRuntimeException("Constructor 'self' invoke must be removed!");
|
throw new JadxRuntimeException("Constructor 'self' invoke must be removed!");
|
||||||
}
|
}
|
||||||
MethodNode callMth = mth.root().resolveMethod(insn.getCallMth());
|
MethodNode callMth = mth.root().resolveMethod(insn.getCallMth());
|
||||||
|
MethodNode refMth = callMth;
|
||||||
|
if (callMth != null) {
|
||||||
|
MethodReplaceAttr replaceAttr = callMth.get(AType.METHOD_REPLACE);
|
||||||
|
if (replaceAttr != null) {
|
||||||
|
refMth = replaceAttr.getReplaceMth();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (insn.isSuper()) {
|
if (insn.isSuper()) {
|
||||||
code.attachAnnotation(callMth);
|
code.attachAnnotation(refMth);
|
||||||
code.add("super");
|
code.add("super");
|
||||||
} else if (insn.isThis()) {
|
} else if (insn.isThis()) {
|
||||||
code.attachAnnotation(callMth);
|
code.attachAnnotation(refMth);
|
||||||
code.add("this");
|
code.add("this");
|
||||||
} else {
|
} else {
|
||||||
code.add("new ");
|
code.add("new ");
|
||||||
if (callMth == null || callMth.contains(AFlag.DONT_GENERATE)) {
|
if (refMth == null || refMth.contains(AFlag.DONT_GENERATE)) {
|
||||||
// use class reference if constructor method is missing (default constructor)
|
// use class reference if constructor method is missing (default constructor)
|
||||||
code.attachAnnotation(mth.root().resolveClass(insn.getCallMth().getDeclClass()));
|
code.attachAnnotation(mth.root().resolveClass(insn.getCallMth().getDeclClass()));
|
||||||
} else {
|
} else {
|
||||||
code.attachAnnotation(callMth);
|
code.attachAnnotation(refMth);
|
||||||
}
|
}
|
||||||
mgen.getClassGen().addClsName(code, insn.getClassType());
|
mgen.getClassGen().addClsName(code, insn.getClassType());
|
||||||
GenericInfoAttr genericInfoAttr = insn.get(AType.GENERIC_INFO);
|
GenericInfoAttr genericInfoAttr = insn.get(AType.GENERIC_INFO);
|
||||||
@@ -711,20 +740,13 @@ public class InsnGen {
|
|||||||
|
|
||||||
private void inlineAnonymousConstructor(ICodeWriter code, ClassNode cls, ConstructorInsn insn) throws CodegenException {
|
private void inlineAnonymousConstructor(ICodeWriter code, ClassNode cls, ConstructorInsn insn) throws CodegenException {
|
||||||
if (this.mth.getParentClass() == cls) {
|
if (this.mth.getParentClass() == cls) {
|
||||||
cls.remove(AFlag.ANONYMOUS_CLASS);
|
cls.remove(AType.ANONYMOUS_CLASS);
|
||||||
cls.remove(AFlag.DONT_GENERATE);
|
cls.remove(AFlag.DONT_GENERATE);
|
||||||
mth.getParentClass().getTopParentClass().add(AFlag.RESTART_CODEGEN);
|
mth.getParentClass().getTopParentClass().add(AFlag.RESTART_CODEGEN);
|
||||||
throw new CodegenException("Anonymous inner class unlimited recursion detected."
|
throw new CodegenException("Anonymous inner class unlimited recursion detected."
|
||||||
+ " Convert class to inner: " + cls.getClassInfo().getFullName());
|
+ " Convert class to inner: " + cls.getClassInfo().getFullName());
|
||||||
}
|
}
|
||||||
|
ArgType parent = cls.get(AType.ANONYMOUS_CLASS).getBaseType();
|
||||||
cls.add(AFlag.DONT_GENERATE);
|
|
||||||
ArgType parent;
|
|
||||||
if (cls.getInterfaces().size() == 1) {
|
|
||||||
parent = cls.getInterfaces().get(0);
|
|
||||||
} else {
|
|
||||||
parent = cls.getSuperClass();
|
|
||||||
}
|
|
||||||
// hide empty anonymous constructors
|
// hide empty anonymous constructors
|
||||||
for (MethodNode ctor : cls.getMethods()) {
|
for (MethodNode ctor : cls.getMethods()) {
|
||||||
if (ctor.contains(AFlag.ANONYMOUS_CONSTRUCTOR)
|
if (ctor.contains(AFlag.ANONYMOUS_CONSTRUCTOR)
|
||||||
@@ -732,14 +754,22 @@ public class InsnGen {
|
|||||||
ctor.add(AFlag.DONT_GENERATE);
|
ctor.add(AFlag.DONT_GENERATE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
code.add("new ");
|
code.add("new ");
|
||||||
if (parent == null) {
|
useClass(code, parent);
|
||||||
code.add("Object");
|
|
||||||
} else {
|
|
||||||
useClass(code, parent);
|
|
||||||
}
|
|
||||||
MethodNode callMth = mth.root().resolveMethod(insn.getCallMth());
|
MethodNode callMth = mth.root().resolveMethod(insn.getCallMth());
|
||||||
|
if (callMth != null) {
|
||||||
|
// copy var names
|
||||||
|
List<RegisterArg> mthArgs = callMth.getArgRegs();
|
||||||
|
int argsCount = Math.min(insn.getArgsCount(), mthArgs.size());
|
||||||
|
for (int i = 0; i < argsCount; i++) {
|
||||||
|
InsnArg arg = insn.getArg(i);
|
||||||
|
if (arg.isRegister()) {
|
||||||
|
RegisterArg mthArg = mthArgs.get(i);
|
||||||
|
RegisterArg insnArg = (RegisterArg) arg;
|
||||||
|
mthArg.getSVar().setCodeVar(insnArg.getSVar().getCodeVar());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
generateMethodArguments(code, insn, 0, callMth);
|
generateMethodArguments(code, insn, 0, callMth);
|
||||||
code.add(' ');
|
code.add(' ');
|
||||||
|
|
||||||
@@ -755,7 +785,7 @@ public class InsnGen {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
MethodInfo callMth = insn.getCallMth();
|
MethodInfo callMth = insn.getCallMth();
|
||||||
MethodNode callMthNode = mth.root().deepResolveMethod(callMth);
|
MethodNode callMthNode = mth.root().resolveMethod(callMth);
|
||||||
|
|
||||||
int k = 0;
|
int k = 0;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
@@ -763,8 +793,7 @@ public class InsnGen {
|
|||||||
case VIRTUAL:
|
case VIRTUAL:
|
||||||
case INTERFACE:
|
case INTERFACE:
|
||||||
InsnArg arg = insn.getArg(0);
|
InsnArg arg = insn.getArg(0);
|
||||||
// FIXME: add 'this' for equals methods in scope
|
if (needInvokeArg(arg)) {
|
||||||
if (!arg.isThis()) {
|
|
||||||
addArgDot(code, arg);
|
addArgDot(code, arg);
|
||||||
}
|
}
|
||||||
k++;
|
k++;
|
||||||
@@ -799,6 +828,20 @@ public class InsnGen {
|
|||||||
generateMethodArguments(code, insn, k, callMthNode);
|
generateMethodArguments(code, insn, k, callMthNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: add 'this' for equals methods in scope
|
||||||
|
private boolean needInvokeArg(InsnArg arg) {
|
||||||
|
if (arg.isAnyThis()) {
|
||||||
|
if (arg.isThis()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ClassNode clsNode = mth.root().resolveClass(arg.getType());
|
||||||
|
if (clsNode != null && clsNode.contains(AFlag.DONT_GENERATE)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private void makeInvokeLambda(ICodeWriter code, InvokeCustomNode customNode) throws CodegenException {
|
private void makeInvokeLambda(ICodeWriter code, InvokeCustomNode customNode) throws CodegenException {
|
||||||
if (customNode.isUseRef()) {
|
if (customNode.isUseRef()) {
|
||||||
makeRefLambda(code, customNode);
|
makeRefLambda(code, customNode);
|
||||||
@@ -1016,7 +1059,6 @@ public class InsnGen {
|
|||||||
} else {
|
} else {
|
||||||
condGen.wrap(code, insn.getCondition());
|
condGen.wrap(code, insn.getCondition());
|
||||||
code.add(" ? ");
|
code.add(" ? ");
|
||||||
addCastIfNeeded(code, first, second);
|
|
||||||
addArg(code, first, false);
|
addArg(code, first, false);
|
||||||
code.add(" : ");
|
code.add(" : ");
|
||||||
addArg(code, second, false);
|
addArg(code, second, false);
|
||||||
@@ -1026,33 +1068,6 @@ public class InsnGen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addCastIfNeeded(ICodeWriter code, InsnArg first, InsnArg second) {
|
|
||||||
if (first.isLiteral() && second.isLiteral()) {
|
|
||||||
if (first.getType() == ArgType.BYTE) {
|
|
||||||
long lit1 = ((LiteralArg) first).getLiteral();
|
|
||||||
long lit2 = ((LiteralArg) second).getLiteral();
|
|
||||||
if (lit1 != Byte.MAX_VALUE && lit1 != Byte.MIN_VALUE
|
|
||||||
&& lit2 != Byte.MAX_VALUE && lit2 != Byte.MIN_VALUE) {
|
|
||||||
code.add("(byte) ");
|
|
||||||
}
|
|
||||||
} else if (first.getType() == ArgType.SHORT) {
|
|
||||||
long lit1 = ((LiteralArg) first).getLiteral();
|
|
||||||
long lit2 = ((LiteralArg) second).getLiteral();
|
|
||||||
if (lit1 != Short.MAX_VALUE && lit1 != Short.MIN_VALUE
|
|
||||||
&& lit2 != Short.MAX_VALUE && lit2 != Short.MIN_VALUE) {
|
|
||||||
code.add("(short) ");
|
|
||||||
}
|
|
||||||
} else if (first.getType() == ArgType.CHAR) {
|
|
||||||
long lit1 = ((LiteralArg) first).getLiteral();
|
|
||||||
long lit2 = ((LiteralArg) second).getLiteral();
|
|
||||||
if (!NameMapper.isPrintableChar((char) (lit1))
|
|
||||||
&& !NameMapper.isPrintableChar((char) (lit2))) {
|
|
||||||
code.add("(char) ");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void makeArith(ArithNode insn, ICodeWriter code, Set<Flags> state) throws CodegenException {
|
private void makeArith(ArithNode insn, ICodeWriter code, Set<Flags> state) throws CodegenException {
|
||||||
if (insn.contains(AFlag.ARITH_ONEARG)) {
|
if (insn.contains(AFlag.ARITH_ONEARG)) {
|
||||||
makeArithOneArg(insn, code);
|
makeArithOneArg(insn, code);
|
||||||
|
|||||||
@@ -6,17 +6,19 @@ import java.util.List;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import jadx.api.CommentsLevel;
|
import jadx.api.CommentsLevel;
|
||||||
import jadx.api.ICodeWriter;
|
import jadx.api.ICodeWriter;
|
||||||
|
import jadx.api.JadxArgs;
|
||||||
import jadx.api.data.annotations.InsnCodeOffset;
|
import jadx.api.data.annotations.InsnCodeOffset;
|
||||||
import jadx.api.data.annotations.VarDeclareRef;
|
import jadx.api.data.annotations.VarDeclareRef;
|
||||||
import jadx.api.plugins.input.data.AccessFlags;
|
import jadx.api.plugins.input.data.AccessFlags;
|
||||||
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||||
import jadx.api.plugins.input.data.attributes.JadxAttrType;
|
import jadx.api.plugins.input.data.attributes.JadxAttrType;
|
||||||
import jadx.api.plugins.input.data.attributes.types.MethodParamsAttr;
|
import jadx.api.plugins.input.data.attributes.types.AnnotationMethodParamsAttr;
|
||||||
import jadx.core.Consts;
|
import jadx.core.Consts;
|
||||||
import jadx.core.Jadx;
|
import jadx.core.Jadx;
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
@@ -32,13 +34,14 @@ import jadx.core.dex.instructions.args.ArgType;
|
|||||||
import jadx.core.dex.instructions.args.CodeVar;
|
import jadx.core.dex.instructions.args.CodeVar;
|
||||||
import jadx.core.dex.instructions.args.RegisterArg;
|
import jadx.core.dex.instructions.args.RegisterArg;
|
||||||
import jadx.core.dex.instructions.args.SSAVar;
|
import jadx.core.dex.instructions.args.SSAVar;
|
||||||
|
import jadx.core.dex.nodes.BlockNode;
|
||||||
import jadx.core.dex.nodes.InsnNode;
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
import jadx.core.dex.trycatch.CatchAttr;
|
import jadx.core.dex.trycatch.CatchAttr;
|
||||||
|
import jadx.core.dex.trycatch.ExceptionHandler;
|
||||||
import jadx.core.dex.visitors.DepthTraversal;
|
import jadx.core.dex.visitors.DepthTraversal;
|
||||||
import jadx.core.dex.visitors.IDexTreeVisitor;
|
import jadx.core.dex.visitors.IDexTreeVisitor;
|
||||||
import jadx.core.utils.CodeGenUtils;
|
import jadx.core.utils.CodeGenUtils;
|
||||||
import jadx.core.utils.InsnUtils;
|
|
||||||
import jadx.core.utils.Utils;
|
import jadx.core.utils.Utils;
|
||||||
import jadx.core.utils.exceptions.CodegenException;
|
import jadx.core.utils.exceptions.CodegenException;
|
||||||
import jadx.core.utils.exceptions.JadxOverflowException;
|
import jadx.core.utils.exceptions.JadxOverflowException;
|
||||||
@@ -111,7 +114,18 @@ public class MethodGen {
|
|||||||
CodeGenUtils.addRenamedComment(code, mth, mth.getName());
|
CodeGenUtils.addRenamedComment(code, mth, mth.getName());
|
||||||
}
|
}
|
||||||
if (mth.contains(AFlag.INCONSISTENT_CODE) && mth.checkCommentsLevel(CommentsLevel.ERROR)) {
|
if (mth.contains(AFlag.INCONSISTENT_CODE) && mth.checkCommentsLevel(CommentsLevel.ERROR)) {
|
||||||
code.startLine("/* Code decompiled incorrectly, please refer to instructions dump */");
|
code.startLine("/*");
|
||||||
|
code.incIndent();
|
||||||
|
code.startLine("Code decompiled incorrectly, please refer to instructions dump.");
|
||||||
|
if (!mth.root().getArgs().isShowInconsistentCode()) {
|
||||||
|
if (code.isMetadataSupported()) {
|
||||||
|
code.startLine("To view partially-correct code enable 'Show inconsistent code' option in preferences");
|
||||||
|
} else {
|
||||||
|
code.startLine("To view partially-correct add '--show-bad-code' argument");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
code.decIndent();
|
||||||
|
code.startLine("*/");
|
||||||
}
|
}
|
||||||
|
|
||||||
code.startLineWithNum(mth.getSourceLine());
|
code.startLineWithNum(mth.getSourceLine());
|
||||||
@@ -169,7 +183,7 @@ public class MethodGen {
|
|||||||
if (overrideAttr == null) {
|
if (overrideAttr == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!overrideAttr.isAtBaseMth()) {
|
if (!overrideAttr.getBaseMethods().contains(mth)) {
|
||||||
code.startLine("@Override");
|
code.startLine("@Override");
|
||||||
if (mth.checkCommentsLevel(CommentsLevel.INFO)) {
|
if (mth.checkCommentsLevel(CommentsLevel.INFO)) {
|
||||||
code.add(" // ");
|
code.add(" // ");
|
||||||
@@ -184,7 +198,7 @@ public class MethodGen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void addMethodArguments(ICodeWriter code, List<RegisterArg> args) {
|
private void addMethodArguments(ICodeWriter code, List<RegisterArg> args) {
|
||||||
MethodParamsAttr paramsAnnotation = mth.get(JadxAttrType.ANNOTATION_MTH_PARAMETERS);
|
AnnotationMethodParamsAttr paramsAnnotation = mth.get(JadxAttrType.ANNOTATION_MTH_PARAMETERS);
|
||||||
int i = 0;
|
int i = 0;
|
||||||
Iterator<RegisterArg> it = args.iterator();
|
Iterator<RegisterArg> it = args.iterator();
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
@@ -227,10 +241,11 @@ public class MethodGen {
|
|||||||
classGen.useType(code, argType);
|
classGen.useType(code, argType);
|
||||||
}
|
}
|
||||||
code.add(' ');
|
code.add(' ');
|
||||||
if (code.isMetadataSupported() && ssaVar != null) {
|
String varName = nameGen.assignArg(var);
|
||||||
|
if (code.isMetadataSupported() && ssaVar != null /* for fallback mode */) {
|
||||||
code.attachDefinition(VarDeclareRef.get(mth, var));
|
code.attachDefinition(VarDeclareRef.get(mth, var));
|
||||||
}
|
}
|
||||||
code.add(nameGen.assignArg(var));
|
code.add(varName);
|
||||||
|
|
||||||
i++;
|
i++;
|
||||||
if (it.hasNext()) {
|
if (it.hasNext()) {
|
||||||
@@ -240,12 +255,28 @@ public class MethodGen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void addInstructions(ICodeWriter code) throws CodegenException {
|
public void addInstructions(ICodeWriter code) throws CodegenException {
|
||||||
if (mth.root().getArgs().isFallbackMode()) {
|
JadxArgs args = mth.root().getArgs();
|
||||||
addFallbackMethodCode(code, FALLBACK_MODE);
|
switch (args.getDecompilationMode()) {
|
||||||
} else if (classGen.isFallbackMode()) {
|
case AUTO:
|
||||||
dumpInstructions(code);
|
if (classGen.isFallbackMode()) {
|
||||||
} else {
|
// TODO: try simple mode first
|
||||||
addRegionInsns(code);
|
dumpInstructions(code);
|
||||||
|
} else {
|
||||||
|
addRegionInsns(code);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RESTRUCTURE:
|
||||||
|
addRegionInsns(code);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SIMPLE:
|
||||||
|
addSimpleMethodCode(code);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FALLBACK:
|
||||||
|
addFallbackMethodCode(code, FALLBACK_MODE);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,6 +298,59 @@ public class MethodGen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void addSimpleMethodCode(ICodeWriter code) {
|
||||||
|
if (mth.getBasicBlocks() == null) {
|
||||||
|
code.startLine("// Blocks not ready for simple mode, using fallback");
|
||||||
|
addFallbackMethodCode(code, FALLBACK_MODE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
JadxArgs args = mth.root().getArgs();
|
||||||
|
ICodeWriter tmpCode = args.getCodeWriterProvider().apply(args);
|
||||||
|
try {
|
||||||
|
tmpCode.setIndent(code.getIndent());
|
||||||
|
generateSimpleCode(tmpCode);
|
||||||
|
code.add(tmpCode);
|
||||||
|
} catch (Exception e) {
|
||||||
|
mth.addError("Simple mode code generation failed", e);
|
||||||
|
CodeGenUtils.addError(code, "Simple mode code generation failed", e);
|
||||||
|
dumpInstructions(code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void generateSimpleCode(ICodeWriter code) throws CodegenException {
|
||||||
|
SimpleModeHelper helper = new SimpleModeHelper(mth);
|
||||||
|
List<BlockNode> blocks = helper.prepareBlocks();
|
||||||
|
InsnGen insnGen = new InsnGen(this, true);
|
||||||
|
for (BlockNode block : blocks) {
|
||||||
|
if (block.contains(AFlag.DONT_GENERATE)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (helper.isNeedStartLabel(block)) {
|
||||||
|
code.decIndent();
|
||||||
|
code.startLine(getLabelName(block)).add(':');
|
||||||
|
code.incIndent();
|
||||||
|
}
|
||||||
|
for (InsnNode insn : block.getInstructions()) {
|
||||||
|
if (!insn.contains(AFlag.DONT_GENERATE)) {
|
||||||
|
if (insn.getResult() != null) {
|
||||||
|
CodeVar codeVar = insn.getResult().getSVar().getCodeVar();
|
||||||
|
if (!codeVar.isDeclared()) {
|
||||||
|
insn.add(AFlag.DECLARE_VAR);
|
||||||
|
codeVar.setDeclared(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
InsnCodeOffset.attach(code, insn);
|
||||||
|
insnGen.makeInsn(insn, code);
|
||||||
|
addCatchComment(code, insn, false);
|
||||||
|
CodeGenUtils.addCodeComments(code, mth, insn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (helper.isNeedEndGoto(block)) {
|
||||||
|
code.startLine("goto ").add(getLabelName(block.getSuccessors().get(0)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void dumpInstructions(ICodeWriter code) {
|
public void dumpInstructions(ICodeWriter code) {
|
||||||
if (mth.checkCommentsLevel(CommentsLevel.ERROR)) {
|
if (mth.checkCommentsLevel(CommentsLevel.ERROR)) {
|
||||||
code.startLine("/*");
|
code.startLine("/*");
|
||||||
@@ -314,7 +398,14 @@ public class MethodGen {
|
|||||||
.filter(insn -> insn.getType() != InsnType.NOP)
|
.filter(insn -> insn.getType() != InsnType.NOP)
|
||||||
.count();
|
.count();
|
||||||
if (insnCountEstimate > 100) {
|
if (insnCountEstimate > 100) {
|
||||||
code.startLine("// Method dump skipped, instructions count: " + insnArr.length);
|
code.incIndent();
|
||||||
|
code.startLine("Method dump skipped, instructions count: " + insnArr.length);
|
||||||
|
if (code.isMetadataSupported()) {
|
||||||
|
code.startLine("To view this dump change 'Code comments level' option to 'DEBUG'");
|
||||||
|
} else {
|
||||||
|
code.startLine("To view this dump add '--comments-level debug' option");
|
||||||
|
}
|
||||||
|
code.decIndent();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -334,60 +425,81 @@ public class MethodGen {
|
|||||||
|
|
||||||
public static void addFallbackInsns(ICodeWriter code, MethodNode mth, InsnNode[] insnArr, FallbackOption option) {
|
public static void addFallbackInsns(ICodeWriter code, MethodNode mth, InsnNode[] insnArr, FallbackOption option) {
|
||||||
int startIndent = code.getIndent();
|
int startIndent = code.getIndent();
|
||||||
InsnGen insnGen = new InsnGen(getFallbackMethodGen(mth), true);
|
MethodGen methodGen = getFallbackMethodGen(mth);
|
||||||
|
InsnGen insnGen = new InsnGen(methodGen, true);
|
||||||
InsnNode prevInsn = null;
|
InsnNode prevInsn = null;
|
||||||
for (InsnNode insn : insnArr) {
|
for (InsnNode insn : insnArr) {
|
||||||
if (insn == null) {
|
if (insn == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (insn.contains(AType.JADX_ERROR)) {
|
methodGen.dumpInsn(code, insnGen, option, startIndent, prevInsn, insn);
|
||||||
for (JadxError error : insn.getAll(AType.JADX_ERROR)) {
|
prevInsn = insn;
|
||||||
code.startLine("// ").add(error.getError());
|
}
|
||||||
}
|
}
|
||||||
continue;
|
|
||||||
|
private boolean dumpInsn(ICodeWriter code, InsnGen insnGen, FallbackOption option, int startIndent,
|
||||||
|
@Nullable InsnNode prevInsn, InsnNode insn) {
|
||||||
|
if (insn.contains(AType.JADX_ERROR)) {
|
||||||
|
for (JadxError error : insn.getAll(AType.JADX_ERROR)) {
|
||||||
|
code.startLine("// ").add(error.getError());
|
||||||
}
|
}
|
||||||
if (option != BLOCK_DUMP && needLabel(insn, prevInsn)) {
|
return true;
|
||||||
|
}
|
||||||
|
if (option != BLOCK_DUMP && needLabel(insn, prevInsn)) {
|
||||||
|
code.decIndent();
|
||||||
|
code.startLine(getLabelName(insn.getOffset()) + ':');
|
||||||
|
code.incIndent();
|
||||||
|
}
|
||||||
|
if (insn.getType() == InsnType.NOP) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
boolean escapeComment = isCommentEscapeNeeded(insn, option);
|
||||||
|
if (escapeComment) {
|
||||||
code.decIndent();
|
code.decIndent();
|
||||||
code.startLine(getLabelName(insn.getOffset()) + ':');
|
code.startLine("*/");
|
||||||
|
code.startLine("// ");
|
||||||
|
} else {
|
||||||
|
code.startLineWithNum(insn.getSourceLine());
|
||||||
|
}
|
||||||
|
InsnCodeOffset.attach(code, insn);
|
||||||
|
RegisterArg resArg = insn.getResult();
|
||||||
|
if (resArg != null) {
|
||||||
|
ArgType varType = resArg.getInitType();
|
||||||
|
if (varType.isTypeKnown()) {
|
||||||
|
code.add(varType.toString()).add(' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
insnGen.makeInsn(insn, code, InsnGen.Flags.INLINE);
|
||||||
|
if (escapeComment) {
|
||||||
|
code.startLine("/*");
|
||||||
code.incIndent();
|
code.incIndent();
|
||||||
}
|
}
|
||||||
if (insn.getType() == InsnType.NOP) {
|
addCatchComment(code, insn, true);
|
||||||
continue;
|
CodeGenUtils.addCodeComments(code, mth, insn);
|
||||||
}
|
} catch (Exception e) {
|
||||||
try {
|
LOG.debug("Error generate fallback instruction: ", e.getCause());
|
||||||
boolean escapeComment = isCommentEscapeNeeded(insn, option);
|
code.setIndent(startIndent);
|
||||||
if (escapeComment) {
|
code.startLine("// error: " + insn);
|
||||||
code.decIndent();
|
}
|
||||||
code.startLine("*/");
|
return false;
|
||||||
code.startLine("// ");
|
}
|
||||||
} else {
|
|
||||||
code.startLineWithNum(insn.getSourceLine());
|
|
||||||
}
|
|
||||||
InsnCodeOffset.attach(code, insn);
|
|
||||||
RegisterArg resArg = insn.getResult();
|
|
||||||
if (resArg != null) {
|
|
||||||
ArgType varType = resArg.getInitType();
|
|
||||||
if (varType.isTypeKnown()) {
|
|
||||||
code.add(varType.toString()).add(' ');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
insnGen.makeInsn(insn, code, InsnGen.Flags.INLINE);
|
|
||||||
if (escapeComment) {
|
|
||||||
code.startLine("/*");
|
|
||||||
code.incIndent();
|
|
||||||
}
|
|
||||||
|
|
||||||
CatchAttr catchAttr = insn.get(AType.EXC_CATCH);
|
private void addCatchComment(ICodeWriter code, InsnNode insn, boolean raw) {
|
||||||
if (catchAttr != null) {
|
CatchAttr catchAttr = insn.get(AType.EXC_CATCH);
|
||||||
code.add(" // " + catchAttr);
|
if (catchAttr == null) {
|
||||||
}
|
return;
|
||||||
CodeGenUtils.addCodeComments(code, mth, insn);
|
}
|
||||||
} catch (Exception e) {
|
code.add(" // Catch:");
|
||||||
LOG.debug("Error generate fallback instruction: ", e.getCause());
|
for (ExceptionHandler handler : catchAttr.getHandlers()) {
|
||||||
code.setIndent(startIndent);
|
code.add(' ');
|
||||||
code.startLine("// error: " + insn);
|
classGen.useClass(code, handler.getArgType());
|
||||||
|
code.add(" -> ");
|
||||||
|
if (raw) {
|
||||||
|
code.add(getLabelName(handler.getHandlerOffset()));
|
||||||
|
} else {
|
||||||
|
code.add(getLabelName(handler.getHandlerBlock()));
|
||||||
}
|
}
|
||||||
prevInsn = insn;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -430,7 +542,22 @@ public class MethodGen {
|
|||||||
return new MethodGen(clsGen, mth);
|
return new MethodGen(clsGen, mth);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getLabelName(BlockNode block) {
|
||||||
|
return String.format("L%d", block.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getLabelName(IfNode insn) {
|
||||||
|
BlockNode thenBlock = insn.getThenBlock();
|
||||||
|
if (thenBlock != null) {
|
||||||
|
return getLabelName(thenBlock);
|
||||||
|
}
|
||||||
|
return getLabelName(insn.getTarget());
|
||||||
|
}
|
||||||
|
|
||||||
public static String getLabelName(int offset) {
|
public static String getLabelName(int offset) {
|
||||||
return "L_" + InsnUtils.formatOffset(offset);
|
if (offset < 0) {
|
||||||
|
return String.format("LB_%x", -offset);
|
||||||
|
}
|
||||||
|
return String.format("L%x", offset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -162,8 +162,7 @@ public class NameGen {
|
|||||||
InsnNode assignInsn = assignArg.getParentInsn();
|
InsnNode assignInsn = assignArg.getParentInsn();
|
||||||
if (assignInsn != null) {
|
if (assignInsn != null) {
|
||||||
String name = makeNameFromInsn(assignInsn);
|
String name = makeNameFromInsn(assignInsn);
|
||||||
if (name != null && !NameMapper.isReserved(name)) {
|
if (name != null && NameMapper.isValidAndPrintable(name)) {
|
||||||
assignArg.setName(name);
|
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -202,7 +201,11 @@ public class NameGen {
|
|||||||
return vName;
|
return vName;
|
||||||
}
|
}
|
||||||
if (shortName != null) {
|
if (shortName != null) {
|
||||||
return StringUtils.escape(shortName.toLowerCase());
|
String lower = StringUtils.escape(shortName.toLowerCase());
|
||||||
|
if (shortName.equals(lower)) {
|
||||||
|
return lower + "Var";
|
||||||
|
}
|
||||||
|
return lower;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return StringUtils.escape(type.toString());
|
return StringUtils.escape(type.toString());
|
||||||
@@ -265,13 +268,17 @@ public class NameGen {
|
|||||||
|
|
||||||
private String makeNameFromInvoke(MethodInfo callMth) {
|
private String makeNameFromInvoke(MethodInfo callMth) {
|
||||||
String name = callMth.getName();
|
String name = callMth.getName();
|
||||||
|
ArgType declType = callMth.getDeclClass().getType();
|
||||||
|
if ("getInstance".equals(name)) {
|
||||||
|
// e.g. Cipher.getInstance
|
||||||
|
return makeNameForType(declType);
|
||||||
|
}
|
||||||
if (name.startsWith("get") || name.startsWith("set")) {
|
if (name.startsWith("get") || name.startsWith("set")) {
|
||||||
return fromName(name.substring(3));
|
return fromName(name.substring(3));
|
||||||
}
|
}
|
||||||
if ("iterator".equals(name)) {
|
if ("iterator".equals(name)) {
|
||||||
return "it";
|
return "it";
|
||||||
}
|
}
|
||||||
ArgType declType = callMth.getDeclClass().getType();
|
|
||||||
if ("toString".equals(name)) {
|
if ("toString".equals(name)) {
|
||||||
return makeNameForType(declType);
|
return makeNameForType(declType);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ public class RegionGen extends InsnGen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void makeLoop(LoopRegion region, ICodeWriter code) throws CodegenException {
|
public void makeLoop(LoopRegion region, ICodeWriter code) throws CodegenException {
|
||||||
code.startLineWithNum(region.getConditionSourceLine());
|
code.startLineWithNum(region.getSourceLine());
|
||||||
LoopLabelAttr labelAttr = region.getInfo().getStart().get(AType.LOOP_LABEL);
|
LoopLabelAttr labelAttr = region.getInfo().getStart().get(AType.LOOP_LABEL);
|
||||||
if (labelAttr != null) {
|
if (labelAttr != null) {
|
||||||
code.add(mgen.getNameGen().getLoopLabel(labelAttr)).add(": ");
|
code.add(mgen.getNameGen().getLoopLabel(labelAttr)).add(": ");
|
||||||
@@ -213,7 +213,7 @@ public class RegionGen extends InsnGen {
|
|||||||
code.add("do {");
|
code.add("do {");
|
||||||
CodeGenUtils.addCodeComments(code, mth, condInsn);
|
CodeGenUtils.addCodeComments(code, mth, condInsn);
|
||||||
makeRegionIndent(code, region.getBody());
|
makeRegionIndent(code, region.getBody());
|
||||||
code.startLineWithNum(region.getConditionSourceLine());
|
code.startLineWithNum(region.getSourceLine());
|
||||||
code.add("} while (");
|
code.add("} while (");
|
||||||
conditionGen.add(code, condition);
|
conditionGen.add(code, condition);
|
||||||
code.add(");");
|
code.add(");");
|
||||||
|
|||||||
@@ -0,0 +1,149 @@
|
|||||||
|
package jadx.core.codegen;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.BitSet;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import jadx.core.dex.attributes.AFlag;
|
||||||
|
import jadx.core.dex.attributes.AType;
|
||||||
|
import jadx.core.dex.instructions.IfNode;
|
||||||
|
import jadx.core.dex.instructions.TargetInsnNode;
|
||||||
|
import jadx.core.dex.nodes.BlockNode;
|
||||||
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
|
import jadx.core.dex.trycatch.ExceptionHandler;
|
||||||
|
import jadx.core.dex.visitors.blocks.BlockProcessor;
|
||||||
|
import jadx.core.dex.visitors.blocks.BlockSplitter;
|
||||||
|
import jadx.core.utils.BlockUtils;
|
||||||
|
|
||||||
|
public class SimpleModeHelper {
|
||||||
|
|
||||||
|
private final MethodNode mth;
|
||||||
|
|
||||||
|
private final BitSet startLabel;
|
||||||
|
private final BitSet endGoto;
|
||||||
|
|
||||||
|
public SimpleModeHelper(MethodNode mth) {
|
||||||
|
this.mth = mth;
|
||||||
|
this.startLabel = BlockUtils.newBlocksBitSet(mth);
|
||||||
|
this.endGoto = BlockUtils.newBlocksBitSet(mth);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<BlockNode> prepareBlocks() {
|
||||||
|
removeEmptyBlocks();
|
||||||
|
List<BlockNode> blocksList = getSortedBlocks();
|
||||||
|
blocksList.removeIf(b -> b.equals(mth.getEnterBlock()) || b.equals(mth.getExitBlock()));
|
||||||
|
unbindExceptionHandlers();
|
||||||
|
if (blocksList.isEmpty()) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
@Nullable
|
||||||
|
BlockNode prev = null;
|
||||||
|
int blocksCount = blocksList.size();
|
||||||
|
for (int i = 0; i < blocksCount; i++) {
|
||||||
|
BlockNode block = blocksList.get(i);
|
||||||
|
BlockNode nextBlock = i + 1 == blocksCount ? null : blocksList.get(i + 1);
|
||||||
|
List<BlockNode> preds = block.getPredecessors();
|
||||||
|
int predsCount = preds.size();
|
||||||
|
if (predsCount > 1) {
|
||||||
|
startLabel.set(block.getId());
|
||||||
|
} else if (predsCount == 1 && prev != null) {
|
||||||
|
if (!prev.equals(preds.get(0))) {
|
||||||
|
startLabel.set(block.getId());
|
||||||
|
if (prev.getSuccessors().size() == 1 && !mth.isPreExitBlocks(prev)) {
|
||||||
|
endGoto.set(prev.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
InsnNode lastInsn = BlockUtils.getLastInsn(block);
|
||||||
|
if (lastInsn instanceof TargetInsnNode) {
|
||||||
|
processTargetInsn(block, lastInsn, nextBlock);
|
||||||
|
}
|
||||||
|
if (block.contains(AType.EXC_HANDLER)) {
|
||||||
|
startLabel.set(block.getId());
|
||||||
|
}
|
||||||
|
if (nextBlock == null && !mth.isPreExitBlocks(block)) {
|
||||||
|
endGoto.set(block.getId());
|
||||||
|
}
|
||||||
|
prev = block;
|
||||||
|
}
|
||||||
|
if (mth.isVoidReturn()) {
|
||||||
|
int last = blocksList.size() - 1;
|
||||||
|
if (blocksList.get(last).contains(AFlag.RETURN)) {
|
||||||
|
// remove trailing return
|
||||||
|
blocksList.remove(last);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return blocksList;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeEmptyBlocks() {
|
||||||
|
for (BlockNode block : mth.getBasicBlocks()) {
|
||||||
|
if (block.getInstructions().isEmpty()
|
||||||
|
&& block.getPredecessors().size() > 0
|
||||||
|
&& block.getSuccessors().size() == 1) {
|
||||||
|
BlockNode successor = block.getSuccessors().get(0);
|
||||||
|
List<BlockNode> predecessors = block.getPredecessors();
|
||||||
|
BlockSplitter.removeConnection(block, successor);
|
||||||
|
if (predecessors.size() == 1) {
|
||||||
|
BlockSplitter.replaceConnection(predecessors.get(0), block, successor);
|
||||||
|
} else {
|
||||||
|
for (BlockNode pred : new ArrayList<>(predecessors)) {
|
||||||
|
BlockSplitter.replaceConnection(pred, block, successor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
block.add(AFlag.REMOVE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BlockProcessor.removeMarkedBlocks(mth);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void unbindExceptionHandlers() {
|
||||||
|
if (mth.isNoExceptionHandlers()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (ExceptionHandler handler : mth.getExceptionHandlers()) {
|
||||||
|
BlockNode handlerBlock = handler.getHandlerBlock();
|
||||||
|
if (handlerBlock != null) {
|
||||||
|
BlockSplitter.removePredecessors(handlerBlock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processTargetInsn(BlockNode block, InsnNode lastInsn, @Nullable BlockNode next) {
|
||||||
|
if (lastInsn instanceof IfNode) {
|
||||||
|
IfNode ifInsn = (IfNode) lastInsn;
|
||||||
|
BlockNode thenBlock = ifInsn.getThenBlock();
|
||||||
|
if (Objects.equals(next, thenBlock)) {
|
||||||
|
ifInsn.invertCondition();
|
||||||
|
startLabel.set(ifInsn.getThenBlock().getId());
|
||||||
|
} else {
|
||||||
|
startLabel.set(thenBlock.getId());
|
||||||
|
}
|
||||||
|
ifInsn.normalize();
|
||||||
|
} else {
|
||||||
|
for (BlockNode successor : block.getSuccessors()) {
|
||||||
|
startLabel.set(successor.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isNeedStartLabel(BlockNode block) {
|
||||||
|
return startLabel.get(block.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isNeedEndGoto(BlockNode block) {
|
||||||
|
return endGoto.get(block.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
// DFS sort blocks to reduce goto count
|
||||||
|
private List<BlockNode> getSortedBlocks() {
|
||||||
|
List<BlockNode> list = new ArrayList<>(mth.getBasicBlocks().size());
|
||||||
|
BlockUtils.dfsVisit(mth, list::add);
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package jadx.core.deobf;
|
||||||
|
|
||||||
|
public class ClsAliasPair {
|
||||||
|
private final String pkg;
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
public ClsAliasPair(String pkg, String name) {
|
||||||
|
this.pkg = pkg;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPkg() {
|
||||||
|
return pkg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return pkg + '.' + name;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,11 +12,11 @@ import java.util.HashMap;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import jadx.api.JadxArgs;
|
import jadx.api.JadxArgs;
|
||||||
|
import jadx.api.args.DeobfuscationMapFileMode;
|
||||||
import jadx.core.dex.info.ClassInfo;
|
import jadx.core.dex.info.ClassInfo;
|
||||||
import jadx.core.dex.info.FieldInfo;
|
import jadx.core.dex.info.FieldInfo;
|
||||||
import jadx.core.dex.info.MethodInfo;
|
import jadx.core.dex.info.MethodInfo;
|
||||||
@@ -37,28 +37,21 @@ public class DeobfPresets {
|
|||||||
private final Map<String, String> fldPresetMap = new HashMap<>();
|
private final Map<String, String> fldPresetMap = new HashMap<>();
|
||||||
private final Map<String, String> mthPresetMap = new HashMap<>();
|
private final Map<String, String> mthPresetMap = new HashMap<>();
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public static DeobfPresets build(RootNode root) {
|
public static DeobfPresets build(RootNode root) {
|
||||||
Path deobfMapPath = getPathDeobfMapPath(root);
|
Path deobfMapPath = getPathDeobfMapPath(root);
|
||||||
if (deobfMapPath == null) {
|
if (root.getArgs().getDeobfuscationMapFileMode() != DeobfuscationMapFileMode.IGNORE) {
|
||||||
return null;
|
LOG.debug("Deobfuscation map file set to: {}", deobfMapPath);
|
||||||
}
|
}
|
||||||
LOG.debug("Deobfuscation map file set to: {}", deobfMapPath);
|
|
||||||
return new DeobfPresets(deobfMapPath);
|
return new DeobfPresets(deobfMapPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private static Path getPathDeobfMapPath(RootNode root) {
|
private static Path getPathDeobfMapPath(RootNode root) {
|
||||||
JadxArgs jadxArgs = root.getArgs();
|
JadxArgs jadxArgs = root.getArgs();
|
||||||
File deobfMapFile = jadxArgs.getDeobfuscationMapFile();
|
File deobfMapFile = jadxArgs.getDeobfuscationMapFile();
|
||||||
if (deobfMapFile != null) {
|
if (deobfMapFile != null) {
|
||||||
return deobfMapFile.toPath();
|
return deobfMapFile.toPath();
|
||||||
}
|
}
|
||||||
List<File> inputFiles = jadxArgs.getInputFiles();
|
Path inputFilePath = jadxArgs.getInputFiles().get(0).toPath().toAbsolutePath();
|
||||||
if (inputFiles.isEmpty()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
Path inputFilePath = inputFiles.get(0).getAbsoluteFile().toPath();
|
|
||||||
String baseName = FileUtils.getPathBaseName(inputFilePath);
|
String baseName = FileUtils.getPathBaseName(inputFilePath);
|
||||||
return inputFilePath.getParent().resolve(baseName + ".jobf");
|
return inputFilePath.getParent().resolve(baseName + ".jobf");
|
||||||
}
|
}
|
||||||
@@ -70,9 +63,9 @@ public class DeobfPresets {
|
|||||||
/**
|
/**
|
||||||
* Loads deobfuscator presets
|
* Loads deobfuscator presets
|
||||||
*/
|
*/
|
||||||
public void load() {
|
public boolean load() {
|
||||||
if (!Files.exists(deobfMapFile)) {
|
if (!Files.exists(deobfMapFile)) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
LOG.info("Loading obfuscation map from: {}", deobfMapFile.toAbsolutePath());
|
LOG.info("Loading obfuscation map from: {}", deobfMapFile.toAbsolutePath());
|
||||||
try {
|
try {
|
||||||
@@ -106,8 +99,10 @@ public class DeobfPresets {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error("Failed to load deobfuscation map file '{}'", deobfMapFile.toAbsolutePath(), e);
|
LOG.error("Failed to load deobfuscation map file '{}'", deobfMapFile.toAbsolutePath(), e);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,9 +137,7 @@ public class DeobfPresets {
|
|||||||
}
|
}
|
||||||
Files.write(deobfMapFile, list, MAP_FILE_CHARSET,
|
Files.write(deobfMapFile, list, MAP_FILE_CHARSET,
|
||||||
StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
|
StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
|
||||||
if (LOG.isDebugEnabled()) {
|
LOG.info("Deobfuscation map file saved as: {}", deobfMapFile);
|
||||||
LOG.debug("Deobfuscation map file saved as: {}", deobfMapFile);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getForCls(ClassInfo cls) {
|
public String getForCls(ClassInfo cls) {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import jadx.api.JadxArgs;
|
import jadx.api.JadxArgs;
|
||||||
|
import jadx.api.args.DeobfuscationMapFileMode;
|
||||||
import jadx.api.plugins.input.data.attributes.JadxAttrType;
|
import jadx.api.plugins.input.data.attributes.JadxAttrType;
|
||||||
import jadx.api.plugins.input.data.attributes.types.SourceFileAttr;
|
import jadx.api.plugins.input.data.attributes.types.SourceFileAttr;
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
@@ -76,22 +77,25 @@ public class Deobfuscator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void execute() {
|
public void execute() {
|
||||||
if (!args.isDeobfuscationForceSave()) {
|
if (args.getDeobfuscationMapFileMode().shouldRead()) {
|
||||||
deobfPresets.load();
|
if (deobfPresets.load()) {
|
||||||
for (Map.Entry<String, String> pkgEntry : deobfPresets.getPkgPresetMap().entrySet()) {
|
for (Map.Entry<String, String> pkgEntry : deobfPresets.getPkgPresetMap().entrySet()) {
|
||||||
addPackagePreset(pkgEntry.getKey(), pkgEntry.getValue());
|
addPackagePreset(pkgEntry.getKey(), pkgEntry.getValue());
|
||||||
|
}
|
||||||
|
deobfPresets.getPkgPresetMap().clear(); // not needed anymore
|
||||||
|
initIndexes();
|
||||||
}
|
}
|
||||||
deobfPresets.getPkgPresetMap().clear(); // not needed anymore
|
|
||||||
initIndexes();
|
|
||||||
}
|
}
|
||||||
process();
|
process();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void savePresets() {
|
public void savePresets() {
|
||||||
|
DeobfuscationMapFileMode mode = args.getDeobfuscationMapFileMode();
|
||||||
|
if (!mode.shouldWrite()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
Path deobfMapFile = deobfPresets.getDeobfMapFile();
|
Path deobfMapFile = deobfPresets.getDeobfMapFile();
|
||||||
if (Files.exists(deobfMapFile) && !args.isDeobfuscationForceSave()) {
|
if (mode == DeobfuscationMapFileMode.READ_OR_SAVE && Files.exists(deobfMapFile)) {
|
||||||
LOG.info("Deobfuscation map file '{}' exists. Use command line option '--deobf-rewrite-cfg' to rewrite it",
|
|
||||||
deobfMapFile.toAbsolutePath());
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@@ -112,16 +116,25 @@ public class Deobfuscator {
|
|||||||
deobfPresets.getPkgPresetMap().put(p.getName(), p.getAlias());
|
deobfPresets.getPkgPresetMap().put(p.getName(), p.getAlias());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (DeobfClsInfo deobfClsInfo : clsMap.values()) {
|
for (ClassNode cls : root.getClasses()) {
|
||||||
if (deobfClsInfo.getAlias() != null) {
|
ClassInfo classInfo = cls.getClassInfo();
|
||||||
deobfPresets.getClsPresetMap().put(deobfClsInfo.getCls().getClassInfo().makeRawFullName(), deobfClsInfo.getAlias());
|
if (classInfo.hasAlias()) {
|
||||||
|
deobfPresets.getClsPresetMap().put(classInfo.makeRawFullName(), classInfo.getAliasShortName());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (FieldNode fld : cls.getFields()) {
|
||||||
|
FieldInfo fieldInfo = fld.getFieldInfo();
|
||||||
|
if (fieldInfo.hasAlias()) {
|
||||||
|
deobfPresets.getFldPresetMap().put(fieldInfo.getRawFullId(), fld.getAlias());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (MethodNode mth : cls.getMethods()) {
|
||||||
|
MethodInfo methodInfo = mth.getMethodInfo();
|
||||||
|
if (methodInfo.hasAlias()) {
|
||||||
|
deobfPresets.getMthPresetMap().put(methodInfo.getRawFullId(), methodInfo.getAlias());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
for (FieldInfo fld : fldMap.keySet()) {
|
|
||||||
deobfPresets.getFldPresetMap().put(fld.getRawFullId(), fld.getAlias());
|
|
||||||
}
|
|
||||||
for (MethodInfo mth : mthMap.keySet()) {
|
|
||||||
deobfPresets.getMthPresetMap().put(mth.getRawFullId(), mth.getAlias());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -377,10 +390,10 @@ public class Deobfuscator {
|
|||||||
String alias = null;
|
String alias = null;
|
||||||
String pkgName = null;
|
String pkgName = null;
|
||||||
if (this.parseKotlinMetadata) {
|
if (this.parseKotlinMetadata) {
|
||||||
ClassInfo kotlinCls = KotlinMetadataUtils.getClassName(cls);
|
ClsAliasPair kotlinCls = KotlinMetadataUtils.getClassAlias(cls);
|
||||||
if (kotlinCls != null) {
|
if (kotlinCls != null) {
|
||||||
alias = prepareNameFull(kotlinCls.getShortName(), "C");
|
alias = kotlinCls.getName();
|
||||||
pkgName = kotlinCls.getPackage();
|
pkgName = kotlinCls.getPkg();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (alias == null && this.useSourceNameAsAlias) {
|
if (alias == null && this.useSourceNameAsAlias) {
|
||||||
@@ -572,6 +585,7 @@ public class Deobfuscator {
|
|||||||
if (!pkg.hasAlias()) {
|
if (!pkg.hasAlias()) {
|
||||||
String pkgName = pkg.getName();
|
String pkgName = pkg.getName();
|
||||||
if ((args.isDeobfuscationOn() && shouldRename(pkgName))
|
if ((args.isDeobfuscationOn() && shouldRename(pkgName))
|
||||||
|
&& (pkg.getParentPackage() != rootPackage || !TldHelper.contains(pkgName)) // check if first level is a valid tld
|
||||||
|| (args.isRenameValid() && !NameMapper.isValidIdentifier(pkgName))
|
|| (args.isRenameValid() && !NameMapper.isValidIdentifier(pkgName))
|
||||||
|| (args.isRenamePrintable() && !NameMapper.isAllCharsPrintable(pkgName))) {
|
|| (args.isRenamePrintable() && !NameMapper.isAllCharsPrintable(pkgName))) {
|
||||||
String pkgAlias = String.format("p%03d%s", pkgIndex++, prepareNamePart(pkg.getName()));
|
String pkgAlias = String.format("p%03d%s", pkgIndex++, prepareNamePart(pkg.getName()));
|
||||||
@@ -592,20 +606,6 @@ public class Deobfuscator {
|
|||||||
return NameMapper.removeInvalidCharsMiddle(name);
|
return NameMapper.removeInvalidCharsMiddle(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String prepareNameFull(String name, String prefix) {
|
|
||||||
if (name.length() > maxLength) {
|
|
||||||
return makeHashName(name, prefix);
|
|
||||||
}
|
|
||||||
String result = NameMapper.removeInvalidChars(name, prefix);
|
|
||||||
if (result.isEmpty()) {
|
|
||||||
return makeHashName(name, prefix);
|
|
||||||
}
|
|
||||||
if (NameMapper.isReserved(result)) {
|
|
||||||
return prefix + result;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String makeHashName(String name, String invalidPrefix) {
|
private static String makeHashName(String name, String invalidPrefix) {
|
||||||
return invalidPrefix + 'x' + Integer.toHexString(name.hashCode());
|
return invalidPrefix + 'x' + Integer.toHexString(name.hashCode());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -143,19 +143,15 @@ public class NameMapper {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Return modified string with removed:
|
* Return modified string with removed:
|
||||||
* <p>
|
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>not printable chars (including unicode)
|
* <li>not printable chars (including unicode)
|
||||||
* <li>chars not valid for java identifier part
|
* <li>chars not valid for java identifier part
|
||||||
* </ul>
|
* </ul>
|
||||||
* <p>
|
|
||||||
* Note: this 'middle' method must be used with prefixed string:
|
* Note: this 'middle' method must be used with prefixed string:
|
||||||
* <p>
|
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>can leave invalid chars for java identifier start (i.e numbers)
|
* <li>can leave invalid chars for java identifier start (i.e numbers)
|
||||||
* <li>result not checked for reserved words
|
* <li>result not checked for reserved words
|
||||||
* </ul>
|
* </ul>
|
||||||
* <p>
|
|
||||||
*/
|
*/
|
||||||
public static String removeInvalidCharsMiddle(String name) {
|
public static String removeInvalidCharsMiddle(String name) {
|
||||||
if (isValidIdentifier(name) && isAllCharsPrintable(name)) {
|
if (isValidIdentifier(name) && isAllCharsPrintable(name)) {
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package jadx.core.deobf;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a list of all top level domains with 3 characters and less,
|
||||||
|
* so we can exclude them from deobfuscation.
|
||||||
|
*/
|
||||||
|
public class TldHelper {
|
||||||
|
|
||||||
|
private static final Set<String> TLD_SET = loadTldFile();
|
||||||
|
|
||||||
|
private static Set<String> loadTldFile() {
|
||||||
|
Set<String> tldNames = new HashSet<>();
|
||||||
|
try (BufferedReader reader = new BufferedReader(new InputStreamReader(Deobfuscator.class.getResourceAsStream("tld_3.txt")))) {
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
line = line.trim();
|
||||||
|
if (!line.startsWith("#") && !line.isEmpty()) {
|
||||||
|
tldNames.add(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tldNames;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new JadxRuntimeException("Failed to load top level domain list tld_3.txt", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean contains(String name) {
|
||||||
|
return TLD_SET.contains(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -33,8 +33,8 @@ public enum AFlag {
|
|||||||
|
|
||||||
SKIP_FIRST_ARG,
|
SKIP_FIRST_ARG,
|
||||||
SKIP_ARG, // skip argument in invoke call
|
SKIP_ARG, // skip argument in invoke call
|
||||||
|
NO_SKIP_ARGS,
|
||||||
ANONYMOUS_CONSTRUCTOR,
|
ANONYMOUS_CONSTRUCTOR,
|
||||||
ANONYMOUS_CLASS,
|
|
||||||
|
|
||||||
THIS,
|
THIS,
|
||||||
SUPER,
|
SUPER,
|
||||||
@@ -76,8 +76,13 @@ public enum AFlag {
|
|||||||
INCONSISTENT_CODE, // warning about incorrect decompilation
|
INCONSISTENT_CODE, // warning about incorrect decompilation
|
||||||
|
|
||||||
REQUEST_IF_REGION_OPTIMIZE, // run if region visitor again
|
REQUEST_IF_REGION_OPTIMIZE, // run if region visitor again
|
||||||
|
REQUEST_CODE_SHRINK,
|
||||||
RERUN_SSA_TRANSFORM,
|
RERUN_SSA_TRANSFORM,
|
||||||
|
|
||||||
|
METHOD_CANDIDATE_FOR_INLINE,
|
||||||
|
|
||||||
|
DISABLE_BLOCKS_LOCK,
|
||||||
|
|
||||||
// Class processing flags
|
// Class processing flags
|
||||||
RESTART_CODEGEN, // codegen must be executed again
|
RESTART_CODEGEN, // codegen must be executed again
|
||||||
RELOAD_AT_CODEGEN_STAGE, // class can't be analyzed at 'process' stage => unload before 'codegen' stage
|
RELOAD_AT_CODEGEN_STAGE, // class can't be analyzed at 'process' stage => unload before 'codegen' stage
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package jadx.core.dex.attributes;
|
|||||||
|
|
||||||
import jadx.api.plugins.input.data.attributes.IJadxAttrType;
|
import jadx.api.plugins.input.data.attributes.IJadxAttrType;
|
||||||
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
|
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
|
||||||
|
import jadx.core.dex.attributes.nodes.AnonymousClassAttr;
|
||||||
import jadx.core.dex.attributes.nodes.ClassTypeVarsAttr;
|
import jadx.core.dex.attributes.nodes.ClassTypeVarsAttr;
|
||||||
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
|
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
|
||||||
import jadx.core.dex.attributes.nodes.EdgeInsnAttr;
|
import jadx.core.dex.attributes.nodes.EdgeInsnAttr;
|
||||||
@@ -10,19 +11,23 @@ import jadx.core.dex.attributes.nodes.EnumMapAttr;
|
|||||||
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
|
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
|
||||||
import jadx.core.dex.attributes.nodes.ForceReturnAttr;
|
import jadx.core.dex.attributes.nodes.ForceReturnAttr;
|
||||||
import jadx.core.dex.attributes.nodes.GenericInfoAttr;
|
import jadx.core.dex.attributes.nodes.GenericInfoAttr;
|
||||||
|
import jadx.core.dex.attributes.nodes.InlinedAttr;
|
||||||
import jadx.core.dex.attributes.nodes.JadxCommentsAttr;
|
import jadx.core.dex.attributes.nodes.JadxCommentsAttr;
|
||||||
import jadx.core.dex.attributes.nodes.JadxError;
|
import jadx.core.dex.attributes.nodes.JadxError;
|
||||||
import jadx.core.dex.attributes.nodes.JumpInfo;
|
import jadx.core.dex.attributes.nodes.JumpInfo;
|
||||||
import jadx.core.dex.attributes.nodes.LocalVarsDebugInfoAttr;
|
import jadx.core.dex.attributes.nodes.LocalVarsDebugInfoAttr;
|
||||||
import jadx.core.dex.attributes.nodes.LoopInfo;
|
import jadx.core.dex.attributes.nodes.LoopInfo;
|
||||||
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
||||||
|
import jadx.core.dex.attributes.nodes.MethodBridgeAttr;
|
||||||
import jadx.core.dex.attributes.nodes.MethodInlineAttr;
|
import jadx.core.dex.attributes.nodes.MethodInlineAttr;
|
||||||
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
|
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
|
||||||
|
import jadx.core.dex.attributes.nodes.MethodReplaceAttr;
|
||||||
import jadx.core.dex.attributes.nodes.MethodTypeVarsAttr;
|
import jadx.core.dex.attributes.nodes.MethodTypeVarsAttr;
|
||||||
import jadx.core.dex.attributes.nodes.PhiListAttr;
|
import jadx.core.dex.attributes.nodes.PhiListAttr;
|
||||||
import jadx.core.dex.attributes.nodes.RegDebugInfoAttr;
|
import jadx.core.dex.attributes.nodes.RegDebugInfoAttr;
|
||||||
import jadx.core.dex.attributes.nodes.RenameReasonAttr;
|
import jadx.core.dex.attributes.nodes.RenameReasonAttr;
|
||||||
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
|
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
|
||||||
|
import jadx.core.dex.attributes.nodes.SpecialEdgeAttr;
|
||||||
import jadx.core.dex.attributes.nodes.TmpEdgeAttr;
|
import jadx.core.dex.attributes.nodes.TmpEdgeAttr;
|
||||||
import jadx.core.dex.nodes.IMethodDetails;
|
import jadx.core.dex.nodes.IMethodDetails;
|
||||||
import jadx.core.dex.trycatch.CatchAttr;
|
import jadx.core.dex.trycatch.CatchAttr;
|
||||||
@@ -51,6 +56,8 @@ public final class AType<T extends IJadxAttribute> implements IJadxAttrType<T> {
|
|||||||
public static final AType<EnumClassAttr> ENUM_CLASS = new AType<>();
|
public static final AType<EnumClassAttr> ENUM_CLASS = new AType<>();
|
||||||
public static final AType<EnumMapAttr> ENUM_MAP = new AType<>();
|
public static final AType<EnumMapAttr> ENUM_MAP = new AType<>();
|
||||||
public static final AType<ClassTypeVarsAttr> CLASS_TYPE_VARS = new AType<>();
|
public static final AType<ClassTypeVarsAttr> CLASS_TYPE_VARS = new AType<>();
|
||||||
|
public static final AType<AnonymousClassAttr> ANONYMOUS_CLASS = new AType<>();
|
||||||
|
public static final AType<InlinedAttr> INLINED = new AType<>();
|
||||||
|
|
||||||
// field
|
// field
|
||||||
public static final AType<FieldInitInsnAttr> FIELD_INIT_INSN = new AType<>();
|
public static final AType<FieldInitInsnAttr> FIELD_INIT_INSN = new AType<>();
|
||||||
@@ -59,6 +66,8 @@ public final class AType<T extends IJadxAttribute> implements IJadxAttrType<T> {
|
|||||||
// method
|
// method
|
||||||
public static final AType<LocalVarsDebugInfoAttr> LOCAL_VARS_DEBUG_INFO = new AType<>();
|
public static final AType<LocalVarsDebugInfoAttr> LOCAL_VARS_DEBUG_INFO = new AType<>();
|
||||||
public static final AType<MethodInlineAttr> METHOD_INLINE = new AType<>();
|
public static final AType<MethodInlineAttr> METHOD_INLINE = new AType<>();
|
||||||
|
public static final AType<MethodReplaceAttr> METHOD_REPLACE = new AType<>();
|
||||||
|
public static final AType<MethodBridgeAttr> BRIDGED_BY = new AType<>();
|
||||||
public static final AType<SkipMethodArgsAttr> SKIP_MTH_ARGS = new AType<>();
|
public static final AType<SkipMethodArgsAttr> SKIP_MTH_ARGS = new AType<>();
|
||||||
public static final AType<MethodOverrideAttr> METHOD_OVERRIDE = new AType<>();
|
public static final AType<MethodOverrideAttr> METHOD_OVERRIDE = new AType<>();
|
||||||
public static final AType<MethodTypeVarsAttr> METHOD_TYPE_VARS = new AType<>();
|
public static final AType<MethodTypeVarsAttr> METHOD_TYPE_VARS = new AType<>();
|
||||||
@@ -72,6 +81,7 @@ public final class AType<T extends IJadxAttribute> implements IJadxAttrType<T> {
|
|||||||
public static final AType<ForceReturnAttr> FORCE_RETURN = new AType<>();
|
public static final AType<ForceReturnAttr> FORCE_RETURN = new AType<>();
|
||||||
public static final AType<AttrList<LoopInfo>> LOOP = new AType<>();
|
public static final AType<AttrList<LoopInfo>> LOOP = new AType<>();
|
||||||
public static final AType<AttrList<EdgeInsnAttr>> EDGE_INSN = new AType<>();
|
public static final AType<AttrList<EdgeInsnAttr>> EDGE_INSN = new AType<>();
|
||||||
|
public static final AType<AttrList<SpecialEdgeAttr>> SPECIAL_EDGE = new AType<>();
|
||||||
public static final AType<TmpEdgeAttr> TMP_EDGE = new AType<>();
|
public static final AType<TmpEdgeAttr> TMP_EDGE = new AType<>();
|
||||||
public static final AType<TryCatchBlockAttr> TRY_BLOCK = new AType<>();
|
public static final AType<TryCatchBlockAttr> TRY_BLOCK = new AType<>();
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package jadx.core.dex.attributes.nodes;
|
||||||
|
|
||||||
|
import jadx.api.plugins.input.data.attributes.PinnedAttribute;
|
||||||
|
import jadx.core.dex.attributes.AType;
|
||||||
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
|
|
||||||
|
public class AnonymousClassAttr extends PinnedAttribute {
|
||||||
|
|
||||||
|
private final ClassNode outerCls;
|
||||||
|
private final ArgType baseType;
|
||||||
|
|
||||||
|
public AnonymousClassAttr(ClassNode outerCls, ArgType baseType) {
|
||||||
|
this.outerCls = outerCls;
|
||||||
|
this.baseType = baseType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClassNode getOuterCls() {
|
||||||
|
return outerCls;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArgType getBaseType() {
|
||||||
|
return baseType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AType<AnonymousClassAttr> getAttrType() {
|
||||||
|
return AType.ANONYMOUS_CLASS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "AnonymousClass{" + outerCls + ", base: " + baseType + '}';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package jadx.core.dex.attributes.nodes;
|
||||||
|
|
||||||
|
import jadx.api.plugins.input.data.attributes.IJadxAttrType;
|
||||||
|
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
|
||||||
|
import jadx.core.dex.attributes.AType;
|
||||||
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
|
|
||||||
|
public class InlinedAttr implements IJadxAttribute {
|
||||||
|
|
||||||
|
private final ClassNode inlineCls;
|
||||||
|
|
||||||
|
public InlinedAttr(ClassNode inlineCls) {
|
||||||
|
this.inlineCls = inlineCls;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClassNode getInlineCls() {
|
||||||
|
return inlineCls;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IJadxAttrType<InlinedAttr> getAttrType() {
|
||||||
|
return AType.INLINED;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "INLINED: " + inlineCls;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ import jadx.api.CommentsLevel;
|
|||||||
import jadx.api.plugins.input.data.attributes.IJadxAttrType;
|
import jadx.api.plugins.input.data.attributes.IJadxAttrType;
|
||||||
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
|
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
|
||||||
import jadx.core.dex.attributes.AType;
|
import jadx.core.dex.attributes.AType;
|
||||||
|
import jadx.core.utils.Utils;
|
||||||
|
|
||||||
public class JadxCommentsAttr implements IJadxAttribute {
|
public class JadxCommentsAttr implements IJadxAttribute {
|
||||||
|
|
||||||
@@ -44,4 +45,12 @@ public class JadxCommentsAttr implements IJadxAttribute {
|
|||||||
public IJadxAttrType<JadxCommentsAttr> getAttrType() {
|
public IJadxAttrType<JadxCommentsAttr> getAttrType() {
|
||||||
return AType.JADX_COMMENTS;
|
return AType.JADX_COMMENTS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "JadxCommentsAttr{\n "
|
||||||
|
+ Utils.listToString(comments.entrySet(), "\n ",
|
||||||
|
e -> e.getKey() + ": \n -> " + Utils.listToString(e.getValue(), "\n -> "))
|
||||||
|
+ '}';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,11 +50,7 @@ public class JadxError implements Comparable<JadxError> {
|
|||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
StringBuilder str = new StringBuilder();
|
StringBuilder str = new StringBuilder();
|
||||||
str.append("JadxError: ");
|
str.append("JadxError: ").append(error).append(' ');
|
||||||
if (error != null) {
|
|
||||||
str.append(error);
|
|
||||||
str.append(' ');
|
|
||||||
}
|
|
||||||
if (cause != null) {
|
if (cause != null) {
|
||||||
str.append(cause.getClass());
|
str.append(cause.getClass());
|
||||||
str.append(':');
|
str.append(':');
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package jadx.core.dex.attributes.nodes;
|
package jadx.core.dex.attributes.nodes;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -20,10 +19,10 @@ public class LoopInfo {
|
|||||||
private int id;
|
private int id;
|
||||||
private LoopInfo parentLoop;
|
private LoopInfo parentLoop;
|
||||||
|
|
||||||
public LoopInfo(BlockNode start, BlockNode end) {
|
public LoopInfo(BlockNode start, BlockNode end, Set<BlockNode> loopBlocks) {
|
||||||
this.start = start;
|
this.start = start;
|
||||||
this.end = end;
|
this.end = end;
|
||||||
this.loopBlocks = Collections.unmodifiableSet(BlockUtils.getAllPathsBlocks(start, end));
|
this.loopBlocks = loopBlocks;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BlockNode getStart() {
|
public BlockNode getStart() {
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package jadx.core.dex.attributes.nodes;
|
||||||
|
|
||||||
|
import jadx.api.plugins.input.data.attributes.PinnedAttribute;
|
||||||
|
import jadx.core.dex.attributes.AType;
|
||||||
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
|
|
||||||
|
public class MethodBridgeAttr extends PinnedAttribute {
|
||||||
|
|
||||||
|
private final MethodNode bridgeMth;
|
||||||
|
|
||||||
|
public MethodBridgeAttr(MethodNode bridgeMth) {
|
||||||
|
this.bridgeMth = bridgeMth;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MethodNode getBridgeMth() {
|
||||||
|
return bridgeMth;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AType<MethodBridgeAttr> getAttrType() {
|
||||||
|
return AType.BRIDGED_BY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "BRIDGED_BY: " + bridgeMth;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package jadx.core.dex.attributes.nodes;
|
package jadx.core.dex.attributes.nodes;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.SortedSet;
|
import java.util.SortedSet;
|
||||||
|
|
||||||
import jadx.api.plugins.input.data.attributes.PinnedAttribute;
|
import jadx.api.plugins.input.data.attributes.PinnedAttribute;
|
||||||
@@ -20,27 +21,26 @@ public class MethodOverrideAttr extends PinnedAttribute {
|
|||||||
*/
|
*/
|
||||||
private SortedSet<MethodNode> relatedMthNodes;
|
private SortedSet<MethodNode> relatedMthNodes;
|
||||||
|
|
||||||
public MethodOverrideAttr(List<IMethodDetails> overrideList, SortedSet<MethodNode> relatedMthNodes) {
|
private Set<IMethodDetails> baseMethods;
|
||||||
|
|
||||||
|
public MethodOverrideAttr(List<IMethodDetails> overrideList, SortedSet<MethodNode> relatedMthNodes, Set<IMethodDetails> baseMethods) {
|
||||||
this.overrideList = overrideList;
|
this.overrideList = overrideList;
|
||||||
this.relatedMthNodes = relatedMthNodes;
|
this.relatedMthNodes = relatedMthNodes;
|
||||||
}
|
this.baseMethods = baseMethods;
|
||||||
|
|
||||||
public boolean isAtBaseMth() {
|
|
||||||
return overrideList.isEmpty();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<IMethodDetails> getOverrideList() {
|
public List<IMethodDetails> getOverrideList() {
|
||||||
return overrideList;
|
return overrideList;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOverrideList(List<IMethodDetails> overrideList) {
|
|
||||||
this.overrideList = overrideList;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SortedSet<MethodNode> getRelatedMthNodes() {
|
public SortedSet<MethodNode> getRelatedMthNodes() {
|
||||||
return relatedMthNodes;
|
return relatedMthNodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Set<IMethodDetails> getBaseMethods() {
|
||||||
|
return baseMethods;
|
||||||
|
}
|
||||||
|
|
||||||
public void setRelatedMthNodes(SortedSet<MethodNode> relatedMthNodes) {
|
public void setRelatedMthNodes(SortedSet<MethodNode> relatedMthNodes) {
|
||||||
this.relatedMthNodes = relatedMthNodes;
|
this.relatedMthNodes = relatedMthNodes;
|
||||||
}
|
}
|
||||||
@@ -52,6 +52,6 @@ public class MethodOverrideAttr extends PinnedAttribute {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "METHOD_OVERRIDE: " + overrideList;
|
return "METHOD_OVERRIDE: " + getBaseMethods();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package jadx.core.dex.attributes.nodes;
|
||||||
|
|
||||||
|
import jadx.api.plugins.input.data.attributes.PinnedAttribute;
|
||||||
|
import jadx.core.dex.attributes.AType;
|
||||||
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls of method should be replaced by provided method (used for synthetic methods redirect)
|
||||||
|
*/
|
||||||
|
public class MethodReplaceAttr extends PinnedAttribute {
|
||||||
|
|
||||||
|
private final MethodNode replaceMth;
|
||||||
|
|
||||||
|
public MethodReplaceAttr(MethodNode replaceMth) {
|
||||||
|
this.replaceMth = replaceMth;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MethodNode getReplaceMth() {
|
||||||
|
return replaceMth;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AType<MethodReplaceAttr> getAttrType() {
|
||||||
|
return AType.METHOD_REPLACE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "REPLACED_BY: " + replaceMth;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,16 @@ import jadx.core.dex.attributes.AttrNode;
|
|||||||
|
|
||||||
public class RenameReasonAttr implements IJadxAttribute {
|
public class RenameReasonAttr implements IJadxAttribute {
|
||||||
|
|
||||||
|
public static RenameReasonAttr forNode(AttrNode node) {
|
||||||
|
RenameReasonAttr renameReasonAttr = node.get(AType.RENAME_REASON);
|
||||||
|
if (renameReasonAttr != null) {
|
||||||
|
return renameReasonAttr;
|
||||||
|
}
|
||||||
|
RenameReasonAttr newAttr = new RenameReasonAttr();
|
||||||
|
node.addAttr(newAttr);
|
||||||
|
return newAttr;
|
||||||
|
}
|
||||||
|
|
||||||
private String description;
|
private String description;
|
||||||
|
|
||||||
public RenameReasonAttr() {
|
public RenameReasonAttr() {
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package jadx.core.dex.attributes.nodes;
|
||||||
|
|
||||||
|
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
|
||||||
|
import jadx.core.dex.attributes.AType;
|
||||||
|
import jadx.core.dex.attributes.AttrList;
|
||||||
|
import jadx.core.dex.nodes.BlockNode;
|
||||||
|
|
||||||
|
public class SpecialEdgeAttr implements IJadxAttribute {
|
||||||
|
|
||||||
|
public enum SpecialEdgeType {
|
||||||
|
BACK_EDGE,
|
||||||
|
CROSS_EDGE
|
||||||
|
}
|
||||||
|
|
||||||
|
private final SpecialEdgeType type;
|
||||||
|
private final BlockNode start;
|
||||||
|
private final BlockNode end;
|
||||||
|
|
||||||
|
public SpecialEdgeAttr(SpecialEdgeType type, BlockNode start, BlockNode end) {
|
||||||
|
this.type = type;
|
||||||
|
this.start = start;
|
||||||
|
this.end = end;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SpecialEdgeType getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlockNode getStart() {
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlockNode getEnd() {
|
||||||
|
return end;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AType<AttrList<SpecialEdgeAttr>> getAttrType() {
|
||||||
|
return AType.SPECIAL_EDGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return type + ": " + start + " -> " + end;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -246,13 +246,13 @@ public final class ClassInfo implements Comparable<ClassInfo> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void notInner(RootNode root) {
|
public void notInner(RootNode root) {
|
||||||
this.parentClass = null;
|
|
||||||
splitAndApplyNames(root, type, false);
|
splitAndApplyNames(root, type, false);
|
||||||
|
this.parentClass = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void convertToInner(ClassNode parent) {
|
public void convertToInner(ClassNode parent) {
|
||||||
this.parentClass = parent.getClassInfo();
|
|
||||||
splitAndApplyNames(parent.root(), type, true);
|
splitAndApplyNames(parent.root(), type, true);
|
||||||
|
this.parentClass = parent.getClassInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateNames(RootNode root) {
|
public void updateNames(RootNode root) {
|
||||||
|
|||||||
@@ -176,6 +176,9 @@ public class ConstStorage {
|
|||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public FieldNode getConstFieldByLiteralArg(ClassNode cls, LiteralArg arg) {
|
public FieldNode getConstFieldByLiteralArg(ClassNode cls, LiteralArg arg) {
|
||||||
|
if (!replaceEnabled) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
PrimitiveType type = arg.getType().getPrimitiveType();
|
PrimitiveType type = arg.getType().getPrimitiveType();
|
||||||
if (type == null) {
|
if (type == null) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import java.util.List;
|
|||||||
import jadx.api.plugins.input.insns.InsnData;
|
import jadx.api.plugins.input.insns.InsnData;
|
||||||
import jadx.core.dex.instructions.args.ArgType;
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
import jadx.core.dex.instructions.args.InsnArg;
|
import jadx.core.dex.instructions.args.InsnArg;
|
||||||
|
import jadx.core.dex.instructions.args.LiteralArg;
|
||||||
import jadx.core.dex.instructions.args.PrimitiveType;
|
import jadx.core.dex.instructions.args.PrimitiveType;
|
||||||
import jadx.core.dex.nodes.BlockNode;
|
import jadx.core.dex.nodes.BlockNode;
|
||||||
import jadx.core.dex.nodes.InsnNode;
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
@@ -70,6 +71,15 @@ public class IfNode extends GotoNode {
|
|||||||
elseBlock = tmp;
|
elseBlock = tmp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change 'a != false' to 'a == true'
|
||||||
|
*/
|
||||||
|
public void normalize() {
|
||||||
|
if (getOp() == IfOp.NE && getArg(1).isFalse()) {
|
||||||
|
changeCondition(IfOp.EQ, getArg(0), LiteralArg.litTrue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void changeCondition(IfOp op, InsnArg arg1, InsnArg arg2) {
|
public void changeCondition(IfOp op, InsnArg arg1, InsnArg arg2) {
|
||||||
this.op = op;
|
this.op = op;
|
||||||
setArg(0, arg1);
|
setArg(0, arg1);
|
||||||
|
|||||||
@@ -389,11 +389,11 @@ public class InsnDecoder {
|
|||||||
return arrLenInsn;
|
return arrLenInsn;
|
||||||
|
|
||||||
case AGET:
|
case AGET:
|
||||||
return arrayGet(insn, ArgType.INT_FLOAT);
|
return arrayGet(insn, ArgType.INT_FLOAT, ArgType.NARROW_NUMBERS_NO_BOOL);
|
||||||
case AGET_BOOLEAN:
|
case AGET_BOOLEAN:
|
||||||
return arrayGet(insn, ArgType.BOOLEAN);
|
return arrayGet(insn, ArgType.BOOLEAN);
|
||||||
case AGET_BYTE:
|
case AGET_BYTE:
|
||||||
return arrayGet(insn, ArgType.BYTE);
|
return arrayGet(insn, ArgType.BYTE, ArgType.NARROW_INTEGRAL);
|
||||||
case AGET_BYTE_BOOLEAN:
|
case AGET_BYTE_BOOLEAN:
|
||||||
return arrayGet(insn, ArgType.BYTE_BOOLEAN);
|
return arrayGet(insn, ArgType.BYTE_BOOLEAN);
|
||||||
case AGET_CHAR:
|
case AGET_CHAR:
|
||||||
@@ -406,7 +406,7 @@ public class InsnDecoder {
|
|||||||
return arrayGet(insn, ArgType.UNKNOWN_OBJECT);
|
return arrayGet(insn, ArgType.UNKNOWN_OBJECT);
|
||||||
|
|
||||||
case APUT:
|
case APUT:
|
||||||
return arrayPut(insn, ArgType.INT_FLOAT);
|
return arrayPut(insn, ArgType.INT_FLOAT, ArgType.NARROW_NUMBERS_NO_BOOL);
|
||||||
case APUT_BOOLEAN:
|
case APUT_BOOLEAN:
|
||||||
return arrayPut(insn, ArgType.BOOLEAN);
|
return arrayPut(insn, ArgType.BOOLEAN);
|
||||||
case APUT_BYTE:
|
case APUT_BYTE:
|
||||||
@@ -437,7 +437,9 @@ public class InsnDecoder {
|
|||||||
case INVOKE_VIRTUAL:
|
case INVOKE_VIRTUAL:
|
||||||
return invoke(insn, InvokeType.VIRTUAL, false);
|
return invoke(insn, InvokeType.VIRTUAL, false);
|
||||||
case INVOKE_CUSTOM:
|
case INVOKE_CUSTOM:
|
||||||
return invoke(insn, InvokeType.CUSTOM, false);
|
return invokeCustom(insn, false);
|
||||||
|
case INVOKE_SPECIAL:
|
||||||
|
return invokeSpecial(insn);
|
||||||
|
|
||||||
case INVOKE_DIRECT_RANGE:
|
case INVOKE_DIRECT_RANGE:
|
||||||
return invoke(insn, InvokeType.DIRECT, true);
|
return invoke(insn, InvokeType.DIRECT, true);
|
||||||
@@ -448,7 +450,7 @@ public class InsnDecoder {
|
|||||||
case INVOKE_VIRTUAL_RANGE:
|
case INVOKE_VIRTUAL_RANGE:
|
||||||
return invoke(insn, InvokeType.VIRTUAL, true);
|
return invoke(insn, InvokeType.VIRTUAL, true);
|
||||||
case INVOKE_CUSTOM_RANGE:
|
case INVOKE_CUSTOM_RANGE:
|
||||||
return invoke(insn, InvokeType.CUSTOM, true);
|
return invokeCustom(insn, true);
|
||||||
|
|
||||||
case NEW_INSTANCE:
|
case NEW_INSTANCE:
|
||||||
ArgType clsType = ArgType.parse(insn.getIndexAsType());
|
ArgType clsType = ArgType.parse(insn.getIndexAsType());
|
||||||
@@ -506,7 +508,17 @@ public class InsnDecoder {
|
|||||||
private InsnNode makeNewArray(InsnData insn) {
|
private InsnNode makeNewArray(InsnData insn) {
|
||||||
ArgType indexType = ArgType.parse(insn.getIndexAsType());
|
ArgType indexType = ArgType.parse(insn.getIndexAsType());
|
||||||
int dim = (int) insn.getLiteral();
|
int dim = (int) insn.getLiteral();
|
||||||
ArgType arrType = dim == 0 ? indexType : ArgType.array(indexType, dim);
|
ArgType arrType;
|
||||||
|
if (dim == 0) {
|
||||||
|
arrType = indexType;
|
||||||
|
} else {
|
||||||
|
if (indexType.isArray()) {
|
||||||
|
// java bytecode can pass array as a base type
|
||||||
|
arrType = indexType;
|
||||||
|
} else {
|
||||||
|
arrType = ArgType.array(indexType, dim);
|
||||||
|
}
|
||||||
|
}
|
||||||
int regsCount = insn.getRegsCount();
|
int regsCount = insn.getRegsCount();
|
||||||
NewArrayNode newArr = new NewArrayNode(arrType, regsCount - 1);
|
NewArrayNode newArr = new NewArrayNode(arrType, regsCount - 1);
|
||||||
newArr.setResult(InsnArg.reg(insn, 0, arrType));
|
newArr.setResult(InsnArg.reg(insn, 0, arrType));
|
||||||
@@ -565,10 +577,27 @@ public class InsnDecoder {
|
|||||||
return inode;
|
return inode;
|
||||||
}
|
}
|
||||||
|
|
||||||
private InsnNode invoke(InsnData insn, InvokeType type, boolean isRange) {
|
private InsnNode invokeCustom(InsnData insn, boolean isRange) {
|
||||||
if (type == InvokeType.CUSTOM) {
|
return InvokeCustomBuilder.build(method, insn, isRange);
|
||||||
return InvokeCustomBuilder.build(method, insn, isRange);
|
}
|
||||||
|
|
||||||
|
private InsnNode invokeSpecial(InsnData insn) {
|
||||||
|
IMethodRef mthRef = InsnDataUtils.getMethodRef(insn);
|
||||||
|
if (mthRef == null) {
|
||||||
|
throw new JadxRuntimeException("Failed to load method reference for insn: " + insn);
|
||||||
}
|
}
|
||||||
|
MethodInfo mthInfo = MethodInfo.fromRef(root, mthRef);
|
||||||
|
// convert 'special' to 'direct/super' same as dx
|
||||||
|
InvokeType type;
|
||||||
|
if (mthInfo.isConstructor() || Objects.equals(mthInfo.getDeclClass(), method.getParentClass().getClassInfo())) {
|
||||||
|
type = InvokeType.DIRECT;
|
||||||
|
} else {
|
||||||
|
type = InvokeType.SUPER;
|
||||||
|
}
|
||||||
|
return new InvokeNode(mthInfo, insn, type, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private InsnNode invoke(InsnData insn, InvokeType type, boolean isRange) {
|
||||||
IMethodRef mthRef = InsnDataUtils.getMethodRef(insn);
|
IMethodRef mthRef = InsnDataUtils.getMethodRef(insn);
|
||||||
if (mthRef == null) {
|
if (mthRef == null) {
|
||||||
throw new JadxRuntimeException("Failed to load method reference for insn: " + insn);
|
throw new JadxRuntimeException("Failed to load method reference for insn: " + insn);
|
||||||
@@ -578,16 +607,24 @@ public class InsnDecoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private InsnNode arrayGet(InsnData insn, ArgType argType) {
|
private InsnNode arrayGet(InsnData insn, ArgType argType) {
|
||||||
|
return arrayGet(insn, argType, argType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private InsnNode arrayGet(InsnData insn, ArgType arrElemType, ArgType resType) {
|
||||||
InsnNode inode = new InsnNode(InsnType.AGET, 2);
|
InsnNode inode = new InsnNode(InsnType.AGET, 2);
|
||||||
inode.setResult(InsnArg.typeImmutableIfKnownReg(insn, 0, argType));
|
inode.setResult(InsnArg.typeImmutableIfKnownReg(insn, 0, resType));
|
||||||
inode.addArg(InsnArg.typeImmutableIfKnownReg(insn, 1, ArgType.array(argType)));
|
inode.addArg(InsnArg.typeImmutableIfKnownReg(insn, 1, ArgType.array(arrElemType)));
|
||||||
inode.addArg(InsnArg.reg(insn, 2, ArgType.NARROW_INTEGRAL));
|
inode.addArg(InsnArg.reg(insn, 2, ArgType.NARROW_INTEGRAL));
|
||||||
return inode;
|
return inode;
|
||||||
}
|
}
|
||||||
|
|
||||||
private InsnNode arrayPut(InsnData insn, ArgType argType) {
|
private InsnNode arrayPut(InsnData insn, ArgType argType) {
|
||||||
|
return arrayPut(insn, argType, argType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private InsnNode arrayPut(InsnData insn, ArgType arrElemType, ArgType argType) {
|
||||||
InsnNode inode = new InsnNode(InsnType.APUT, 3);
|
InsnNode inode = new InsnNode(InsnType.APUT, 3);
|
||||||
inode.addArg(InsnArg.typeImmutableIfKnownReg(insn, 1, ArgType.array(argType)));
|
inode.addArg(InsnArg.typeImmutableIfKnownReg(insn, 1, ArgType.array(arrElemType)));
|
||||||
inode.addArg(InsnArg.reg(insn, 2, ArgType.NARROW_INTEGRAL));
|
inode.addArg(InsnArg.reg(insn, 2, ArgType.NARROW_INTEGRAL));
|
||||||
inode.addArg(InsnArg.typeImmutableIfKnownReg(insn, 0, argType));
|
inode.addArg(InsnArg.typeImmutableIfKnownReg(insn, 0, argType));
|
||||||
return inode;
|
return inode;
|
||||||
|
|||||||
@@ -655,7 +655,7 @@ public abstract class ArgType {
|
|||||||
if (from.equals(to)) {
|
if (from.equals(to)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
TypeCompareEnum result = root.getTypeUpdate().getTypeCompare().compareTypes(from, to);
|
TypeCompareEnum result = root.getTypeCompare().compareTypes(from, to);
|
||||||
return !result.isNarrow();
|
return !result.isNarrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -232,6 +232,27 @@ public abstract class InsnArg extends Typed {
|
|||||||
return contains(AFlag.THIS);
|
return contains(AFlag.THIS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true for 'this' from other classes (often occur in anonymous classes)
|
||||||
|
*/
|
||||||
|
public boolean isAnyThis() {
|
||||||
|
if (contains(AFlag.THIS)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
InsnNode wrappedInsn = unwrap();
|
||||||
|
if (wrappedInsn != null && wrappedInsn.getType() == InsnType.IGET) {
|
||||||
|
return wrappedInsn.getArg(0).isAnyThis();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public InsnNode unwrap() {
|
||||||
|
if (isInsnWrap()) {
|
||||||
|
return ((InsnWrapArg) this).getWrapInsn();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isConst() {
|
public boolean isConst() {
|
||||||
return isLiteral() || (isInsnWrap() && ((InsnWrapArg) this).getWrapInsn().isConstInsn());
|
return isLiteral() || (isInsnWrap() && ((InsnWrapArg) this).getWrapInsn().isConstInsn());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package jadx.core.dex.instructions.args;
|
package jadx.core.dex.instructions.args;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import jadx.core.codegen.TypeGen;
|
import jadx.core.codegen.TypeGen;
|
||||||
import jadx.core.utils.StringUtils;
|
import jadx.core.utils.StringUtils;
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
@@ -57,12 +59,48 @@ public final class LiteralArg extends InsnArg {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isInteger() {
|
public boolean isInteger() {
|
||||||
PrimitiveType type = this.type.getPrimitiveType();
|
switch (type.getPrimitiveType()) {
|
||||||
return type == PrimitiveType.INT
|
case INT:
|
||||||
|| type == PrimitiveType.BYTE
|
case BYTE:
|
||||||
|| type == PrimitiveType.CHAR
|
case CHAR:
|
||||||
|| type == PrimitiveType.SHORT
|
case SHORT:
|
||||||
|| type == PrimitiveType.LONG;
|
case LONG:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isNegative() {
|
||||||
|
if (isInteger()) {
|
||||||
|
return literal < 0;
|
||||||
|
}
|
||||||
|
if (type == ArgType.FLOAT) {
|
||||||
|
float val = Float.intBitsToFloat(((int) literal));
|
||||||
|
return val < 0 && Float.isFinite(val);
|
||||||
|
}
|
||||||
|
if (type == ArgType.DOUBLE) {
|
||||||
|
double val = Double.longBitsToDouble(literal);
|
||||||
|
return val < 0 && Double.isFinite(val);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public LiteralArg negate() {
|
||||||
|
long neg;
|
||||||
|
if (isInteger()) {
|
||||||
|
neg = -literal;
|
||||||
|
} else if (type == ArgType.FLOAT) {
|
||||||
|
float val = Float.intBitsToFloat(((int) literal));
|
||||||
|
neg = Float.floatToIntBits(-val);
|
||||||
|
} else if (type == ArgType.DOUBLE) {
|
||||||
|
double val = Double.longBitsToDouble(literal);
|
||||||
|
neg = Double.doubleToLongBits(-val);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new LiteralArg(neg, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -59,6 +59,11 @@ public class SSAVar {
|
|||||||
return assign;
|
return assign;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public InsnNode getAssignInsn() {
|
||||||
|
return assign.getParentInsn();
|
||||||
|
}
|
||||||
|
|
||||||
public void setAssign(@NotNull RegisterArg assign) {
|
public void setAssign(@NotNull RegisterArg assign) {
|
||||||
this.assign = assign;
|
this.assign = assign;
|
||||||
}
|
}
|
||||||
@@ -187,19 +192,13 @@ public class SSAVar {
|
|||||||
return usedInPhi;
|
return usedInPhi;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isUsedInPhi() {
|
public boolean isAssignInPhi() {
|
||||||
return usedInPhi != null && !usedInPhi.isEmpty();
|
InsnNode assignInsn = getAssignInsn();
|
||||||
|
return assignInsn != null && assignInsn.getType() == InsnType.PHI;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getVariableUseCount() {
|
public boolean isUsedInPhi() {
|
||||||
int count = useList.size();
|
return usedInPhi != null && !usedInPhi.isEmpty();
|
||||||
if (usedInPhi == null) {
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
for (PhiInsn phiInsn : usedInPhi) {
|
|
||||||
count += phiInsn.getResult().getSVar().getUseCount();
|
|
||||||
}
|
|
||||||
return count;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setName(String name) {
|
public void setName(String name) {
|
||||||
|
|||||||
@@ -16,9 +16,11 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import jadx.api.DecompilationMode;
|
||||||
import jadx.api.ICodeCache;
|
import jadx.api.ICodeCache;
|
||||||
import jadx.api.ICodeInfo;
|
import jadx.api.ICodeInfo;
|
||||||
import jadx.api.ICodeWriter;
|
import jadx.api.ICodeWriter;
|
||||||
|
import jadx.api.JadxArgs;
|
||||||
import jadx.api.plugins.input.data.IClassData;
|
import jadx.api.plugins.input.data.IClassData;
|
||||||
import jadx.api.plugins.input.data.IFieldData;
|
import jadx.api.plugins.input.data.IFieldData;
|
||||||
import jadx.api.plugins.input.data.IMethodData;
|
import jadx.api.plugins.input.data.IMethodData;
|
||||||
@@ -33,6 +35,8 @@ import jadx.api.plugins.input.data.impl.ListConsumer;
|
|||||||
import jadx.core.Consts;
|
import jadx.core.Consts;
|
||||||
import jadx.core.ProcessClass;
|
import jadx.core.ProcessClass;
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
|
import jadx.core.dex.attributes.AType;
|
||||||
|
import jadx.core.dex.attributes.nodes.InlinedAttr;
|
||||||
import jadx.core.dex.attributes.nodes.NotificationAttrNode;
|
import jadx.core.dex.attributes.nodes.NotificationAttrNode;
|
||||||
import jadx.core.dex.info.AccessInfo;
|
import jadx.core.dex.info.AccessInfo;
|
||||||
import jadx.core.dex.info.AccessInfo.AFType;
|
import jadx.core.dex.info.AccessInfo.AFType;
|
||||||
@@ -42,12 +46,12 @@ import jadx.core.dex.info.MethodInfo;
|
|||||||
import jadx.core.dex.instructions.args.ArgType;
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
import jadx.core.dex.instructions.args.LiteralArg;
|
import jadx.core.dex.instructions.args.LiteralArg;
|
||||||
import jadx.core.dex.nodes.utils.TypeUtils;
|
import jadx.core.dex.nodes.utils.TypeUtils;
|
||||||
|
import jadx.core.utils.ListUtils;
|
||||||
import jadx.core.utils.Utils;
|
import jadx.core.utils.Utils;
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
import static jadx.core.dex.nodes.ProcessState.LOADED;
|
import static jadx.core.dex.nodes.ProcessState.LOADED;
|
||||||
import static jadx.core.dex.nodes.ProcessState.NOT_LOADED;
|
import static jadx.core.dex.nodes.ProcessState.NOT_LOADED;
|
||||||
import static jadx.core.dex.nodes.ProcessState.PROCESS_COMPLETE;
|
|
||||||
|
|
||||||
public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeNode, Comparable<ClassNode> {
|
public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeNode, Comparable<ClassNode> {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(ClassNode.class);
|
private static final Logger LOG = LoggerFactory.getLogger(ClassNode.class);
|
||||||
@@ -79,6 +83,10 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
|||||||
* Top level classes used in this class (only for top level classes, empty for inners)
|
* Top level classes used in this class (only for top level classes, empty for inners)
|
||||||
*/
|
*/
|
||||||
private List<ClassNode> dependencies = Collections.emptyList();
|
private List<ClassNode> dependencies = Collections.emptyList();
|
||||||
|
/**
|
||||||
|
* Top level classes needed for code generation stage
|
||||||
|
*/
|
||||||
|
private List<ClassNode> codegenDeps = Collections.emptyList();
|
||||||
/**
|
/**
|
||||||
* Classes which uses this class
|
* Classes which uses this class
|
||||||
*/
|
*/
|
||||||
@@ -108,6 +116,10 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
|||||||
ListConsumer<IFieldData, FieldNode> fieldsConsumer = new ListConsumer<>(fld -> FieldNode.build(this, fld));
|
ListConsumer<IFieldData, FieldNode> fieldsConsumer = new ListConsumer<>(fld -> FieldNode.build(this, fld));
|
||||||
ListConsumer<IMethodData, MethodNode> methodsConsumer = new ListConsumer<>(mth -> MethodNode.build(this, mth));
|
ListConsumer<IMethodData, MethodNode> methodsConsumer = new ListConsumer<>(mth -> MethodNode.build(this, mth));
|
||||||
cls.visitFieldsAndMethods(fieldsConsumer, methodsConsumer);
|
cls.visitFieldsAndMethods(fieldsConsumer, methodsConsumer);
|
||||||
|
if (this.fields != null && this.methods != null) {
|
||||||
|
// TODO: temporary solution for restore usage info in reloaded methods and fields
|
||||||
|
restoreUsageData(this.fields, this.methods, fieldsConsumer.getResult(), methodsConsumer.getResult());
|
||||||
|
}
|
||||||
this.fields = fieldsConsumer.getResult();
|
this.fields = fieldsConsumer.getResult();
|
||||||
this.methods = methodsConsumer.getResult();
|
this.methods = methodsConsumer.getResult();
|
||||||
|
|
||||||
@@ -124,6 +136,24 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void restoreUsageData(List<FieldNode> oldFields, List<MethodNode> oldMethods,
|
||||||
|
List<FieldNode> newFields, List<MethodNode> newMethods) {
|
||||||
|
Map<FieldInfo, FieldNode> oldFieldMap = Utils.groupBy(oldFields, FieldNode::getFieldInfo);
|
||||||
|
for (FieldNode newField : newFields) {
|
||||||
|
FieldNode oldField = oldFieldMap.get(newField.getFieldInfo());
|
||||||
|
if (oldField != null) {
|
||||||
|
newField.setUseIn(oldField.getUseIn());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Map<MethodInfo, MethodNode> oldMethodsMap = Utils.groupBy(oldMethods, MethodNode::getMethodInfo);
|
||||||
|
for (MethodNode newMethod : newMethods) {
|
||||||
|
MethodNode oldMethod = oldMethodsMap.get(newMethod.getMethodInfo());
|
||||||
|
if (oldMethod != null) {
|
||||||
|
newMethod.setUseIn(oldMethod.getUseIn());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private ArgType checkSuperType(IClassData cls) {
|
private ArgType checkSuperType(IClassData cls) {
|
||||||
String superType = cls.getSuperType();
|
String superType = cls.getSuperType();
|
||||||
if (superType == null) {
|
if (superType == null) {
|
||||||
@@ -261,12 +291,15 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean checkProcessed() {
|
||||||
|
return getTopParentClass().getState().isProcessComplete();
|
||||||
|
}
|
||||||
|
|
||||||
public void ensureProcessed() {
|
public void ensureProcessed() {
|
||||||
ClassNode topClass = getTopParentClass();
|
if (!checkProcessed()) {
|
||||||
ProcessState state = topClass.getState();
|
ClassNode topParentClass = getTopParentClass();
|
||||||
if (state != PROCESS_COMPLETE) {
|
|
||||||
throw new JadxRuntimeException("Expected class to be processed at this point,"
|
throw new JadxRuntimeException("Expected class to be processed at this point,"
|
||||||
+ " class: " + topClass + ", state: " + state);
|
+ " class: " + topParentClass + ", state: " + topParentClass.getState());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -274,6 +307,26 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
|||||||
return decompile(true);
|
return decompile(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WARNING: Slow operation! Use with caution!
|
||||||
|
*/
|
||||||
|
public ICodeInfo decompileWithMode(DecompilationMode mode) {
|
||||||
|
DecompilationMode baseMode = root.getArgs().getDecompilationMode();
|
||||||
|
if (mode == baseMode) {
|
||||||
|
return decompile(true);
|
||||||
|
}
|
||||||
|
JadxArgs args = root.getArgs();
|
||||||
|
try {
|
||||||
|
unload();
|
||||||
|
args.setDecompilationMode(mode);
|
||||||
|
ProcessClass process = new ProcessClass(args);
|
||||||
|
process.initPasses(root);
|
||||||
|
return process.generateCode(this);
|
||||||
|
} finally {
|
||||||
|
args.setDecompilationMode(baseMode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public ICodeInfo getCode() {
|
public ICodeInfo getCode() {
|
||||||
return decompile(true);
|
return decompile(true);
|
||||||
}
|
}
|
||||||
@@ -325,11 +378,22 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
|||||||
return code;
|
return code;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ICodeInfo codeInfo = ProcessClass.generateCode(this);
|
ICodeInfo codeInfo = root.getProcessClasses().generateCode(this);
|
||||||
codeCache.add(clsRawName, codeInfo);
|
codeCache.add(clsRawName, codeInfo);
|
||||||
return codeInfo;
|
return codeInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public ICodeInfo getCodeFromCache() {
|
||||||
|
ICodeCache codeCache = root().getCodeCache();
|
||||||
|
String clsRawName = getRawName();
|
||||||
|
ICodeInfo codeInfo = codeCache.get(clsRawName);
|
||||||
|
if (codeInfo == ICodeInfo.EMPTY) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return codeInfo;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void load() {
|
public void load() {
|
||||||
for (MethodNode mth : getMethods()) {
|
for (MethodNode mth : getMethods()) {
|
||||||
@@ -535,6 +599,10 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
|||||||
return innerClasses;
|
return innerClasses;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<ClassNode> getInlinedClasses() {
|
||||||
|
return inlinedClasses;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all inner and inlined classes recursively
|
* Get all inner and inlined classes recursively
|
||||||
*
|
*
|
||||||
@@ -566,6 +634,7 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
|||||||
if (inlinedClasses.isEmpty()) {
|
if (inlinedClasses.isEmpty()) {
|
||||||
inlinedClasses = new ArrayList<>(5);
|
inlinedClasses = new ArrayList<>(5);
|
||||||
}
|
}
|
||||||
|
cls.addAttr(new InlinedAttr(this));
|
||||||
inlinedClasses.add(cls);
|
inlinedClasses.add(cls);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -576,7 +645,7 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isAnonymous() {
|
public boolean isAnonymous() {
|
||||||
return contains(AFlag.ANONYMOUS_CLASS);
|
return contains(AType.ANONYMOUS_CLASS);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isInner() {
|
public boolean isInner() {
|
||||||
@@ -707,6 +776,26 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
|||||||
this.dependencies = dependencies;
|
this.dependencies = dependencies;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void removeDependency(ClassNode dep) {
|
||||||
|
this.dependencies = ListUtils.safeRemoveAndTrim(this.dependencies, dep);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ClassNode> getCodegenDeps() {
|
||||||
|
return codegenDeps;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCodegenDeps(List<ClassNode> codegenDeps) {
|
||||||
|
this.codegenDeps = codegenDeps;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addCodegenDep(ClassNode dep) {
|
||||||
|
this.codegenDeps = ListUtils.safeAdd(this.codegenDeps, dep);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTotalDepsCount() {
|
||||||
|
return dependencies.size() + codegenDeps.size();
|
||||||
|
}
|
||||||
|
|
||||||
public List<ClassNode> getUseIn() {
|
public List<ClassNode> getUseIn() {
|
||||||
return useIn;
|
return useIn;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import jadx.core.dex.info.AccessInfo;
|
|||||||
import jadx.core.dex.info.AccessInfo.AFType;
|
import jadx.core.dex.info.AccessInfo.AFType;
|
||||||
import jadx.core.dex.info.FieldInfo;
|
import jadx.core.dex.info.FieldInfo;
|
||||||
import jadx.core.dex.instructions.args.ArgType;
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
|
import jadx.core.utils.ListUtils;
|
||||||
|
|
||||||
public class FieldNode extends NotificationAttrNode implements ICodeNode {
|
public class FieldNode extends NotificationAttrNode implements ICodeNode {
|
||||||
|
|
||||||
@@ -80,6 +81,10 @@ public class FieldNode extends NotificationAttrNode implements ICodeNode {
|
|||||||
this.useIn = useIn;
|
this.useIn = useIn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public synchronized void addUseIn(MethodNode mth) {
|
||||||
|
useIn = ListUtils.safeAdd(useIn, mth);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String typeName() {
|
public String typeName() {
|
||||||
return "field";
|
return "field";
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package jadx.core.dex.nodes;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import jadx.core.dex.regions.conditions.IfCondition;
|
||||||
|
|
||||||
|
public interface IConditionRegion extends IRegion {
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
IfCondition getCondition();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Blocks merged into condition
|
||||||
|
* Needed for backtracking
|
||||||
|
* TODO: merge into condition object ???
|
||||||
|
*/
|
||||||
|
List<BlockNode> getConditionBlocks();
|
||||||
|
|
||||||
|
void invertCondition();
|
||||||
|
|
||||||
|
boolean simplifyCondition();
|
||||||
|
|
||||||
|
int getConditionSourceLine();
|
||||||
|
}
|
||||||
@@ -120,12 +120,7 @@ public class InsnNode extends LineAttrNode {
|
|||||||
if (getArgsCount() == 0) {
|
if (getArgsCount() == 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for (InsnArg insnArg : arguments) {
|
return InsnUtils.containsVar(arguments, arg);
|
||||||
if (insnArg == arg || arg.sameRegAndSVar(insnArg)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -327,6 +322,40 @@ public class InsnNode extends LineAttrNode {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Visit all args recursively (including inner instructions), but excluding wrapped args
|
||||||
|
*/
|
||||||
|
public void visitArgs(Consumer<InsnArg> visitor) {
|
||||||
|
for (InsnArg arg : getArguments()) {
|
||||||
|
if (arg.isInsnWrap()) {
|
||||||
|
((InsnWrapArg) arg).getWrapInsn().visitArgs(visitor);
|
||||||
|
} else {
|
||||||
|
visitor.accept(arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Visit all args recursively (including inner instructions), but excluding wrapped args.
|
||||||
|
* To terminate visiting return non-null value
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public <R> R visitArgs(Function<InsnArg, R> visitor) {
|
||||||
|
for (InsnArg arg : getArguments()) {
|
||||||
|
R result;
|
||||||
|
if (arg.isInsnWrap()) {
|
||||||
|
InsnNode wrapInsn = ((InsnWrapArg) arg).getWrapInsn();
|
||||||
|
result = wrapInsn.visitArgs(visitor);
|
||||||
|
} else {
|
||||||
|
result = visitor.apply(arg);
|
||||||
|
}
|
||||||
|
if (result != null) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 'Soft' equals, don't compare arguments, only instruction specific parameters.
|
* 'Soft' equals, don't compare arguments, only instruction specific parameters.
|
||||||
*/
|
*/
|
||||||
@@ -390,17 +419,16 @@ public class InsnNode extends LineAttrNode {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Make copy of InsnNode object.
|
* Make copy of InsnNode object.
|
||||||
* <p>
|
* <br>
|
||||||
* NOTE: can't copy instruction with result argument
|
* NOTE: can't copy instruction with result argument
|
||||||
* (SSA variable can't be used in two different assigns).
|
* (SSA variable can't be used in two different assigns).
|
||||||
* <p>
|
* <br>
|
||||||
* Prefer use next methods:
|
* Prefer use next methods:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>{@link #copyWithoutResult()} to explicitly state that result not needed
|
* <li>{@link #copyWithoutResult()} to explicitly state that result not needed
|
||||||
* <li>{@link #copy(RegisterArg)} to provide new result arg
|
* <li>{@link #copy(RegisterArg)} to provide new result arg
|
||||||
* <li>{@link #copyWithNewSsaVar(MethodNode)} to make new SSA variable for result arg
|
* <li>{@link #copyWithNewSsaVar(MethodNode)} to make new SSA variable for result arg
|
||||||
* </ul>
|
* </ul>
|
||||||
* <p>
|
|
||||||
*/
|
*/
|
||||||
public InsnNode copy() {
|
public InsnNode copy() {
|
||||||
if (this.getClass() != InsnNode.class) {
|
if (this.getClass() != InsnNode.class) {
|
||||||
|
|||||||
@@ -99,9 +99,6 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
|
|||||||
@Override
|
@Override
|
||||||
public void unload() {
|
public void unload() {
|
||||||
loaded = false;
|
loaded = false;
|
||||||
if (noCode) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// don't unload retType, argTypes, typeParameters
|
// don't unload retType, argTypes, typeParameters
|
||||||
thisArg = null;
|
thisArg = null;
|
||||||
argsList = null;
|
argsList = null;
|
||||||
@@ -339,6 +336,14 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
|
|||||||
return exitBlock.getPredecessors();
|
return exitBlock.getPredecessors();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isPreExitBlocks(BlockNode block) {
|
||||||
|
List<BlockNode> successors = block.getSuccessors();
|
||||||
|
if (successors.size() == 1) {
|
||||||
|
return successors.get(0).equals(exitBlock);
|
||||||
|
}
|
||||||
|
return exitBlock.getPredecessors().contains(block);
|
||||||
|
}
|
||||||
|
|
||||||
public void registerLoop(LoopInfo loop) {
|
public void registerLoop(LoopInfo loop) {
|
||||||
if (loops.isEmpty()) {
|
if (loops.isEmpty()) {
|
||||||
loops = new ArrayList<>(5);
|
loops = new ArrayList<>(5);
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import jadx.api.data.ICodeData;
|
|||||||
import jadx.api.plugins.input.data.IClassData;
|
import jadx.api.plugins.input.data.IClassData;
|
||||||
import jadx.api.plugins.input.data.ILoadResult;
|
import jadx.api.plugins.input.data.ILoadResult;
|
||||||
import jadx.core.Jadx;
|
import jadx.core.Jadx;
|
||||||
|
import jadx.core.ProcessClass;
|
||||||
import jadx.core.clsp.ClspGraph;
|
import jadx.core.clsp.ClspGraph;
|
||||||
import jadx.core.dex.info.ClassInfo;
|
import jadx.core.dex.info.ClassInfo;
|
||||||
import jadx.core.dex.info.ConstStorage;
|
import jadx.core.dex.info.ConstStorage;
|
||||||
@@ -51,9 +52,9 @@ public class RootNode {
|
|||||||
|
|
||||||
private final JadxArgs args;
|
private final JadxArgs args;
|
||||||
private final List<IDexTreeVisitor> preDecompilePasses;
|
private final List<IDexTreeVisitor> preDecompilePasses;
|
||||||
private final List<IDexTreeVisitor> passes;
|
|
||||||
private final List<ICodeDataUpdateListener> codeDataUpdateListeners = new ArrayList<>();
|
private final List<ICodeDataUpdateListener> codeDataUpdateListeners = new ArrayList<>();
|
||||||
|
|
||||||
|
private final ProcessClass processClasses;
|
||||||
private final ErrorsCounter errorsCounter = new ErrorsCounter();
|
private final ErrorsCounter errorsCounter = new ErrorsCounter();
|
||||||
private final StringUtils stringUtils;
|
private final StringUtils stringUtils;
|
||||||
private final ConstStorage constValues;
|
private final ConstStorage constValues;
|
||||||
@@ -76,7 +77,7 @@ public class RootNode {
|
|||||||
public RootNode(JadxArgs args) {
|
public RootNode(JadxArgs args) {
|
||||||
this.args = args;
|
this.args = args;
|
||||||
this.preDecompilePasses = Jadx.getPreDecompilePassesList();
|
this.preDecompilePasses = Jadx.getPreDecompilePassesList();
|
||||||
this.passes = Jadx.getPassesList(args);
|
this.processClasses = new ProcessClass(this.getArgs());
|
||||||
this.stringUtils = new StringUtils(args);
|
this.stringUtils = new StringUtils(args);
|
||||||
this.constValues = new ConstStorage(args);
|
this.constValues = new ConstStorage(args);
|
||||||
this.typeUpdate = new TypeUpdate(this);
|
this.typeUpdate = new TypeUpdate(this);
|
||||||
@@ -334,6 +335,12 @@ public class RootNode {
|
|||||||
return resolveClass(clsInfo);
|
return resolveClass(clsInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches for ClassNode by its full name (original or alias name)
|
||||||
|
* <br>
|
||||||
|
* Warning: This method has a runtime of O(n) (n = number of classes).
|
||||||
|
* If you need to call it more than once consider {@link #buildFullAliasClassCache()} instead
|
||||||
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
public ClassNode searchClassByFullAlias(String fullName) {
|
public ClassNode searchClassByFullAlias(String fullName) {
|
||||||
for (ClassNode cls : classes) {
|
for (ClassNode cls : classes) {
|
||||||
@@ -346,6 +353,20 @@ public class RootNode {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Map<String, ClassNode> buildFullAliasClassCache() {
|
||||||
|
Map<String, ClassNode> classNameCache = new HashMap<>(classes.size());
|
||||||
|
for (ClassNode cls : classes) {
|
||||||
|
ClassInfo classInfo = cls.getClassInfo();
|
||||||
|
String fullName = classInfo.getFullName();
|
||||||
|
String alias = classInfo.getAliasFullName();
|
||||||
|
classNameCache.put(fullName, cls);
|
||||||
|
if (alias != null && !fullName.equals(alias)) {
|
||||||
|
classNameCache.put(alias, cls);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return classNameCache;
|
||||||
|
}
|
||||||
|
|
||||||
public List<ClassNode> searchClassByShortName(String shortName) {
|
public List<ClassNode> searchClassByShortName(String shortName) {
|
||||||
List<ClassNode> list = new ArrayList<>();
|
List<ClassNode> list = new ArrayList<>();
|
||||||
for (ClassNode cls : classes) {
|
for (ClassNode cls : classes) {
|
||||||
@@ -358,15 +379,6 @@ public class RootNode {
|
|||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public MethodNode resolveMethod(@NotNull MethodInfo mth) {
|
public MethodNode resolveMethod(@NotNull MethodInfo mth) {
|
||||||
ClassNode cls = resolveClass(mth.getDeclClass());
|
|
||||||
if (cls != null) {
|
|
||||||
return cls.searchMethod(mth);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public MethodNode deepResolveMethod(@NotNull MethodInfo mth) {
|
|
||||||
ClassNode cls = resolveClass(mth.getDeclClass());
|
ClassNode cls = resolveClass(mth.getDeclClass());
|
||||||
if (cls == null) {
|
if (cls == null) {
|
||||||
return null;
|
return null;
|
||||||
@@ -410,19 +422,14 @@ public class RootNode {
|
|||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public FieldNode resolveField(FieldInfo field) {
|
public FieldNode resolveField(FieldInfo field) {
|
||||||
ClassNode cls = resolveClass(field.getDeclClass());
|
|
||||||
if (cls != null) {
|
|
||||||
return cls.searchField(field);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public FieldNode deepResolveField(@NotNull FieldInfo field) {
|
|
||||||
ClassNode cls = resolveClass(field.getDeclClass());
|
ClassNode cls = resolveClass(field.getDeclClass());
|
||||||
if (cls == null) {
|
if (cls == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
FieldNode fieldNode = cls.searchField(field);
|
||||||
|
if (fieldNode != null) {
|
||||||
|
return fieldNode;
|
||||||
|
}
|
||||||
return deepResolveField(cls, field);
|
return deepResolveField(cls, field);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -454,18 +461,16 @@ public class RootNode {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ProcessClass getProcessClasses() {
|
||||||
|
return processClasses;
|
||||||
|
}
|
||||||
|
|
||||||
public List<IDexTreeVisitor> getPasses() {
|
public List<IDexTreeVisitor> getPasses() {
|
||||||
return passes;
|
return processClasses.getPasses();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initPasses() {
|
public void initPasses() {
|
||||||
for (IDexTreeVisitor pass : passes) {
|
processClasses.initPasses(this);
|
||||||
try {
|
|
||||||
pass.init(this);
|
|
||||||
} catch (Exception e) {
|
|
||||||
LOG.error("Visitor init failed: {}", pass.getClass().getSimpleName(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ICodeWriter makeCodeWriter() {
|
public ICodeWriter makeCodeWriter() {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package jadx.core.dex.nodes.parser;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
@@ -197,6 +196,8 @@ public class SignatureParser {
|
|||||||
String obj = slice();
|
String obj = slice();
|
||||||
if (!innerType) {
|
if (!innerType) {
|
||||||
obj += ';';
|
obj += ';';
|
||||||
|
} else {
|
||||||
|
obj = obj.replace('/', '.');
|
||||||
}
|
}
|
||||||
List<ArgType> typeVars = consumeGenericArgs();
|
List<ArgType> typeVars = consumeGenericArgs();
|
||||||
consume('>');
|
consume('>');
|
||||||
@@ -227,7 +228,7 @@ public class SignatureParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private List<ArgType> consumeGenericArgs() {
|
private List<ArgType> consumeGenericArgs() {
|
||||||
List<ArgType> list = new LinkedList<>();
|
List<ArgType> list = new ArrayList<>();
|
||||||
ArgType type;
|
ArgType type;
|
||||||
do {
|
do {
|
||||||
if (lookAhead('*')) {
|
if (lookAhead('*')) {
|
||||||
@@ -251,8 +252,8 @@ public class SignatureParser {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Map of generic types names to extends classes.
|
* Map of generic types names to extends classes.
|
||||||
* <p/>
|
* <p>
|
||||||
* Example: "<T:Ljava/lang/Exception;:Ljava/lang/Object;>"
|
* Example: "<T:Ljava/lang/Exception;:Ljava/lang/Object;>"
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("ConditionalBreakInInfiniteLoop")
|
@SuppressWarnings("ConditionalBreakInInfiniteLoop")
|
||||||
public List<ArgType> consumeGenericTypeParameters() {
|
public List<ArgType> consumeGenericTypeParameters() {
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
import jadx.core.clsp.ClspClass;
|
import jadx.core.clsp.ClspClass;
|
||||||
import jadx.core.clsp.ClspMethod;
|
import jadx.core.clsp.ClspMethod;
|
||||||
import jadx.core.dex.attributes.AType;
|
import jadx.core.dex.attributes.AType;
|
||||||
|
import jadx.core.dex.attributes.nodes.MethodBridgeAttr;
|
||||||
|
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
|
||||||
|
import jadx.core.dex.info.ClassInfo;
|
||||||
import jadx.core.dex.info.MethodInfo;
|
import jadx.core.dex.info.MethodInfo;
|
||||||
import jadx.core.dex.instructions.BaseInvokeNode;
|
import jadx.core.dex.instructions.BaseInvokeNode;
|
||||||
import jadx.core.dex.instructions.args.ArgType;
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
@@ -15,6 +18,7 @@ import jadx.core.dex.nodes.ClassNode;
|
|||||||
import jadx.core.dex.nodes.IMethodDetails;
|
import jadx.core.dex.nodes.IMethodDetails;
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
import jadx.core.dex.nodes.RootNode;
|
import jadx.core.dex.nodes.RootNode;
|
||||||
|
import jadx.core.utils.Utils;
|
||||||
|
|
||||||
public class MethodUtils {
|
public class MethodUtils {
|
||||||
private final RootNode root;
|
private final RootNode root;
|
||||||
@@ -34,7 +38,7 @@ public class MethodUtils {
|
|||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public IMethodDetails getMethodDetails(MethodInfo callMth) {
|
public IMethodDetails getMethodDetails(MethodInfo callMth) {
|
||||||
MethodNode mthNode = root.deepResolveMethod(callMth);
|
MethodNode mthNode = root.resolveMethod(callMth);
|
||||||
if (mthNode != null) {
|
if (mthNode != null) {
|
||||||
return mthNode;
|
return mthNode;
|
||||||
}
|
}
|
||||||
@@ -67,7 +71,7 @@ public class MethodUtils {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean processMethodArgsOverloaded(ArgType startCls, MethodInfo mthInfo, @Nullable List<IMethodDetails> collectedMths) {
|
private boolean processMethodArgsOverloaded(ArgType startCls, MethodInfo mthInfo, @Nullable List<IMethodDetails> collectedMths) {
|
||||||
if (startCls == null || !startCls.isObject()) {
|
if (startCls == null || !startCls.isObject()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -122,4 +126,25 @@ public class MethodUtils {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public IMethodDetails getOverrideBaseMth(MethodNode mth) {
|
||||||
|
MethodOverrideAttr overrideAttr = mth.get(AType.METHOD_OVERRIDE);
|
||||||
|
if (overrideAttr == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return Utils.getOne(overrideAttr.getBaseMethods());
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClassInfo getMethodOriginDeclClass(MethodNode mth) {
|
||||||
|
IMethodDetails baseMth = getOverrideBaseMth(mth);
|
||||||
|
if (baseMth != null) {
|
||||||
|
return baseMth.getMethodInfo().getDeclClass();
|
||||||
|
}
|
||||||
|
MethodBridgeAttr bridgeAttr = mth.get(AType.BRIDGED_BY);
|
||||||
|
if (bridgeAttr != null) {
|
||||||
|
return getMethodOriginDeclClass(bridgeAttr.getBridgeMth());
|
||||||
|
}
|
||||||
|
return mth.getMethodInfo().getDeclClass();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ public class TypeUtils {
|
|||||||
|
|
||||||
public ArgType expandTypeVariables(ClassNode cls, ArgType type) {
|
public ArgType expandTypeVariables(ClassNode cls, ArgType type) {
|
||||||
if (type.containsTypeVariable()) {
|
if (type.containsTypeVariable()) {
|
||||||
expandTypeVar(cls, type, cls.getGenericTypeParameters());
|
expandTypeVar(cls, type, getKnownTypeVarsAtClass(cls));
|
||||||
}
|
}
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
@@ -115,11 +115,18 @@ public class TypeUtils {
|
|||||||
return varsAttr.getTypeVars();
|
return varsAttr.getTypeVars();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Set<ArgType> collectKnownTypeVarsAtMethod(MethodNode mth) {
|
private static Collection<ArgType> getKnownTypeVarsAtClass(ClassNode cls) {
|
||||||
ClassNode declCls = mth.getParentClass();
|
if (cls.isInner()) {
|
||||||
Set<ArgType> typeVars = new HashSet<>(declCls.getGenericTypeParameters());
|
Set<ArgType> typeVars = new HashSet<>(cls.getGenericTypeParameters());
|
||||||
declCls.visitParentClasses(parent -> typeVars.addAll(parent.getGenericTypeParameters()));
|
cls.visitParentClasses(parent -> typeVars.addAll(parent.getGenericTypeParameters()));
|
||||||
|
return typeVars;
|
||||||
|
}
|
||||||
|
return cls.getGenericTypeParameters();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Set<ArgType> collectKnownTypeVarsAtMethod(MethodNode mth) {
|
||||||
|
Set<ArgType> typeVars = new HashSet<>();
|
||||||
|
typeVars.addAll(getKnownTypeVarsAtClass(mth.getParentClass()));
|
||||||
typeVars.addAll(mth.getTypeParameters());
|
typeVars.addAll(mth.getTypeParameters());
|
||||||
return typeVars.isEmpty() ? Collections.emptySet() : typeVars;
|
return typeVars.isEmpty() ? Collections.emptySet() : typeVars;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import jadx.core.dex.attributes.AFlag;
|
|||||||
import jadx.core.dex.instructions.IfNode;
|
import jadx.core.dex.instructions.IfNode;
|
||||||
import jadx.core.dex.instructions.IfOp;
|
import jadx.core.dex.instructions.IfOp;
|
||||||
import jadx.core.dex.instructions.args.InsnArg;
|
import jadx.core.dex.instructions.args.InsnArg;
|
||||||
import jadx.core.dex.instructions.args.LiteralArg;
|
|
||||||
|
|
||||||
public final class Compare {
|
public final class Compare {
|
||||||
private final IfNode insn;
|
private final IfNode insn;
|
||||||
@@ -35,13 +34,8 @@ public final class Compare {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Change 'a != false' to 'a == true'
|
|
||||||
*/
|
|
||||||
public void normalize() {
|
public void normalize() {
|
||||||
if (getOp() == IfOp.NE && getB().isFalse()) {
|
insn.normalize();
|
||||||
insn.changeCondition(IfOp.EQ, getA(), LiteralArg.litTrue());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
package jadx.core.dex.regions.conditions;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import jadx.core.dex.nodes.BlockNode;
|
||||||
|
import jadx.core.dex.nodes.IConditionRegion;
|
||||||
|
import jadx.core.dex.nodes.IRegion;
|
||||||
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
|
import jadx.core.dex.regions.AbstractRegion;
|
||||||
|
import jadx.core.utils.BlockUtils;
|
||||||
|
|
||||||
|
public abstract class ConditionRegion extends AbstractRegion implements IConditionRegion {
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private IfCondition condition;
|
||||||
|
private List<BlockNode> conditionBlocks = Collections.emptyList();
|
||||||
|
|
||||||
|
public ConditionRegion(IRegion parent) {
|
||||||
|
super(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public IfCondition getCondition() {
|
||||||
|
return condition;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<BlockNode> getConditionBlocks() {
|
||||||
|
return conditionBlocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invertCondition() {
|
||||||
|
if (condition != null) {
|
||||||
|
condition = IfCondition.invert(condition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean simplifyCondition() {
|
||||||
|
if (condition == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
IfCondition updated = IfCondition.simplify(condition);
|
||||||
|
if (updated != condition) {
|
||||||
|
condition = updated;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getConditionSourceLine() {
|
||||||
|
for (BlockNode block : conditionBlocks) {
|
||||||
|
InsnNode lastInsn = BlockUtils.getLastInsn(block);
|
||||||
|
if (lastInsn != null) {
|
||||||
|
int sourceLine = lastInsn.getSourceLine();
|
||||||
|
if (sourceLine != 0) {
|
||||||
|
return sourceLine;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prefer way for update condition info
|
||||||
|
*/
|
||||||
|
public void updateCondition(IfInfo info) {
|
||||||
|
this.condition = info.getCondition();
|
||||||
|
this.conditionBlocks = info.getMergedBlocks();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateCondition(IfCondition condition, List<BlockNode> conditionBlocks) {
|
||||||
|
this.condition = condition;
|
||||||
|
this.conditionBlocks = conditionBlocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateCondition(BlockNode block) {
|
||||||
|
this.condition = IfCondition.fromIfBlock(block);
|
||||||
|
this.conditionBlocks = Collections.singletonList(block);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -272,6 +272,12 @@ public final class IfCondition extends AttrNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<InsnNode> collectInsns() {
|
||||||
|
List<InsnNode> list = new ArrayList<>();
|
||||||
|
visitInsns(list::add);
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public InsnNode getFirstInsn() {
|
public InsnNode getFirstInsn() {
|
||||||
if (mode == Mode.COMPARE) {
|
if (mode == Mode.COMPARE) {
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package jadx.core.dex.regions.conditions;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import jadx.api.ICodeWriter;
|
import jadx.api.ICodeWriter;
|
||||||
import jadx.core.codegen.RegionGen;
|
import jadx.core.codegen.RegionGen;
|
||||||
@@ -11,16 +10,9 @@ import jadx.core.dex.nodes.BlockNode;
|
|||||||
import jadx.core.dex.nodes.IBranchRegion;
|
import jadx.core.dex.nodes.IBranchRegion;
|
||||||
import jadx.core.dex.nodes.IContainer;
|
import jadx.core.dex.nodes.IContainer;
|
||||||
import jadx.core.dex.nodes.IRegion;
|
import jadx.core.dex.nodes.IRegion;
|
||||||
import jadx.core.dex.nodes.InsnNode;
|
|
||||||
import jadx.core.dex.regions.AbstractRegion;
|
|
||||||
import jadx.core.utils.BlockUtils;
|
|
||||||
import jadx.core.utils.exceptions.CodegenException;
|
import jadx.core.utils.exceptions.CodegenException;
|
||||||
|
|
||||||
public final class IfRegion extends AbstractRegion implements IBranchRegion {
|
public final class IfRegion extends ConditionRegion implements IBranchRegion {
|
||||||
|
|
||||||
private List<BlockNode> conditionBlocks;
|
|
||||||
|
|
||||||
private IfCondition condition;
|
|
||||||
private IContainer thenRegion;
|
private IContainer thenRegion;
|
||||||
private IContainer elseRegion;
|
private IContainer elseRegion;
|
||||||
|
|
||||||
@@ -28,14 +20,6 @@ public final class IfRegion extends AbstractRegion implements IBranchRegion {
|
|||||||
super(parent);
|
super(parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IfCondition getCondition() {
|
|
||||||
return condition;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCondition(IfCondition condition) {
|
|
||||||
this.condition = condition;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IContainer getThenRegion() {
|
public IContainer getThenRegion() {
|
||||||
return thenRegion;
|
return thenRegion;
|
||||||
}
|
}
|
||||||
@@ -52,31 +36,8 @@ public final class IfRegion extends AbstractRegion implements IBranchRegion {
|
|||||||
this.elseRegion = elseRegion;
|
this.elseRegion = elseRegion;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<BlockNode> getConditionBlocks() {
|
|
||||||
return conditionBlocks;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setConditionBlocks(List<BlockNode> conditionBlocks) {
|
|
||||||
this.conditionBlocks = conditionBlocks;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setConditionBlocks(Set<BlockNode> conditionBlocks) {
|
|
||||||
List<BlockNode> list = new ArrayList<>(conditionBlocks);
|
|
||||||
Collections.sort(list);
|
|
||||||
this.conditionBlocks = list;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean simplifyCondition() {
|
|
||||||
IfCondition cond = IfCondition.simplify(condition);
|
|
||||||
if (cond != condition) {
|
|
||||||
condition = cond;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void invert() {
|
public void invert() {
|
||||||
condition = IfCondition.invert(condition);
|
invertCondition();
|
||||||
// swap regions
|
// swap regions
|
||||||
IContainer tmp = thenRegion;
|
IContainer tmp = thenRegion;
|
||||||
thenRegion = elseRegion;
|
thenRegion = elseRegion;
|
||||||
@@ -84,20 +45,12 @@ public final class IfRegion extends AbstractRegion implements IBranchRegion {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public int getSourceLine() {
|
public int getSourceLine() {
|
||||||
for (BlockNode block : conditionBlocks) {
|
return getConditionSourceLine();
|
||||||
InsnNode lastInsn = BlockUtils.getLastInsn(block);
|
|
||||||
if (lastInsn != null) {
|
|
||||||
int sourceLine = lastInsn.getSourceLine();
|
|
||||||
if (sourceLine != 0) {
|
|
||||||
return sourceLine;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<IContainer> getSubBlocks() {
|
public List<IContainer> getSubBlocks() {
|
||||||
|
List<BlockNode> conditionBlocks = getConditionBlocks();
|
||||||
List<IContainer> all = new ArrayList<>(conditionBlocks.size() + 2);
|
List<IContainer> all = new ArrayList<>(conditionBlocks.size() + 2);
|
||||||
all.addAll(conditionBlocks);
|
all.addAll(conditionBlocks);
|
||||||
if (thenRegion != null) {
|
if (thenRegion != null) {
|
||||||
@@ -151,6 +104,6 @@ public final class IfRegion extends AbstractRegion implements IBranchRegion {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "IF " + conditionBlocks + " THEN: " + thenRegion + " ELSE: " + elseRegion;
|
return "IF " + getConditionBlocks() + " THEN: " + thenRegion + " ELSE: " + elseRegion;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package jadx.core.dex.regions.loops;
|
package jadx.core.dex.regions.loops;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
@@ -9,55 +8,45 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
import jadx.api.ICodeWriter;
|
import jadx.api.ICodeWriter;
|
||||||
import jadx.core.codegen.RegionGen;
|
import jadx.core.codegen.RegionGen;
|
||||||
import jadx.core.dex.attributes.nodes.LoopInfo;
|
import jadx.core.dex.attributes.nodes.LoopInfo;
|
||||||
import jadx.core.dex.instructions.IfNode;
|
|
||||||
import jadx.core.dex.instructions.args.RegisterArg;
|
import jadx.core.dex.instructions.args.RegisterArg;
|
||||||
import jadx.core.dex.nodes.BlockNode;
|
import jadx.core.dex.nodes.BlockNode;
|
||||||
import jadx.core.dex.nodes.IContainer;
|
import jadx.core.dex.nodes.IContainer;
|
||||||
import jadx.core.dex.nodes.IRegion;
|
import jadx.core.dex.nodes.IRegion;
|
||||||
import jadx.core.dex.nodes.InsnNode;
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
import jadx.core.dex.regions.AbstractRegion;
|
import jadx.core.dex.regions.conditions.ConditionRegion;
|
||||||
import jadx.core.dex.regions.conditions.IfCondition;
|
import jadx.core.dex.regions.conditions.IfCondition;
|
||||||
import jadx.core.utils.BlockUtils;
|
import jadx.core.utils.BlockUtils;
|
||||||
|
import jadx.core.utils.InsnUtils;
|
||||||
import jadx.core.utils.exceptions.CodegenException;
|
import jadx.core.utils.exceptions.CodegenException;
|
||||||
|
|
||||||
public final class LoopRegion extends AbstractRegion {
|
public final class LoopRegion extends ConditionRegion {
|
||||||
|
|
||||||
private final LoopInfo info;
|
private final LoopInfo info;
|
||||||
/**
|
|
||||||
* loop header contains one 'if' insn, equals null for infinite loop
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
private IfCondition condition;
|
|
||||||
private final BlockNode conditionBlock;
|
|
||||||
// instruction which must be executed before condition in every loop
|
|
||||||
private BlockNode preCondition;
|
|
||||||
private IRegion body;
|
|
||||||
private final boolean conditionAtEnd;
|
private final boolean conditionAtEnd;
|
||||||
|
private final @Nullable BlockNode header;
|
||||||
|
// instruction which must be executed before condition in every loop
|
||||||
|
private @Nullable BlockNode preCondition;
|
||||||
|
|
||||||
|
private IRegion body;
|
||||||
private LoopType type;
|
private LoopType type;
|
||||||
|
|
||||||
public LoopRegion(IRegion parent, LoopInfo info, @Nullable BlockNode header, boolean reversed) {
|
public LoopRegion(IRegion parent, LoopInfo info, @Nullable BlockNode header, boolean reversed) {
|
||||||
super(parent);
|
super(parent);
|
||||||
this.info = info;
|
this.info = info;
|
||||||
this.conditionBlock = header;
|
this.header = header;
|
||||||
this.condition = IfCondition.fromIfBlock(header);
|
|
||||||
this.conditionAtEnd = reversed;
|
this.conditionAtEnd = reversed;
|
||||||
|
if (header != null) {
|
||||||
|
updateCondition(header);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public LoopInfo getInfo() {
|
public LoopInfo getInfo() {
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IfCondition getCondition() {
|
@Nullable
|
||||||
return condition;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCondition(IfCondition condition) {
|
|
||||||
this.condition = condition;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BlockNode getHeader() {
|
public BlockNode getHeader() {
|
||||||
return conditionBlock;
|
return header;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IRegion getBody() {
|
public IRegion getBody() {
|
||||||
@@ -79,10 +68,6 @@ public final class LoopRegion extends AbstractRegion {
|
|||||||
this.preCondition = preCondition;
|
this.preCondition = preCondition;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IfNode getIfInsn() {
|
|
||||||
return (IfNode) BlockUtils.getLastInsn(conditionBlock);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if pre-conditions can be inlined into loop condition
|
* Check if pre-conditions can be inlined into loop condition
|
||||||
*/
|
*/
|
||||||
@@ -91,7 +76,14 @@ public final class LoopRegion extends AbstractRegion {
|
|||||||
if (insns.isEmpty()) {
|
if (insns.isEmpty()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
IfNode ifInsn = getIfInsn();
|
IfCondition condition = getCondition();
|
||||||
|
if (condition == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
List<RegisterArg> conditionArgs = condition.getRegisterArgs();
|
||||||
|
if (conditionArgs.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
int size = insns.size();
|
int size = insns.size();
|
||||||
for (int i = 0; i < size; i++) {
|
for (int i = 0; i < size; i++) {
|
||||||
InsnNode insn = insns.get(i);
|
InsnNode insn = insns.get(i);
|
||||||
@@ -110,7 +102,7 @@ public final class LoopRegion extends AbstractRegion {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// or in if insn
|
// or in if insn
|
||||||
if (!found && ifInsn.containsVar(res)) {
|
if (!found && InsnUtils.containsVar(conditionArgs, res)) {
|
||||||
found = true;
|
found = true;
|
||||||
}
|
}
|
||||||
if (!found) {
|
if (!found) {
|
||||||
@@ -124,8 +116,8 @@ public final class LoopRegion extends AbstractRegion {
|
|||||||
* Move all preCondition block instructions before conditionBlock instructions
|
* Move all preCondition block instructions before conditionBlock instructions
|
||||||
*/
|
*/
|
||||||
public void mergePreCondition() {
|
public void mergePreCondition() {
|
||||||
if (preCondition != null && conditionBlock != null) {
|
if (preCondition != null && header != null) {
|
||||||
List<InsnNode> condInsns = conditionBlock.getInstructions();
|
List<InsnNode> condInsns = header.getInstructions();
|
||||||
List<InsnNode> preCondInsns = preCondition.getInstructions();
|
List<InsnNode> preCondInsns = preCondition.getInstructions();
|
||||||
preCondInsns.addAll(condInsns);
|
preCondInsns.addAll(condInsns);
|
||||||
condInsns.clear();
|
condInsns.clear();
|
||||||
@@ -135,9 +127,13 @@ public final class LoopRegion extends AbstractRegion {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getConditionSourceLine() {
|
public int getSourceLine() {
|
||||||
InsnNode lastInsn = BlockUtils.getLastInsn(conditionBlock);
|
InsnNode lastInsn = BlockUtils.getLastInsn(header);
|
||||||
return lastInsn == null ? 0 : lastInsn.getSourceLine();
|
int headerLine = lastInsn == null ? 0 : lastInsn.getSourceLine();
|
||||||
|
if (headerLine != 0) {
|
||||||
|
return headerLine;
|
||||||
|
}
|
||||||
|
return getConditionSourceLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
public LoopType getType() {
|
public LoopType getType() {
|
||||||
@@ -150,17 +146,15 @@ public final class LoopRegion extends AbstractRegion {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<IContainer> getSubBlocks() {
|
public List<IContainer> getSubBlocks() {
|
||||||
List<IContainer> all = new ArrayList<>(3);
|
List<IContainer> all = new ArrayList<>(2 + getConditionBlocks().size());
|
||||||
if (preCondition != null) {
|
if (preCondition != null) {
|
||||||
all.add(preCondition);
|
all.add(preCondition);
|
||||||
}
|
}
|
||||||
if (conditionBlock != null) {
|
all.addAll(getConditionBlocks());
|
||||||
all.add(conditionBlock);
|
|
||||||
}
|
|
||||||
if (body != null) {
|
if (body != null) {
|
||||||
all.add(body);
|
all.add(body);
|
||||||
}
|
}
|
||||||
return Collections.unmodifiableList(all);
|
return all;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package jadx.core.dex.trycatch;
|
package jadx.core.dex.trycatch;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
|
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
|
||||||
@@ -8,9 +9,14 @@ import jadx.core.utils.Utils;
|
|||||||
|
|
||||||
public class CatchAttr implements IJadxAttribute {
|
public class CatchAttr implements IJadxAttribute {
|
||||||
|
|
||||||
|
public static CatchAttr build(List<ExceptionHandler> handlers) {
|
||||||
|
handlers.sort(Comparator.comparingInt(ExceptionHandler::getHandlerOffset));
|
||||||
|
return new CatchAttr(handlers);
|
||||||
|
}
|
||||||
|
|
||||||
private final List<ExceptionHandler> handlers;
|
private final List<ExceptionHandler> handlers;
|
||||||
|
|
||||||
public CatchAttr(List<ExceptionHandler> handlers) {
|
private CatchAttr(List<ExceptionHandler> handlers) {
|
||||||
this.handlers = handlers;
|
this.handlers = handlers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,133 @@
|
|||||||
|
package jadx.core.dex.visitors;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
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.FieldInfo;
|
||||||
|
import jadx.core.dex.info.MethodInfo;
|
||||||
|
import jadx.core.dex.instructions.IndexInsnNode;
|
||||||
|
import jadx.core.dex.instructions.InsnType;
|
||||||
|
import jadx.core.dex.instructions.args.InsnArg;
|
||||||
|
import jadx.core.dex.instructions.args.RegisterArg;
|
||||||
|
import jadx.core.dex.instructions.args.SSAVar;
|
||||||
|
import jadx.core.dex.instructions.mods.ConstructorInsn;
|
||||||
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
|
import jadx.core.dex.nodes.FieldNode;
|
||||||
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
|
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
|
||||||
|
import jadx.core.utils.exceptions.JadxException;
|
||||||
|
|
||||||
|
@JadxVisitor(
|
||||||
|
name = "AnonymousClassVisitor",
|
||||||
|
desc = "Prepare anonymous class for inline",
|
||||||
|
runBefore = {
|
||||||
|
ModVisitor.class,
|
||||||
|
CodeShrinkVisitor.class
|
||||||
|
}
|
||||||
|
)
|
||||||
|
public class AnonymousClassVisitor extends AbstractVisitor {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean visit(ClassNode cls) throws JadxException {
|
||||||
|
if (cls.contains(AType.ANONYMOUS_CLASS)) {
|
||||||
|
for (MethodNode mth : cls.getMethods()) {
|
||||||
|
if (mth.contains(AFlag.ANONYMOUS_CONSTRUCTOR)) {
|
||||||
|
processAnonymousConstructor(mth);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void processAnonymousConstructor(MethodNode mth) {
|
||||||
|
List<InsnNode> usedInsns = new ArrayList<>();
|
||||||
|
Map<InsnArg, FieldNode> argsMap = getArgsToFieldsMapping(mth, usedInsns);
|
||||||
|
if (argsMap.isEmpty()) {
|
||||||
|
mth.add(AFlag.NO_SKIP_ARGS);
|
||||||
|
} else {
|
||||||
|
for (Map.Entry<InsnArg, FieldNode> entry : argsMap.entrySet()) {
|
||||||
|
FieldNode field = entry.getValue();
|
||||||
|
if (field == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
InsnArg arg = entry.getKey();
|
||||||
|
field.addAttr(new FieldReplaceAttr(arg));
|
||||||
|
field.add(AFlag.DONT_GENERATE);
|
||||||
|
if (arg.isRegister()) {
|
||||||
|
arg.add(AFlag.SKIP_ARG);
|
||||||
|
SkipMethodArgsAttr.skipArg(mth, ((RegisterArg) arg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (InsnNode usedInsn : usedInsns) {
|
||||||
|
usedInsn.add(AFlag.DONT_GENERATE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<InsnArg, FieldNode> getArgsToFieldsMapping(MethodNode mth, List<InsnNode> usedInsns) {
|
||||||
|
MethodInfo callMth = mth.getMethodInfo();
|
||||||
|
ClassNode cls = mth.getParentClass();
|
||||||
|
List<RegisterArg> argList = mth.getArgRegs();
|
||||||
|
ClassNode outerCls = mth.getUseIn().get(0).getParentClass();
|
||||||
|
int startArg = 0;
|
||||||
|
if (callMth.getArgsCount() != 0 && callMth.getArgumentsTypes().get(0).equals(outerCls.getClassInfo().getType())) {
|
||||||
|
startArg = 1;
|
||||||
|
}
|
||||||
|
Map<InsnArg, FieldNode> map = new LinkedHashMap<>();
|
||||||
|
int argsCount = argList.size();
|
||||||
|
for (int i = startArg; i < argsCount; i++) {
|
||||||
|
RegisterArg arg = argList.get(i);
|
||||||
|
InsnNode useInsn = getParentInsnSkipMove(arg);
|
||||||
|
if (useInsn == null) {
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
switch (useInsn.getType()) {
|
||||||
|
case IPUT:
|
||||||
|
FieldNode fieldNode = cls.searchField((FieldInfo) ((IndexInsnNode) useInsn).getIndex());
|
||||||
|
if (fieldNode == null || !fieldNode.getAccessFlags().isSynthetic()) {
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
map.put(arg, fieldNode);
|
||||||
|
usedInsns.add(useInsn);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CONSTRUCTOR:
|
||||||
|
ConstructorInsn superConstr = (ConstructorInsn) useInsn;
|
||||||
|
if (!superConstr.isSuper()) {
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
usedInsns.add(useInsn);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static InsnNode getParentInsnSkipMove(RegisterArg arg) {
|
||||||
|
SSAVar sVar = arg.getSVar();
|
||||||
|
if (sVar.getUseCount() != 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
RegisterArg useArg = sVar.getUseList().get(0);
|
||||||
|
InsnNode parentInsn = useArg.getParentInsn();
|
||||||
|
if (parentInsn == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (parentInsn.getType() == InsnType.MOVE) {
|
||||||
|
return getParentInsnSkipMove(parentInsn.getResult());
|
||||||
|
}
|
||||||
|
return parentInsn;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package jadx.core.dex.visitors;
|
package jadx.core.dex.visitors;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
@@ -15,12 +16,16 @@ import jadx.core.dex.attributes.AFlag;
|
|||||||
import jadx.core.dex.attributes.AType;
|
import jadx.core.dex.attributes.AType;
|
||||||
import jadx.core.dex.info.ClassInfo;
|
import jadx.core.dex.info.ClassInfo;
|
||||||
import jadx.core.dex.instructions.InsnType;
|
import jadx.core.dex.instructions.InsnType;
|
||||||
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
import jadx.core.dex.nodes.InsnNode;
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
import jadx.core.dex.trycatch.CatchAttr;
|
import jadx.core.dex.trycatch.CatchAttr;
|
||||||
import jadx.core.dex.trycatch.ExcHandlerAttr;
|
import jadx.core.dex.trycatch.ExcHandlerAttr;
|
||||||
import jadx.core.dex.trycatch.ExceptionHandler;
|
import jadx.core.dex.trycatch.ExceptionHandler;
|
||||||
|
import jadx.core.dex.visitors.typeinference.TypeCompare;
|
||||||
|
import jadx.core.dex.visitors.typeinference.TypeCompareEnum;
|
||||||
import jadx.core.utils.exceptions.JadxException;
|
import jadx.core.utils.exceptions.JadxException;
|
||||||
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
import static jadx.core.dex.visitors.ProcessInstructionsVisitor.getNextInsnOffset;
|
import static jadx.core.dex.visitors.ProcessInstructionsVisitor.getNextInsnOffset;
|
||||||
|
|
||||||
@@ -51,11 +56,11 @@ public class AttachTryCatchVisitor extends AbstractVisitor {
|
|||||||
tries.forEach(tryData -> LOG.debug(" - {}", tryData));
|
tries.forEach(tryData -> LOG.debug(" - {}", tryData));
|
||||||
}
|
}
|
||||||
for (ITry tryData : tries) {
|
for (ITry tryData : tries) {
|
||||||
List<ExceptionHandler> handlers = attachHandlers(mth, tryData.getCatch(), insnByOffset);
|
List<ExceptionHandler> handlers = convertToHandlers(mth, tryData.getCatch(), insnByOffset);
|
||||||
if (handlers.isEmpty()) {
|
if (handlers.isEmpty()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
markTryBounds(insnByOffset, tryData, new CatchAttr(handlers));
|
markTryBounds(insnByOffset, tryData, CatchAttr.build(handlers));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,13 +101,13 @@ public class AttachTryCatchVisitor extends AbstractVisitor {
|
|||||||
if (existAttr != null) {
|
if (existAttr != null) {
|
||||||
// merge handlers
|
// merge handlers
|
||||||
List<ExceptionHandler> handlers = Utils.concat(existAttr.getHandlers(), catchAttr.getHandlers());
|
List<ExceptionHandler> handlers = Utils.concat(existAttr.getHandlers(), catchAttr.getHandlers());
|
||||||
insn.addAttr(new CatchAttr(handlers));
|
insn.addAttr(CatchAttr.build(handlers));
|
||||||
} else {
|
} else {
|
||||||
insn.addAttr(catchAttr);
|
insn.addAttr(catchAttr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<ExceptionHandler> attachHandlers(MethodNode mth, ICatch catchBlock, InsnNode[] insnByOffset) {
|
private static List<ExceptionHandler> convertToHandlers(MethodNode mth, ICatch catchBlock, InsnNode[] insnByOffset) {
|
||||||
int[] handlerOffsetArr = catchBlock.getHandlers();
|
int[] handlerOffsetArr = catchBlock.getHandlers();
|
||||||
String[] handlerTypes = catchBlock.getTypes();
|
String[] handlerTypes = catchBlock.getTypes();
|
||||||
|
|
||||||
@@ -117,6 +122,7 @@ public class AttachTryCatchVisitor extends AbstractVisitor {
|
|||||||
if (allHandlerOffset >= 0) {
|
if (allHandlerOffset >= 0) {
|
||||||
Utils.addToList(list, createHandler(mth, insnByOffset, allHandlerOffset, null));
|
Utils.addToList(list, createHandler(mth, insnByOffset, allHandlerOffset, null));
|
||||||
}
|
}
|
||||||
|
checkAndFilterHandlers(mth, list);
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,6 +149,45 @@ public class AttachTryCatchVisitor extends AbstractVisitor {
|
|||||||
return handler;
|
return handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void checkAndFilterHandlers(MethodNode mth, List<ExceptionHandler> list) {
|
||||||
|
if (list.size() <= 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Remove shadowed handlers (with same or narrow type compared to previous)
|
||||||
|
TypeCompare typeCompare = mth.root().getTypeCompare();
|
||||||
|
Iterator<ExceptionHandler> it = list.iterator();
|
||||||
|
ArgType maxType = null;
|
||||||
|
while (it.hasNext()) {
|
||||||
|
ExceptionHandler handler = it.next();
|
||||||
|
ArgType maxCatch = maxCatchFromHandler(handler, typeCompare);
|
||||||
|
if (maxType == null) {
|
||||||
|
maxType = maxCatch;
|
||||||
|
} else {
|
||||||
|
TypeCompareEnum result = typeCompare.compareObjects(maxType, maxCatch);
|
||||||
|
if (result.isWiderOrEqual()) {
|
||||||
|
if (Consts.DEBUG_EXC_HANDLERS) {
|
||||||
|
LOG.debug("Removed shadowed catch handler: {}, from list: {}", handler, list);
|
||||||
|
}
|
||||||
|
it.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ArgType maxCatchFromHandler(ExceptionHandler handler, TypeCompare typeCompare) {
|
||||||
|
List<ClassInfo> catchTypes = handler.getCatchTypes();
|
||||||
|
if (catchTypes.isEmpty()) {
|
||||||
|
return ArgType.THROWABLE;
|
||||||
|
}
|
||||||
|
if (catchTypes.size() == 1) {
|
||||||
|
return catchTypes.get(0).getType();
|
||||||
|
}
|
||||||
|
return catchTypes.stream()
|
||||||
|
.map(ClassInfo::getType)
|
||||||
|
.max(typeCompare.getComparator())
|
||||||
|
.orElseThrow(() -> new JadxRuntimeException("Failed to get max type from catch list: " + catchTypes));
|
||||||
|
}
|
||||||
|
|
||||||
private static InsnNode insertNOP(InsnNode[] insnByOffset, int offset) {
|
private static InsnNode insertNOP(InsnNode[] insnByOffset, int offset) {
|
||||||
InsnNode nop = new InsnNode(InsnType.NOP, 0);
|
InsnNode nop = new InsnNode(InsnType.NOP, 0);
|
||||||
nop.setOffset(offset);
|
nop.setOffset(offset);
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
package jadx.core.dex.visitors;
|
package jadx.core.dex.visitors;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import jadx.api.plugins.input.data.AccessFlags;
|
import jadx.api.plugins.input.data.AccessFlags;
|
||||||
import jadx.core.Consts;
|
import jadx.core.Consts;
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
|
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
|
||||||
|
import jadx.core.dex.attributes.nodes.MethodReplaceAttr;
|
||||||
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
|
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
|
||||||
import jadx.core.dex.info.AccessInfo;
|
import jadx.core.dex.info.AccessInfo;
|
||||||
import jadx.core.dex.info.ClassInfo;
|
import jadx.core.dex.info.ClassInfo;
|
||||||
@@ -28,6 +32,7 @@ import jadx.core.dex.nodes.ClassNode;
|
|||||||
import jadx.core.dex.nodes.FieldNode;
|
import jadx.core.dex.nodes.FieldNode;
|
||||||
import jadx.core.dex.nodes.InsnNode;
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
|
import jadx.core.dex.visitors.usage.UsageInfoVisitor;
|
||||||
import jadx.core.utils.BlockUtils;
|
import jadx.core.utils.BlockUtils;
|
||||||
import jadx.core.utils.InsnRemover;
|
import jadx.core.utils.InsnRemover;
|
||||||
import jadx.core.utils.exceptions.JadxException;
|
import jadx.core.utils.exceptions.JadxException;
|
||||||
@@ -55,7 +60,7 @@ public class ClassModifier extends AbstractVisitor {
|
|||||||
removeSyntheticFields(cls);
|
removeSyntheticFields(cls);
|
||||||
cls.getMethods().forEach(ClassModifier::removeSyntheticMethods);
|
cls.getMethods().forEach(ClassModifier::removeSyntheticMethods);
|
||||||
cls.getMethods().forEach(ClassModifier::removeEmptyMethods);
|
cls.getMethods().forEach(ClassModifier::removeEmptyMethods);
|
||||||
cls.getMethods().forEach(ClassModifier::cleanInsnsInAnonymousConstructor);
|
cls.getMethods().forEach(ClassModifier::processAnonymousConstructor);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,7 +157,7 @@ public class ClassModifier extends AbstractVisitor {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// remove synthetic constructor for inner classes
|
// remove synthetic constructor for inner classes
|
||||||
if (af.isConstructor()) {
|
if (mth.isConstructor() && mth.contains(AFlag.METHOD_CANDIDATE_FOR_INLINE)) {
|
||||||
InsnNode insn = BlockUtils.getOnlyOneInsnFromMth(mth);
|
InsnNode insn = BlockUtils.getOnlyOneInsnFromMth(mth);
|
||||||
if (insn != null) {
|
if (insn != null) {
|
||||||
List<RegisterArg> args = mth.getArgRegs();
|
List<RegisterArg> args = mth.getArgRegs();
|
||||||
@@ -207,7 +212,14 @@ public class ClassModifier extends AbstractVisitor {
|
|||||||
SkipMethodArgsAttr.skipArg(mth, i);
|
SkipMethodArgsAttr.skipArg(mth, i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mth.add(AFlag.DONT_GENERATE);
|
MethodInfo callMth = constr.getCallMth();
|
||||||
|
MethodNode callMthNode = cls.root().resolveMethod(callMth);
|
||||||
|
if (callMthNode != null) {
|
||||||
|
mth.addAttr(new MethodReplaceAttr(callMthNode));
|
||||||
|
mth.add(AFlag.DONT_GENERATE);
|
||||||
|
// code generation order should be already fixed for marked methods
|
||||||
|
UsageInfoVisitor.replaceMethodUsage(callMthNode, mth);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -241,7 +253,7 @@ public class ClassModifier extends AbstractVisitor {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
MethodInfo callMth = invokeInsn.getCallMth();
|
MethodInfo callMth = invokeInsn.getCallMth();
|
||||||
MethodNode wrappedMth = mth.root().deepResolveMethod(callMth);
|
MethodNode wrappedMth = mth.root().resolveMethod(callMth);
|
||||||
if (wrappedMth == null) {
|
if (wrappedMth == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -326,27 +338,86 @@ public class ClassModifier extends AbstractVisitor {
|
|||||||
/**
|
/**
|
||||||
* Remove super call and put into removed fields from anonymous constructor
|
* Remove super call and put into removed fields from anonymous constructor
|
||||||
*/
|
*/
|
||||||
private static void cleanInsnsInAnonymousConstructor(MethodNode mth) {
|
private static void processAnonymousConstructor(MethodNode mth) {
|
||||||
if (!mth.contains(AFlag.ANONYMOUS_CONSTRUCTOR)) {
|
if (!mth.contains(AFlag.ANONYMOUS_CONSTRUCTOR)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (BlockNode block : mth.getBasicBlocks()) {
|
List<InsnNode> usedInsns = new ArrayList<>();
|
||||||
for (InsnNode insn : block.getInstructions()) {
|
Map<InsnArg, FieldNode> argsMap = getArgsToFieldsMapping(mth, usedInsns);
|
||||||
InsnType type = insn.getType();
|
for (Map.Entry<InsnArg, FieldNode> entry : argsMap.entrySet()) {
|
||||||
if (type == InsnType.CONSTRUCTOR) {
|
FieldNode field = entry.getValue();
|
||||||
ConstructorInsn ctorInsn = (ConstructorInsn) insn;
|
if (field == null) {
|
||||||
if (ctorInsn.isSuper()) {
|
continue;
|
||||||
ctorInsn.add(AFlag.DONT_GENERATE);
|
}
|
||||||
}
|
InsnArg arg = entry.getKey();
|
||||||
} else if (type == InsnType.IPUT) {
|
field.addAttr(new FieldReplaceAttr(arg));
|
||||||
FieldInfo fldInfo = (FieldInfo) ((IndexInsnNode) insn).getIndex();
|
field.add(AFlag.DONT_GENERATE);
|
||||||
FieldNode fieldNode = mth.root().resolveField(fldInfo);
|
if (arg.isRegister()) {
|
||||||
if (fieldNode != null && fieldNode.contains(AFlag.DONT_GENERATE)) {
|
arg.add(AFlag.SKIP_ARG);
|
||||||
insn.add(AFlag.DONT_GENERATE);
|
SkipMethodArgsAttr.skipArg(mth, ((RegisterArg) arg));
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for (InsnNode usedInsn : usedInsns) {
|
||||||
|
usedInsn.add(AFlag.DONT_GENERATE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<InsnArg, FieldNode> getArgsToFieldsMapping(MethodNode mth, List<InsnNode> usedInsns) {
|
||||||
|
MethodInfo callMth = mth.getMethodInfo();
|
||||||
|
ClassNode cls = mth.getParentClass();
|
||||||
|
List<RegisterArg> argList = mth.getArgRegs();
|
||||||
|
ClassNode outerCls = mth.getUseIn().get(0).getParentClass();
|
||||||
|
int startArg = 0;
|
||||||
|
if (callMth.getArgsCount() != 0 && callMth.getArgumentsTypes().get(0).equals(outerCls.getClassInfo().getType())) {
|
||||||
|
startArg = 1;
|
||||||
|
}
|
||||||
|
Map<InsnArg, FieldNode> map = new LinkedHashMap<>();
|
||||||
|
int argsCount = argList.size();
|
||||||
|
for (int i = startArg; i < argsCount; i++) {
|
||||||
|
RegisterArg arg = argList.get(i);
|
||||||
|
InsnNode useInsn = getParentInsnSkipMove(arg);
|
||||||
|
if (useInsn == null) {
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
switch (useInsn.getType()) {
|
||||||
|
case IPUT:
|
||||||
|
FieldNode fieldNode = cls.searchField((FieldInfo) ((IndexInsnNode) useInsn).getIndex());
|
||||||
|
if (fieldNode == null || !fieldNode.getAccessFlags().isSynthetic()) {
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
map.put(arg, fieldNode);
|
||||||
|
usedInsns.add(useInsn);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CONSTRUCTOR:
|
||||||
|
ConstructorInsn superConstr = (ConstructorInsn) useInsn;
|
||||||
|
if (!superConstr.isSuper()) {
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
usedInsns.add(useInsn);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static InsnNode getParentInsnSkipMove(RegisterArg arg) {
|
||||||
|
SSAVar sVar = arg.getSVar();
|
||||||
|
if (sVar.getUseCount() != 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
RegisterArg useArg = sVar.getUseList().get(0);
|
||||||
|
InsnNode parentInsn = useArg.getParentInsn();
|
||||||
|
if (parentInsn == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (parentInsn.getType() == InsnType.MOVE) {
|
||||||
|
return getParentInsnSkipMove(parentInsn.getResult());
|
||||||
|
}
|
||||||
|
return parentInsn;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isNonDefaultConstructorExists(MethodNode defCtor) {
|
private static boolean isNonDefaultConstructorExists(MethodNode defCtor) {
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ public class ConstInlineVisitor extends AbstractVisitor {
|
|||||||
|
|
||||||
SSAVar sVar = insn.getResult().getSVar();
|
SSAVar sVar = insn.getResult().getSVar();
|
||||||
InsnArg constArg;
|
InsnArg constArg;
|
||||||
|
Runnable onSuccess = null;
|
||||||
|
|
||||||
InsnType insnType = insn.getType();
|
InsnType insnType = insn.getType();
|
||||||
if (insnType == InsnType.CONST || insnType == InsnType.MOVE) {
|
if (insnType == InsnType.CONST || insnType == InsnType.MOVE) {
|
||||||
@@ -90,6 +91,7 @@ public class ConstInlineVisitor extends AbstractVisitor {
|
|||||||
InsnNode constGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0);
|
InsnNode constGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0);
|
||||||
constArg = InsnArg.wrapArg(constGet);
|
constArg = InsnArg.wrapArg(constGet);
|
||||||
constArg.setType(ArgType.STRING);
|
constArg.setType(ArgType.STRING);
|
||||||
|
onSuccess = () -> f.addUseIn(mth);
|
||||||
}
|
}
|
||||||
} else if (insnType == InsnType.CONST_CLASS) {
|
} else if (insnType == InsnType.CONST_CLASS) {
|
||||||
if (sVar.isUsedInPhi()) {
|
if (sVar.isUsedInPhi()) {
|
||||||
@@ -104,6 +106,9 @@ public class ConstInlineVisitor extends AbstractVisitor {
|
|||||||
// all check passed, run replace
|
// all check passed, run replace
|
||||||
if (replaceConst(mth, insn, constArg)) {
|
if (replaceConst(mth, insn, constArg)) {
|
||||||
toRemove.add(insn);
|
toRemove.add(insn);
|
||||||
|
if (onSuccess != null) {
|
||||||
|
onSuccess.run();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,7 +240,10 @@ public class ConstInlineVisitor extends AbstractVisitor {
|
|||||||
fieldNode = mth.getParentClass().getConstField((int) literal, false);
|
fieldNode = mth.getParentClass().getConstField((int) literal, false);
|
||||||
}
|
}
|
||||||
if (fieldNode != null) {
|
if (fieldNode != null) {
|
||||||
litArg.wrapInstruction(mth, new IndexInsnNode(InsnType.SGET, fieldNode.getFieldInfo(), 0));
|
IndexInsnNode sgetInsn = new IndexInsnNode(InsnType.SGET, fieldNode.getFieldInfo(), 0);
|
||||||
|
if (litArg.wrapInstruction(mth, sgetInsn) != null) {
|
||||||
|
fieldNode.addUseIn(mth);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (needExplicitCast(useInsn, litArg)) {
|
if (needExplicitCast(useInsn, litArg)) {
|
||||||
litArg.add(AFlag.EXPLICIT_PRIMITIVE_TYPE);
|
litArg.add(AFlag.EXPLICIT_PRIMITIVE_TYPE);
|
||||||
|
|||||||
@@ -137,6 +137,17 @@ public class DeboxingVisitor extends AbstractVisitor {
|
|||||||
if (ssaVar.isTypeImmutable()) {
|
if (ssaVar.isTypeImmutable()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
InsnNode assignInsn = ssaVar.getAssignInsn();
|
||||||
|
if (assignInsn == null) {
|
||||||
|
// method arg
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
InsnType assignInsnType = assignInsn.getType();
|
||||||
|
if (assignInsnType == InsnType.CONST || assignInsnType == InsnType.MOVE) {
|
||||||
|
if (assignInsn.getArg(0).getType().isObject()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
for (RegisterArg useArg : ssaVar.getUseList()) {
|
for (RegisterArg useArg : ssaVar.getUseList()) {
|
||||||
InsnNode parentInsn = useArg.getParentInsn();
|
InsnNode parentInsn = useArg.getParentInsn();
|
||||||
if (parentInsn == null) {
|
if (parentInsn == null) {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import jadx.api.plugins.input.data.AccessFlags;
|
|||||||
import jadx.core.codegen.TypeGen;
|
import jadx.core.codegen.TypeGen;
|
||||||
import jadx.core.deobf.NameMapper;
|
import jadx.core.deobf.NameMapper;
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
|
import jadx.core.dex.attributes.AType;
|
||||||
import jadx.core.dex.attributes.nodes.EnumClassAttr;
|
import jadx.core.dex.attributes.nodes.EnumClassAttr;
|
||||||
import jadx.core.dex.attributes.nodes.EnumClassAttr.EnumField;
|
import jadx.core.dex.attributes.nodes.EnumClassAttr.EnumField;
|
||||||
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
|
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
|
||||||
@@ -71,7 +72,14 @@ public class EnumVisitor extends AbstractVisitor {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean visit(ClassNode cls) throws JadxException {
|
public boolean visit(ClassNode cls) throws JadxException {
|
||||||
if (!convertToEnum(cls)) {
|
boolean converted;
|
||||||
|
try {
|
||||||
|
converted = convertToEnum(cls);
|
||||||
|
} catch (Exception e) {
|
||||||
|
cls.addWarnComment("Enum visitor error", e);
|
||||||
|
converted = false;
|
||||||
|
}
|
||||||
|
if (!converted) {
|
||||||
AccessInfo accessFlags = cls.getAccessFlags();
|
AccessInfo accessFlags = cls.getAccessFlags();
|
||||||
if (accessFlags.isEnum()) {
|
if (accessFlags.isEnum()) {
|
||||||
cls.setAccessFlags(accessFlags.remove(AccessFlags.ENUM));
|
cls.setAccessFlags(accessFlags.remove(AccessFlags.ENUM));
|
||||||
@@ -179,8 +187,7 @@ public class EnumVisitor extends AbstractVisitor {
|
|||||||
if (!enumClsInfo.equals(cls.getClassInfo())) {
|
if (!enumClsInfo.equals(cls.getClassInfo())) {
|
||||||
ClassNode enumCls = cls.root().resolveClass(enumClsInfo);
|
ClassNode enumCls = cls.root().resolveClass(enumClsInfo);
|
||||||
if (enumCls != null) {
|
if (enumCls != null) {
|
||||||
processEnumCls(enumField, enumCls);
|
processEnumCls(cls, enumField, enumCls);
|
||||||
cls.addInlinedClass(enumCls);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
List<RegisterArg> regs = new ArrayList<>();
|
List<RegisterArg> regs = new ArrayList<>();
|
||||||
@@ -381,7 +388,11 @@ public class EnumVisitor extends AbstractVisitor {
|
|||||||
if (constrCls == null) {
|
if (constrCls == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (!clsInfo.equals(cls.getClassInfo()) && !constrCls.getAccessFlags().isEnum()) {
|
if (constrCls.equals(cls)) {
|
||||||
|
// allow same class
|
||||||
|
} else if (constrCls.contains(AType.ANONYMOUS_CLASS)) {
|
||||||
|
// allow external class already marked as anonymous
|
||||||
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
MethodNode ctrMth = cls.root().resolveMethod(co.getCallMth());
|
MethodNode ctrMth = cls.root().resolveMethod(co.getCallMth());
|
||||||
@@ -466,7 +477,7 @@ public class EnumVisitor extends AbstractVisitor {
|
|||||||
return InsnUtils.searchInsn(mth, InsnType.SGET, insnTest) != null;
|
return InsnUtils.searchInsn(mth, InsnType.SGET, insnTest) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void processEnumCls(EnumField field, ClassNode innerCls) {
|
private static void processEnumCls(ClassNode cls, EnumField field, ClassNode innerCls) {
|
||||||
// remove constructor, because it is anonymous class
|
// remove constructor, because it is anonymous class
|
||||||
for (MethodNode innerMth : innerCls.getMethods()) {
|
for (MethodNode innerMth : innerCls.getMethods()) {
|
||||||
if (innerMth.getAccessFlags().isConstructor()) {
|
if (innerMth.getAccessFlags().isConstructor()) {
|
||||||
@@ -474,7 +485,11 @@ public class EnumVisitor extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
field.setCls(innerCls);
|
field.setCls(innerCls);
|
||||||
innerCls.add(AFlag.DONT_GENERATE);
|
if (!innerCls.getParentClass().equals(cls)) {
|
||||||
|
// not inner
|
||||||
|
cls.addInlinedClass(innerCls);
|
||||||
|
innerCls.add(AFlag.DONT_GENERATE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ConstructorInsn getConstructorInsn(InsnNode insn) {
|
private ConstructorInsn getConstructorInsn(InsnNode insn) {
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ public class FixAccessModifiers extends AbstractVisitor {
|
|||||||
if (!accessFlags.isPublic()) {
|
if (!accessFlags.isPublic()) {
|
||||||
// if class is used in inlinable method => make it public
|
// if class is used in inlinable method => make it public
|
||||||
for (MethodNode useMth : cls.getUseInMth()) {
|
for (MethodNode useMth : cls.getUseInMth()) {
|
||||||
boolean canInline = MarkMethodsForInline.canInline(useMth) || useMth.contains(AType.METHOD_INLINE);
|
boolean canInline = useMth.contains(AFlag.METHOD_CANDIDATE_FOR_INLINE) || useMth.contains(AType.METHOD_INLINE);
|
||||||
if (canInline && !useMth.getUseIn().isEmpty()) {
|
if (canInline && !useMth.getUseIn().isEmpty()) {
|
||||||
return AccessFlags.PUBLIC;
|
return AccessFlags.PUBLIC;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,9 +26,6 @@ public class InitCodeVariables extends AbstractVisitor {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void visit(MethodNode mth) throws JadxException {
|
public void visit(MethodNode mth) throws JadxException {
|
||||||
if (mth.isNoCode()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
initCodeVars(mth);
|
initCodeVars(mth);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,16 +39,24 @@ public class InitCodeVariables extends AbstractVisitor {
|
|||||||
private static void initCodeVars(MethodNode mth) {
|
private static void initCodeVars(MethodNode mth) {
|
||||||
RegisterArg thisArg = mth.getThisArg();
|
RegisterArg thisArg = mth.getThisArg();
|
||||||
if (thisArg != null) {
|
if (thisArg != null) {
|
||||||
initCodeVar(thisArg.getSVar());
|
initCodeVar(mth, thisArg);
|
||||||
}
|
}
|
||||||
for (RegisterArg mthArg : mth.getArgRegs()) {
|
for (RegisterArg mthArg : mth.getArgRegs()) {
|
||||||
initCodeVar(mthArg.getSVar());
|
initCodeVar(mth, mthArg);
|
||||||
}
|
}
|
||||||
for (SSAVar ssaVar : mth.getSVars()) {
|
for (SSAVar ssaVar : mth.getSVars()) {
|
||||||
initCodeVar(ssaVar);
|
initCodeVar(ssaVar);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void initCodeVar(MethodNode mth, RegisterArg regArg) {
|
||||||
|
SSAVar ssaVar = regArg.getSVar();
|
||||||
|
if (ssaVar == null) {
|
||||||
|
ssaVar = mth.makeNewSVar(regArg);
|
||||||
|
}
|
||||||
|
initCodeVar(ssaVar);
|
||||||
|
}
|
||||||
|
|
||||||
public static void initCodeVar(SSAVar ssaVar) {
|
public static void initCodeVar(SSAVar ssaVar) {
|
||||||
if (ssaVar.isCodeVarSet()) {
|
if (ssaVar.isCodeVarSet()) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import jadx.core.Consts;
|
|||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
import jadx.core.dex.attributes.AType;
|
import jadx.core.dex.attributes.AType;
|
||||||
import jadx.core.dex.attributes.nodes.MethodInlineAttr;
|
import jadx.core.dex.attributes.nodes.MethodInlineAttr;
|
||||||
import jadx.core.dex.info.AccessInfo;
|
|
||||||
import jadx.core.dex.info.FieldInfo;
|
import jadx.core.dex.info.FieldInfo;
|
||||||
import jadx.core.dex.instructions.IndexInsnNode;
|
import jadx.core.dex.instructions.IndexInsnNode;
|
||||||
import jadx.core.dex.instructions.InsnType;
|
import jadx.core.dex.instructions.InsnType;
|
||||||
@@ -47,7 +46,7 @@ public class MarkMethodsForInline extends AbstractVisitor {
|
|||||||
if (mia != null) {
|
if (mia != null) {
|
||||||
return mia;
|
return mia;
|
||||||
}
|
}
|
||||||
if (canInline(mth)) {
|
if (mth.contains(AFlag.METHOD_CANDIDATE_FOR_INLINE)) {
|
||||||
if (mth.getBasicBlocks() == null) {
|
if (mth.getBasicBlocks() == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -59,14 +58,6 @@ public class MarkMethodsForInline extends AbstractVisitor {
|
|||||||
return MethodInlineAttr.inlineNotNeeded(mth);
|
return MethodInlineAttr.inlineNotNeeded(mth);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean canInline(MethodNode mth) {
|
|
||||||
if (mth.isNoCode() || mth.contains(AFlag.DONT_GENERATE)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
AccessInfo accessFlags = mth.getAccessFlags();
|
|
||||||
return accessFlags.isSynthetic() && accessFlags.isStatic();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private static MethodInlineAttr inlineMth(MethodNode mth) {
|
private static MethodInlineAttr inlineMth(MethodNode mth) {
|
||||||
List<InsnNode> insns = BlockUtils.collectInsnsWithLimit(mth.getBasicBlocks(), 2);
|
List<InsnNode> insns = BlockUtils.collectInsnsWithLimit(mth.getBasicBlocks(), 2);
|
||||||
@@ -79,7 +70,11 @@ public class MarkMethodsForInline extends AbstractVisitor {
|
|||||||
if (insn.getType() == InsnType.RETURN && insn.getArgsCount() == 1) {
|
if (insn.getType() == InsnType.RETURN && insn.getArgsCount() == 1) {
|
||||||
// synthetic field getter
|
// synthetic field getter
|
||||||
// set arg from 'return' instruction
|
// set arg from 'return' instruction
|
||||||
return addInlineAttr(mth, InsnNode.wrapArg(insn.getArg(0)));
|
InsnArg arg = insn.getArg(0);
|
||||||
|
if (!arg.isInsnWrap()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return addInlineAttr(mth, ((InsnWrapArg) arg).getWrapInsn());
|
||||||
}
|
}
|
||||||
// method invoke
|
// method invoke
|
||||||
return addInlineAttr(mth, insn);
|
return addInlineAttr(mth, insn);
|
||||||
@@ -111,7 +106,7 @@ public class MarkMethodsForInline extends AbstractVisitor {
|
|||||||
InsnType insnType = insn.getType();
|
InsnType insnType = insn.getType();
|
||||||
if (insnType == InsnType.INVOKE) {
|
if (insnType == InsnType.INVOKE) {
|
||||||
InvokeNode invoke = (InvokeNode) insn;
|
InvokeNode invoke = (InvokeNode) insn;
|
||||||
MethodNode callMthNode = mth.root().deepResolveMethod(invoke.getCallMth());
|
MethodNode callMthNode = mth.root().resolveMethod(invoke.getCallMth());
|
||||||
if (callMthNode != null) {
|
if (callMthNode != null) {
|
||||||
FixAccessModifiers.changeVisibility(callMthNode, newVisFlag);
|
FixAccessModifiers.changeVisibility(callMthNode, newVisFlag);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package jadx.core.dex.visitors;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
|
import jadx.core.dex.nodes.RootNode;
|
||||||
|
import jadx.core.utils.exceptions.JadxException;
|
||||||
|
|
||||||
|
public class MethodVisitor implements IDexTreeVisitor {
|
||||||
|
|
||||||
|
private final Consumer<MethodNode> visitor;
|
||||||
|
|
||||||
|
public MethodVisitor(Consumer<MethodNode> visitor) {
|
||||||
|
this.visitor = visitor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visit(MethodNode mth) throws JadxException {
|
||||||
|
visitor.accept(mth);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(RootNode root) throws JadxException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean visit(ClassNode cls) throws JadxException {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
package jadx.core.dex.visitors;
|
package jadx.core.dex.visitors;
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
@@ -19,10 +17,9 @@ import jadx.api.plugins.input.data.attributes.types.AnnotationsAttr;
|
|||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
import jadx.core.dex.attributes.AType;
|
import jadx.core.dex.attributes.AType;
|
||||||
import jadx.core.dex.attributes.AttrNode;
|
import jadx.core.dex.attributes.AttrNode;
|
||||||
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.AccessInfo;
|
||||||
import jadx.core.dex.info.FieldInfo;
|
import jadx.core.dex.info.FieldInfo;
|
||||||
import jadx.core.dex.info.MethodInfo;
|
|
||||||
import jadx.core.dex.instructions.ArithNode;
|
import jadx.core.dex.instructions.ArithNode;
|
||||||
import jadx.core.dex.instructions.ConstClassNode;
|
import jadx.core.dex.instructions.ConstClassNode;
|
||||||
import jadx.core.dex.instructions.ConstStringNode;
|
import jadx.core.dex.instructions.ConstStringNode;
|
||||||
@@ -46,6 +43,7 @@ import jadx.core.dex.instructions.mods.TernaryInsn;
|
|||||||
import jadx.core.dex.nodes.BlockNode;
|
import jadx.core.dex.nodes.BlockNode;
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
import jadx.core.dex.nodes.FieldNode;
|
import jadx.core.dex.nodes.FieldNode;
|
||||||
|
import jadx.core.dex.nodes.IMethodDetails;
|
||||||
import jadx.core.dex.nodes.InsnNode;
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
import jadx.core.dex.regions.conditions.IfCondition;
|
import jadx.core.dex.regions.conditions.IfCondition;
|
||||||
@@ -116,7 +114,7 @@ public class ModVisitor extends AbstractVisitor {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case SWITCH:
|
case SWITCH:
|
||||||
replaceConstKeys(parentClass, (SwitchInsn) insn);
|
replaceConstKeys(mth, parentClass, (SwitchInsn) insn);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case NEW_ARRAY:
|
case NEW_ARRAY:
|
||||||
@@ -230,13 +228,14 @@ public class ModVisitor extends AbstractVisitor {
|
|||||||
return result == TypeCompareEnum.NARROW; // true if use class is subclass of field class
|
return result == TypeCompareEnum.NARROW; // true if use class is subclass of field class
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void replaceConstKeys(ClassNode parentClass, SwitchInsn insn) {
|
private static void replaceConstKeys(MethodNode mth, ClassNode parentClass, SwitchInsn insn) {
|
||||||
int[] keys = insn.getKeys();
|
int[] keys = insn.getKeys();
|
||||||
int len = keys.length;
|
int len = keys.length;
|
||||||
for (int k = 0; k < len; k++) {
|
for (int k = 0; k < len; k++) {
|
||||||
FieldNode f = parentClass.getConstField(keys[k]);
|
FieldNode f = parentClass.getConstField(keys[k]);
|
||||||
if (f != null) {
|
if (f != null) {
|
||||||
insn.modifyKey(k, f);
|
insn.modifyKey(k, f);
|
||||||
|
f.addUseIn(mth);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -293,6 +292,13 @@ public class ModVisitor extends AbstractVisitor {
|
|||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private EncodedValue replaceConstValue(ClassNode parentCls, EncodedValue encodedValue) {
|
private EncodedValue replaceConstValue(ClassNode parentCls, EncodedValue encodedValue) {
|
||||||
|
if (encodedValue.getType() == EncodedType.ENCODED_ANNOTATION) {
|
||||||
|
IAnnotation annotation = (IAnnotation) encodedValue.getValue();
|
||||||
|
for (Map.Entry<String, EncodedValue> entry : annotation.getValues().entrySet()) {
|
||||||
|
entry.setValue(replaceConstValue(parentCls, entry.getValue()));
|
||||||
|
}
|
||||||
|
return encodedValue;
|
||||||
|
}
|
||||||
if (encodedValue.getType() == EncodedType.ENCODED_ARRAY) {
|
if (encodedValue.getType() == EncodedType.ENCODED_ARRAY) {
|
||||||
List<EncodedValue> listVal = (List<EncodedValue>) encodedValue.getValue();
|
List<EncodedValue> listVal = (List<EncodedValue>) encodedValue.getValue();
|
||||||
if (!listVal.isEmpty()) {
|
if (!listVal.isEmpty()) {
|
||||||
@@ -322,6 +328,7 @@ public class ModVisitor extends AbstractVisitor {
|
|||||||
InsnNode inode = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0);
|
InsnNode inode = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0);
|
||||||
inode.setResult(insn.getResult());
|
inode.setResult(insn.getResult());
|
||||||
replaceInsn(mth, block, i, inode);
|
replaceInsn(mth, block, i, inode);
|
||||||
|
f.addUseIn(mth);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -334,7 +341,9 @@ public class ModVisitor extends AbstractVisitor {
|
|||||||
FieldNode f = parentClass.getConstFieldByLiteralArg((LiteralArg) litArg);
|
FieldNode f = parentClass.getConstFieldByLiteralArg((LiteralArg) litArg);
|
||||||
if (f != null) {
|
if (f != null) {
|
||||||
InsnNode fGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0);
|
InsnNode fGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0);
|
||||||
arithNode.replaceArg(litArg, InsnArg.wrapArg(fGet));
|
if (arithNode.replaceArg(litArg, InsnArg.wrapArg(fGet))) {
|
||||||
|
f.addUseIn(mth);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -432,96 +441,39 @@ public class ModVisitor extends AbstractVisitor {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For args in anonymous constructor invoke apply:
|
||||||
|
* - forbid inline into constructor call
|
||||||
|
* - make variables final (compiler require this implicitly)
|
||||||
|
*/
|
||||||
private static void processAnonymousConstructor(MethodNode mth, ConstructorInsn co) {
|
private static void processAnonymousConstructor(MethodNode mth, ConstructorInsn co) {
|
||||||
MethodInfo callMth = co.getCallMth();
|
IMethodDetails callMthDetails = mth.root().getMethodUtils().getMethodDetails(co);
|
||||||
MethodNode callMthNode = mth.root().resolveMethod(callMth);
|
if (!(callMthDetails instanceof MethodNode)) {
|
||||||
if (callMthNode == null) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
MethodNode callMth = (MethodNode) callMthDetails;
|
||||||
ClassNode classNode = callMthNode.getParentClass();
|
if (!callMth.contains(AFlag.ANONYMOUS_CONSTRUCTOR) || callMth.contains(AFlag.NO_SKIP_ARGS)) {
|
||||||
if (!classNode.isAnonymous()) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!mth.getParentClass().getInnerClasses().contains(classNode)) {
|
SkipMethodArgsAttr attr = callMth.get(AType.SKIP_MTH_ARGS);
|
||||||
return;
|
if (attr != null) {
|
||||||
}
|
int argsCount = Math.min(callMth.getMethodInfo().getArgsCount(), co.getArgsCount());
|
||||||
Map<InsnArg, FieldNode> argsMap = getArgsToFieldsMapping(callMthNode, co);
|
for (int i = 0; i < argsCount; i++) {
|
||||||
if (argsMap.isEmpty() && !callMthNode.getArgRegs().isEmpty()) {
|
if (attr.isSkip(i)) {
|
||||||
return;
|
anonymousCallArgMod(co.getArg(i));
|
||||||
}
|
|
||||||
|
|
||||||
for (Map.Entry<InsnArg, FieldNode> entry : argsMap.entrySet()) {
|
|
||||||
FieldNode field = entry.getValue();
|
|
||||||
if (field == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
InsnArg arg = entry.getKey();
|
|
||||||
field.addAttr(new FieldReplaceAttr(arg));
|
|
||||||
field.add(AFlag.DONT_GENERATE);
|
|
||||||
if (arg.isRegister()) {
|
|
||||||
RegisterArg reg = (RegisterArg) arg;
|
|
||||||
SSAVar sVar = reg.getSVar();
|
|
||||||
if (sVar != null) {
|
|
||||||
sVar.getCodeVar().setFinal(true);
|
|
||||||
}
|
}
|
||||||
reg.add(AFlag.DONT_INLINE);
|
|
||||||
reg.add(AFlag.SKIP_ARG);
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// additional info not available apply mods to all args (the safest solution)
|
||||||
|
co.getArguments().forEach(ModVisitor::anonymousCallArgMod);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Map<InsnArg, FieldNode> getArgsToFieldsMapping(MethodNode callMthNode, ConstructorInsn co) {
|
private static void anonymousCallArgMod(InsnArg arg) {
|
||||||
Map<InsnArg, FieldNode> map = new LinkedHashMap<>();
|
arg.add(AFlag.DONT_INLINE);
|
||||||
MethodInfo callMth = callMthNode.getMethodInfo();
|
if (arg.isRegister()) {
|
||||||
ClassNode cls = callMthNode.getParentClass();
|
((RegisterArg) arg).getSVar().getCodeVar().setFinal(true);
|
||||||
ClassNode parentClass = cls.getParentClass();
|
|
||||||
List<RegisterArg> argList = callMthNode.getArgRegs();
|
|
||||||
int startArg = 0;
|
|
||||||
if (callMth.getArgsCount() != 0 && callMth.getArgumentsTypes().get(0).equals(parentClass.getClassInfo().getType())) {
|
|
||||||
startArg = 1;
|
|
||||||
}
|
}
|
||||||
int argsCount = argList.size();
|
|
||||||
for (int i = startArg; i < argsCount; i++) {
|
|
||||||
RegisterArg arg = argList.get(i);
|
|
||||||
InsnNode useInsn = getParentInsnSkipMove(arg);
|
|
||||||
if (useInsn == null) {
|
|
||||||
return Collections.emptyMap();
|
|
||||||
}
|
|
||||||
FieldNode fieldNode = null;
|
|
||||||
if (useInsn.getType() == InsnType.IPUT) {
|
|
||||||
FieldInfo field = (FieldInfo) ((IndexInsnNode) useInsn).getIndex();
|
|
||||||
fieldNode = cls.searchField(field);
|
|
||||||
if (fieldNode == null || !fieldNode.getAccessFlags().isSynthetic()) {
|
|
||||||
return Collections.emptyMap();
|
|
||||||
}
|
|
||||||
} else if (useInsn.getType() == InsnType.CONSTRUCTOR) {
|
|
||||||
ConstructorInsn superConstr = (ConstructorInsn) useInsn;
|
|
||||||
if (!superConstr.isSuper()) {
|
|
||||||
return Collections.emptyMap();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Collections.emptyMap();
|
|
||||||
}
|
|
||||||
map.put(co.getArg(i), fieldNode);
|
|
||||||
}
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static InsnNode getParentInsnSkipMove(RegisterArg arg) {
|
|
||||||
SSAVar sVar = arg.getSVar();
|
|
||||||
if (sVar.getUseCount() != 1) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
RegisterArg useArg = sVar.getUseList().get(0);
|
|
||||||
InsnNode parentInsn = useArg.getParentInsn();
|
|
||||||
if (parentInsn == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (parentInsn.getType() == InsnType.MOVE) {
|
|
||||||
return getParentInsnSkipMove(parentInsn.getResult());
|
|
||||||
}
|
|
||||||
return parentInsn;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -575,6 +527,7 @@ public class ModVisitor extends AbstractVisitor {
|
|||||||
if (f != null) {
|
if (f != null) {
|
||||||
InsnNode fGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0);
|
InsnNode fGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0);
|
||||||
filledArr.addArg(InsnArg.wrapArg(fGet));
|
filledArr.addArg(InsnArg.wrapArg(fGet));
|
||||||
|
f.addUseIn(mth);
|
||||||
} else {
|
} else {
|
||||||
filledArr.addArg(arg);
|
filledArr.addArg(arg);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,13 @@ public class MoveInlineVisitor extends AbstractVisitor {
|
|||||||
if (resultArg.sameRegAndSVar(moveArg)) {
|
if (resultArg.sameRegAndSVar(moveArg)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (moveArg.isRegister()) {
|
||||||
|
RegisterArg moveReg = (RegisterArg) moveArg;
|
||||||
|
if (moveReg.getSVar().isAssignInPhi()) {
|
||||||
|
// don't mix already merged variables
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
SSAVar ssaVar = resultArg.getSVar();
|
SSAVar ssaVar = resultArg.getSVar();
|
||||||
if (ssaVar.isUsedInPhi()) {
|
if (ssaVar.isUsedInPhi()) {
|
||||||
return deleteMove(mth, move);
|
return deleteMove(mth, move);
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
package jadx.core.dex.visitors;
|
package jadx.core.dex.visitors;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.SortedSet;
|
import java.util.SortedSet;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@@ -17,6 +17,7 @@ import jadx.core.clsp.ClspClass;
|
|||||||
import jadx.core.clsp.ClspMethod;
|
import jadx.core.clsp.ClspMethod;
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
import jadx.core.dex.attributes.AType;
|
import jadx.core.dex.attributes.AType;
|
||||||
|
import jadx.core.dex.attributes.nodes.MethodBridgeAttr;
|
||||||
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
|
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
|
||||||
import jadx.core.dex.attributes.nodes.RenameReasonAttr;
|
import jadx.core.dex.attributes.nodes.RenameReasonAttr;
|
||||||
import jadx.core.dex.info.AccessInfo;
|
import jadx.core.dex.info.AccessInfo;
|
||||||
@@ -32,6 +33,7 @@ import jadx.core.dex.visitors.typeinference.TypeCompareEnum;
|
|||||||
import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor;
|
import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor;
|
||||||
import jadx.core.utils.Utils;
|
import jadx.core.utils.Utils;
|
||||||
import jadx.core.utils.exceptions.JadxException;
|
import jadx.core.utils.exceptions.JadxException;
|
||||||
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
@JadxVisitor(
|
@JadxVisitor(
|
||||||
name = "OverrideMethodVisitor",
|
name = "OverrideMethodVisitor",
|
||||||
@@ -45,70 +47,86 @@ public class OverrideMethodVisitor extends AbstractVisitor {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean visit(ClassNode cls) throws JadxException {
|
public boolean visit(ClassNode cls) throws JadxException {
|
||||||
processCls(cls);
|
SuperTypesData superData = collectSuperTypes(cls);
|
||||||
|
if (superData != null) {
|
||||||
|
for (MethodNode mth : cls.getMethods()) {
|
||||||
|
processMth(mth, superData);
|
||||||
|
}
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processCls(ClassNode cls) {
|
private void processMth(MethodNode mth, SuperTypesData superData) {
|
||||||
List<ArgType> superTypes = collectSuperTypes(cls);
|
|
||||||
if (!superTypes.isEmpty()) {
|
|
||||||
for (MethodNode mth : cls.getMethods()) {
|
|
||||||
processMth(cls, superTypes, mth);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processMth(ClassNode cls, List<ArgType> superTypes, MethodNode mth) {
|
|
||||||
if (mth.isConstructor() || mth.getAccessFlags().isStatic() || mth.getAccessFlags().isPrivate()) {
|
if (mth.isConstructor() || mth.getAccessFlags().isStatic() || mth.getAccessFlags().isPrivate()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
MethodOverrideAttr attr = processOverrideMethods(cls, mth, superTypes);
|
MethodOverrideAttr attr = processOverrideMethods(mth, superData);
|
||||||
if (attr != null) {
|
if (attr != null) {
|
||||||
|
if (attr.getBaseMethods().isEmpty()) {
|
||||||
|
throw new JadxRuntimeException("No base methods for override attribute: " + attr.getOverrideList());
|
||||||
|
}
|
||||||
mth.addAttr(attr);
|
mth.addAttr(attr);
|
||||||
IMethodDetails baseMth = Utils.last(attr.getOverrideList());
|
IMethodDetails baseMth = Utils.getOne(attr.getBaseMethods());
|
||||||
if (baseMth != null) {
|
if (baseMth != null) {
|
||||||
boolean updated = fixMethodReturnType(mth, baseMth, superTypes);
|
boolean updated = fixMethodReturnType(mth, baseMth, superData);
|
||||||
updated |= fixMethodArgTypes(mth, baseMth, superTypes);
|
updated |= fixMethodArgTypes(mth, baseMth, superData);
|
||||||
if (updated && cls.root().getArgs().isRenameValid()) {
|
if (updated) {
|
||||||
// check if new signature cause method collisions
|
// check if new signature cause method collisions
|
||||||
fixMethodSignatureCollisions(mth);
|
checkMethodSignatureCollisions(mth, mth.root().getArgs().isRenameValid());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private MethodOverrideAttr processOverrideMethods(ClassNode cls, MethodNode mth, List<ArgType> superTypes) {
|
private MethodOverrideAttr processOverrideMethods(MethodNode mth, SuperTypesData superData) {
|
||||||
MethodOverrideAttr result = mth.get(AType.METHOD_OVERRIDE);
|
MethodOverrideAttr result = mth.get(AType.METHOD_OVERRIDE);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
ClassNode cls = mth.getParentClass();
|
||||||
String signature = mth.getMethodInfo().makeSignature(false);
|
String signature = mth.getMethodInfo().makeSignature(false);
|
||||||
List<IMethodDetails> overrideList = new ArrayList<>();
|
List<IMethodDetails> overrideList = new ArrayList<>();
|
||||||
for (ArgType superType : superTypes) {
|
Set<IMethodDetails> baseMethods = new HashSet<>();
|
||||||
ClassNode classNode = cls.root().resolveClass(superType);
|
for (ArgType superType : superData.getSuperTypes()) {
|
||||||
|
ClassNode classNode = mth.root().resolveClass(superType);
|
||||||
if (classNode != null) {
|
if (classNode != null) {
|
||||||
MethodNode ovrdMth = searchOverriddenMethod(classNode, signature);
|
MethodNode ovrdMth = searchOverriddenMethod(classNode, signature);
|
||||||
if (ovrdMth != null && isMethodVisibleInCls(ovrdMth, cls)) {
|
if (ovrdMth != null) {
|
||||||
overrideList.add(ovrdMth);
|
if (isMethodVisibleInCls(ovrdMth, cls)) {
|
||||||
MethodOverrideAttr attr = ovrdMth.get(AType.METHOD_OVERRIDE);
|
overrideList.add(ovrdMth);
|
||||||
if (attr != null) {
|
MethodOverrideAttr attr = ovrdMth.get(AType.METHOD_OVERRIDE);
|
||||||
return buildOverrideAttr(mth, overrideList, attr);
|
if (attr != null) {
|
||||||
|
addBaseMethod(superData, overrideList, baseMethods, superType);
|
||||||
|
return buildOverrideAttr(mth, overrideList, baseMethods, attr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ClspClass clsDetails = cls.root().getClsp().getClsDetails(superType);
|
ClspClass clsDetails = mth.root().getClsp().getClsDetails(superType);
|
||||||
if (clsDetails != null) {
|
if (clsDetails != null) {
|
||||||
Map<String, ClspMethod> methodsMap = clsDetails.getMethodsMap();
|
Map<String, ClspMethod> methodsMap = clsDetails.getMethodsMap();
|
||||||
for (Map.Entry<String, ClspMethod> entry : methodsMap.entrySet()) {
|
for (Map.Entry<String, ClspMethod> entry : methodsMap.entrySet()) {
|
||||||
String mthShortId = entry.getKey();
|
String mthShortId = entry.getKey();
|
||||||
if (mthShortId.startsWith(signature)) {
|
if (mthShortId.startsWith(signature)) {
|
||||||
overrideList.add(entry.getValue());
|
overrideList.add(entry.getValue());
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
addBaseMethod(superData, overrideList, baseMethods, superType);
|
||||||
|
}
|
||||||
|
return buildOverrideAttr(mth, overrideList, baseMethods, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addBaseMethod(SuperTypesData superData, List<IMethodDetails> overrideList, Set<IMethodDetails> baseMethods,
|
||||||
|
ArgType superType) {
|
||||||
|
if (superData.getEndTypes().contains(superType.getObject())) {
|
||||||
|
IMethodDetails last = Utils.last(overrideList);
|
||||||
|
if (last != null) {
|
||||||
|
baseMethods.add(last);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return buildOverrideAttr(mth, overrideList, null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -123,22 +141,24 @@ public class OverrideMethodVisitor extends AbstractVisitor {
|
|||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private MethodOverrideAttr buildOverrideAttr(MethodNode mth, List<IMethodDetails> overrideList,
|
private MethodOverrideAttr buildOverrideAttr(MethodNode mth, List<IMethodDetails> overrideList,
|
||||||
@Nullable MethodOverrideAttr attr) {
|
Set<IMethodDetails> baseMethods, @Nullable MethodOverrideAttr attr) {
|
||||||
if (overrideList.isEmpty() && attr == null) {
|
if (overrideList.isEmpty() && attr == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (attr == null) {
|
if (attr == null) {
|
||||||
// traced to base method
|
// traced to base method
|
||||||
List<IMethodDetails> cleanOverrideList = overrideList.stream().distinct().collect(Collectors.toList());
|
List<IMethodDetails> cleanOverrideList = overrideList.stream().distinct().collect(Collectors.toList());
|
||||||
return applyOverrideAttr(mth, cleanOverrideList, false);
|
return applyOverrideAttr(mth, cleanOverrideList, baseMethods, false);
|
||||||
}
|
}
|
||||||
// trace stopped at already processed method -> start merging
|
// trace stopped at already processed method -> start merging
|
||||||
List<IMethodDetails> mergedOverrideList = Utils.mergeLists(overrideList, attr.getOverrideList());
|
List<IMethodDetails> mergedOverrideList = Utils.mergeLists(overrideList, attr.getOverrideList());
|
||||||
List<IMethodDetails> cleanOverrideList = mergedOverrideList.stream().distinct().collect(Collectors.toList());
|
List<IMethodDetails> cleanOverrideList = mergedOverrideList.stream().distinct().collect(Collectors.toList());
|
||||||
return applyOverrideAttr(mth, cleanOverrideList, true);
|
Set<IMethodDetails> mergedBaseMethods = Utils.mergeSets(baseMethods, attr.getBaseMethods());
|
||||||
|
return applyOverrideAttr(mth, cleanOverrideList, mergedBaseMethods, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private MethodOverrideAttr applyOverrideAttr(MethodNode mth, List<IMethodDetails> overrideList, boolean update) {
|
private MethodOverrideAttr applyOverrideAttr(MethodNode mth, List<IMethodDetails> overrideList,
|
||||||
|
Set<IMethodDetails> baseMethods, boolean update) {
|
||||||
// don't rename method if override list contains not resolved method
|
// don't rename method if override list contains not resolved method
|
||||||
boolean dontRename = overrideList.stream().anyMatch(m -> !(m instanceof MethodNode));
|
boolean dontRename = overrideList.stream().anyMatch(m -> !(m instanceof MethodNode));
|
||||||
SortedSet<MethodNode> relatedMethods = null;
|
SortedSet<MethodNode> relatedMethods = null;
|
||||||
@@ -188,10 +208,10 @@ public class OverrideMethodVisitor extends AbstractVisitor {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mthNode.addAttr(new MethodOverrideAttr(Utils.listTail(overrideList, depth), relatedMethods));
|
mthNode.addAttr(new MethodOverrideAttr(Utils.listTail(overrideList, depth), relatedMethods, baseMethods));
|
||||||
depth++;
|
depth++;
|
||||||
}
|
}
|
||||||
return new MethodOverrideAttr(overrideList, relatedMethods);
|
return new MethodOverrideAttr(overrideList, relatedMethods, baseMethods);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@@ -221,52 +241,92 @@ public class OverrideMethodVisitor extends AbstractVisitor {
|
|||||||
return Objects.equals(superMth.getParentClass().getPackage(), cls.getPackage());
|
return Objects.equals(superMth.getParentClass().getPackage(), cls.getPackage());
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<ArgType> collectSuperTypes(ClassNode cls) {
|
private static final class SuperTypesData {
|
||||||
Map<String, ArgType> superTypes = new LinkedHashMap<>();
|
private final List<ArgType> superTypes;
|
||||||
collectSuperTypes(cls, superTypes);
|
private final Set<String> endTypes;
|
||||||
if (superTypes.isEmpty()) {
|
|
||||||
return Collections.emptyList();
|
private SuperTypesData(List<ArgType> superTypes, Set<String> endTypes) {
|
||||||
|
this.superTypes = superTypes;
|
||||||
|
this.endTypes = endTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ArgType> getSuperTypes() {
|
||||||
|
return superTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<String> getEndTypes() {
|
||||||
|
return endTypes;
|
||||||
}
|
}
|
||||||
return new ArrayList<>(superTypes.values());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void collectSuperTypes(ClassNode cls, Map<String, ArgType> superTypes) {
|
@Nullable
|
||||||
|
private SuperTypesData collectSuperTypes(ClassNode cls) {
|
||||||
|
List<ArgType> superTypes = new ArrayList<>();
|
||||||
|
Set<String> endTypes = new HashSet<>();
|
||||||
|
collectSuperTypes(cls, superTypes, endTypes);
|
||||||
|
if (superTypes.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (endTypes.isEmpty()) {
|
||||||
|
throw new JadxRuntimeException("No end types in class hierarchy: " + cls);
|
||||||
|
}
|
||||||
|
return new SuperTypesData(superTypes, endTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void collectSuperTypes(ClassNode cls, List<ArgType> superTypes, Set<String> endTypes) {
|
||||||
RootNode root = cls.root();
|
RootNode root = cls.root();
|
||||||
|
int k = 0;
|
||||||
ArgType superClass = cls.getSuperClass();
|
ArgType superClass = cls.getSuperClass();
|
||||||
if (superClass != null && !Objects.equals(superClass, ArgType.OBJECT)) {
|
if (superClass != null) {
|
||||||
addSuperType(root, superTypes, superClass);
|
k += addSuperType(root, superTypes, endTypes, superClass);
|
||||||
}
|
}
|
||||||
for (ArgType iface : cls.getInterfaces()) {
|
for (ArgType iface : cls.getInterfaces()) {
|
||||||
addSuperType(root, superTypes, iface);
|
k += addSuperType(root, superTypes, endTypes, iface);
|
||||||
|
}
|
||||||
|
if (k == 0) {
|
||||||
|
endTypes.add(cls.getType().getObject());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addSuperType(RootNode root, Map<String, ArgType> superTypesMap, ArgType superType) {
|
private int addSuperType(RootNode root, List<ArgType> superTypesMap, Set<String> endTypes, ArgType superType) {
|
||||||
superTypesMap.put(superType.getObject(), superType);
|
if (Objects.equals(superType, ArgType.OBJECT)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
superTypesMap.add(superType);
|
||||||
ClassNode classNode = root.resolveClass(superType);
|
ClassNode classNode = root.resolveClass(superType);
|
||||||
if (classNode == null) {
|
if (classNode != null) {
|
||||||
for (String superCls : root.getClsp().getSuperTypes(superType.getObject())) {
|
collectSuperTypes(classNode, superTypesMap, endTypes);
|
||||||
ArgType type = ArgType.object(superCls);
|
return 1;
|
||||||
superTypesMap.put(type.getObject(), type);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
collectSuperTypes(classNode, superTypesMap);
|
|
||||||
}
|
}
|
||||||
|
ClspClass clsDetails = root.getClsp().getClsDetails(superType);
|
||||||
|
if (clsDetails != null) {
|
||||||
|
int k = 0;
|
||||||
|
for (ArgType parentType : clsDetails.getParents()) {
|
||||||
|
k += addSuperType(root, superTypesMap, endTypes, parentType);
|
||||||
|
}
|
||||||
|
if (k == 0) {
|
||||||
|
endTypes.add(superType.getObject());
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
// no info found => treat as hierarchy end
|
||||||
|
endTypes.add(superType.getObject());
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean fixMethodReturnType(MethodNode mth, IMethodDetails baseMth, List<ArgType> superTypes) {
|
private boolean fixMethodReturnType(MethodNode mth, IMethodDetails baseMth, SuperTypesData superData) {
|
||||||
ArgType returnType = mth.getReturnType();
|
ArgType returnType = mth.getReturnType();
|
||||||
if (returnType == ArgType.VOID) {
|
if (returnType == ArgType.VOID) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
boolean updated = updateReturnType(mth, baseMth, superTypes);
|
boolean updated = updateReturnType(mth, baseMth, superData);
|
||||||
if (updated) {
|
if (updated) {
|
||||||
mth.addDebugComment("Return type fixed from '" + returnType + "' to match base method");
|
mth.addDebugComment("Return type fixed from '" + returnType + "' to match base method");
|
||||||
}
|
}
|
||||||
return updated;
|
return updated;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean updateReturnType(MethodNode mth, IMethodDetails baseMth, List<ArgType> superTypes) {
|
private boolean updateReturnType(MethodNode mth, IMethodDetails baseMth, SuperTypesData superData) {
|
||||||
ArgType baseReturnType = baseMth.getReturnType();
|
ArgType baseReturnType = baseMth.getReturnType();
|
||||||
if (mth.getReturnType().equals(baseReturnType)) {
|
if (mth.getReturnType().equals(baseReturnType)) {
|
||||||
return false;
|
return false;
|
||||||
@@ -276,7 +336,7 @@ public class OverrideMethodVisitor extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
TypeCompare typeCompare = mth.root().getTypeUpdate().getTypeCompare();
|
TypeCompare typeCompare = mth.root().getTypeUpdate().getTypeCompare();
|
||||||
ArgType baseCls = baseMth.getMethodInfo().getDeclClass().getType();
|
ArgType baseCls = baseMth.getMethodInfo().getDeclClass().getType();
|
||||||
for (ArgType superType : superTypes) {
|
for (ArgType superType : superData.getSuperTypes()) {
|
||||||
TypeCompareEnum compareResult = typeCompare.compareTypes(superType, baseCls);
|
TypeCompareEnum compareResult = typeCompare.compareTypes(superType, baseCls);
|
||||||
if (compareResult == TypeCompareEnum.NARROW_BY_GENERIC) {
|
if (compareResult == TypeCompareEnum.NARROW_BY_GENERIC) {
|
||||||
ArgType targetRetType = mth.root().getTypeUtils().replaceClassGenerics(superType, baseReturnType);
|
ArgType targetRetType = mth.root().getTypeUtils().replaceClassGenerics(superType, baseReturnType);
|
||||||
@@ -291,7 +351,7 @@ public class OverrideMethodVisitor extends AbstractVisitor {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean fixMethodArgTypes(MethodNode mth, IMethodDetails baseMth, List<ArgType> superTypes) {
|
private boolean fixMethodArgTypes(MethodNode mth, IMethodDetails baseMth, SuperTypesData superData) {
|
||||||
List<ArgType> mthArgTypes = mth.getArgTypes();
|
List<ArgType> mthArgTypes = mth.getArgTypes();
|
||||||
List<ArgType> baseArgTypes = baseMth.getArgTypes();
|
List<ArgType> baseArgTypes = baseMth.getArgTypes();
|
||||||
if (mthArgTypes.equals(baseArgTypes)) {
|
if (mthArgTypes.equals(baseArgTypes)) {
|
||||||
@@ -304,7 +364,7 @@ public class OverrideMethodVisitor extends AbstractVisitor {
|
|||||||
boolean changed = false;
|
boolean changed = false;
|
||||||
List<ArgType> newArgTypes = new ArrayList<>(argCount);
|
List<ArgType> newArgTypes = new ArrayList<>(argCount);
|
||||||
for (int argNum = 0; argNum < argCount; argNum++) {
|
for (int argNum = 0; argNum < argCount; argNum++) {
|
||||||
ArgType newType = updateArgType(mth, baseMth, superTypes, argNum);
|
ArgType newType = updateArgType(mth, baseMth, superData, argNum);
|
||||||
if (newType != null) {
|
if (newType != null) {
|
||||||
changed = true;
|
changed = true;
|
||||||
newArgTypes.add(newType);
|
newArgTypes.add(newType);
|
||||||
@@ -318,7 +378,7 @@ public class OverrideMethodVisitor extends AbstractVisitor {
|
|||||||
return changed;
|
return changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ArgType updateArgType(MethodNode mth, IMethodDetails baseMth, List<ArgType> superTypes, int argNum) {
|
private ArgType updateArgType(MethodNode mth, IMethodDetails baseMth, SuperTypesData superData, int argNum) {
|
||||||
ArgType arg = mth.getArgTypes().get(argNum);
|
ArgType arg = mth.getArgTypes().get(argNum);
|
||||||
ArgType baseArg = baseMth.getArgTypes().get(argNum);
|
ArgType baseArg = baseMth.getArgTypes().get(argNum);
|
||||||
if (arg.equals(baseArg)) {
|
if (arg.equals(baseArg)) {
|
||||||
@@ -329,7 +389,7 @@ public class OverrideMethodVisitor extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
TypeCompare typeCompare = mth.root().getTypeUpdate().getTypeCompare();
|
TypeCompare typeCompare = mth.root().getTypeUpdate().getTypeCompare();
|
||||||
ArgType baseCls = baseMth.getMethodInfo().getDeclClass().getType();
|
ArgType baseCls = baseMth.getMethodInfo().getDeclClass().getType();
|
||||||
for (ArgType superType : superTypes) {
|
for (ArgType superType : superData.getSuperTypes()) {
|
||||||
TypeCompareEnum compareResult = typeCompare.compareTypes(superType, baseCls);
|
TypeCompareEnum compareResult = typeCompare.compareTypes(superType, baseCls);
|
||||||
if (compareResult == TypeCompareEnum.NARROW_BY_GENERIC) {
|
if (compareResult == TypeCompareEnum.NARROW_BY_GENERIC) {
|
||||||
ArgType targetArgType = mth.root().getTypeUtils().replaceClassGenerics(superType, baseArg);
|
ArgType targetArgType = mth.root().getTypeUtils().replaceClassGenerics(superType, baseArg);
|
||||||
@@ -343,7 +403,7 @@ public class OverrideMethodVisitor extends AbstractVisitor {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fixMethodSignatureCollisions(MethodNode mth) {
|
private void checkMethodSignatureCollisions(MethodNode mth, boolean rename) {
|
||||||
String mthName = mth.getMethodInfo().getAlias();
|
String mthName = mth.getMethodInfo().getAlias();
|
||||||
String newSignature = MethodInfo.makeShortId(mthName, mth.getArgTypes(), null);
|
String newSignature = MethodInfo.makeShortId(mthName, mth.getArgTypes(), null);
|
||||||
for (MethodNode otherMth : mth.getParentClass().getMethods()) {
|
for (MethodNode otherMth : mth.getParentClass().getMethods()) {
|
||||||
@@ -351,12 +411,16 @@ public class OverrideMethodVisitor extends AbstractVisitor {
|
|||||||
if (otherMthName.equals(mthName) && otherMth != mth) {
|
if (otherMthName.equals(mthName) && otherMth != mth) {
|
||||||
String otherSignature = otherMth.getMethodInfo().makeSignature(true, false);
|
String otherSignature = otherMth.getMethodInfo().makeSignature(true, false);
|
||||||
if (otherSignature.equals(newSignature)) {
|
if (otherSignature.equals(newSignature)) {
|
||||||
if (otherMth.contains(AFlag.DONT_RENAME) || otherMth.contains(AType.METHOD_OVERRIDE)) {
|
if (rename) {
|
||||||
otherMth.addWarnComment("Can't rename method to resolve collision");
|
if (otherMth.contains(AFlag.DONT_RENAME) || otherMth.contains(AType.METHOD_OVERRIDE)) {
|
||||||
} else {
|
otherMth.addWarnComment("Can't rename method to resolve collision");
|
||||||
otherMth.getMethodInfo().setAlias(makeNewAlias(otherMth));
|
} else {
|
||||||
otherMth.addAttr(new RenameReasonAttr("avoid collision after fix types in other method"));
|
otherMth.getMethodInfo().setAlias(makeNewAlias(otherMth));
|
||||||
|
otherMth.addAttr(new RenameReasonAttr("avoid collision after fix types in other method"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
otherMth.addAttr(new MethodBridgeAttr(mth));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,18 +5,28 @@ import java.util.Collections;
|
|||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import jadx.api.plugins.input.data.IFieldRef;
|
||||||
|
import jadx.api.plugins.input.data.annotations.AnnotationVisibility;
|
||||||
|
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||||
|
import jadx.api.plugins.input.data.annotations.IAnnotation;
|
||||||
|
import jadx.api.plugins.input.data.attributes.JadxAttrType;
|
||||||
|
import jadx.api.plugins.input.data.attributes.types.AnnotationsAttr;
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
import jadx.core.dex.attributes.AType;
|
import jadx.core.dex.attributes.AType;
|
||||||
|
import jadx.core.dex.attributes.AttrNode;
|
||||||
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
|
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
|
||||||
import jadx.core.dex.attributes.nodes.LineAttrNode;
|
import jadx.core.dex.attributes.nodes.LineAttrNode;
|
||||||
|
import jadx.core.dex.info.FieldInfo;
|
||||||
import jadx.core.dex.instructions.ArithNode;
|
import jadx.core.dex.instructions.ArithNode;
|
||||||
import jadx.core.dex.instructions.ArithOp;
|
import jadx.core.dex.instructions.ArithOp;
|
||||||
import jadx.core.dex.instructions.InsnType;
|
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.InsnArg;
|
||||||
import jadx.core.dex.instructions.args.InsnWrapArg;
|
import jadx.core.dex.instructions.args.InsnWrapArg;
|
||||||
import jadx.core.dex.instructions.args.RegisterArg;
|
import jadx.core.dex.instructions.args.RegisterArg;
|
||||||
@@ -24,6 +34,7 @@ import jadx.core.dex.instructions.mods.ConstructorInsn;
|
|||||||
import jadx.core.dex.instructions.mods.TernaryInsn;
|
import jadx.core.dex.instructions.mods.TernaryInsn;
|
||||||
import jadx.core.dex.nodes.BlockNode;
|
import jadx.core.dex.nodes.BlockNode;
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
|
import jadx.core.dex.nodes.FieldNode;
|
||||||
import jadx.core.dex.nodes.InsnContainer;
|
import jadx.core.dex.nodes.InsnContainer;
|
||||||
import jadx.core.dex.nodes.InsnNode;
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
@@ -53,6 +64,7 @@ public class PrepareForCodeGen extends AbstractVisitor {
|
|||||||
if (cls.root().getArgs().isDebugInfo()) {
|
if (cls.root().getArgs().isDebugInfo()) {
|
||||||
setClassSourceLine(cls);
|
setClassSourceLine(cls);
|
||||||
}
|
}
|
||||||
|
collectFieldsUsageInAnnotations(cls);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,8 +81,10 @@ public class PrepareForCodeGen extends AbstractVisitor {
|
|||||||
checkInline(block);
|
checkInline(block);
|
||||||
removeParenthesis(block);
|
removeParenthesis(block);
|
||||||
modifyArith(block);
|
modifyArith(block);
|
||||||
|
checkConstUsage(block);
|
||||||
}
|
}
|
||||||
moveConstructorInConstructor(mth);
|
moveConstructorInConstructor(mth);
|
||||||
|
collectFieldsUsageInAnnotations(mth, mth);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void removeInstructions(BlockNode block) {
|
private static void removeInstructions(BlockNode block) {
|
||||||
@@ -122,6 +136,38 @@ public class PrepareForCodeGen extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add explicit type for non int constants
|
||||||
|
*/
|
||||||
|
private static void checkConstUsage(BlockNode block) {
|
||||||
|
for (InsnNode blockInsn : block.getInstructions()) {
|
||||||
|
blockInsn.visitInsns(insn -> {
|
||||||
|
if (forbidExplicitType(insn.getType())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (InsnArg arg : insn.getArguments()) {
|
||||||
|
if (arg.isLiteral() && arg.getType() != ArgType.INT) {
|
||||||
|
arg.add(AFlag.EXPLICIT_PRIMITIVE_TYPE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean forbidExplicitType(InsnType type) {
|
||||||
|
switch (type) {
|
||||||
|
case CONST:
|
||||||
|
case CAST:
|
||||||
|
case IF:
|
||||||
|
case FILLED_NEW_ARRAY:
|
||||||
|
case APUT:
|
||||||
|
case ARITH:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void removeParenthesis(BlockNode block) {
|
private static void removeParenthesis(BlockNode block) {
|
||||||
for (InsnNode insn : block.getInstructions()) {
|
for (InsnNode insn : block.getInstructions()) {
|
||||||
removeParenthesis(insn);
|
removeParenthesis(insn);
|
||||||
@@ -276,4 +322,61 @@ public class PrepareForCodeGen extends AbstractVisitor {
|
|||||||
cls.setSourceLine(minLine - 1);
|
cls.setSourceLine(minLine - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void collectFieldsUsageInAnnotations(ClassNode cls) {
|
||||||
|
MethodNode useMth = cls.getDefaultConstructor();
|
||||||
|
if (useMth == null && !cls.getMethods().isEmpty()) {
|
||||||
|
useMth = cls.getMethods().get(0);
|
||||||
|
}
|
||||||
|
if (useMth == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
collectFieldsUsageInAnnotations(useMth, cls);
|
||||||
|
MethodNode finalUseMth = useMth;
|
||||||
|
cls.getFields().forEach(f -> collectFieldsUsageInAnnotations(finalUseMth, f));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void collectFieldsUsageInAnnotations(MethodNode mth, AttrNode attrNode) {
|
||||||
|
AnnotationsAttr annotationsList = attrNode.get(JadxAttrType.ANNOTATION_LIST);
|
||||||
|
if (annotationsList == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (IAnnotation annotation : annotationsList.getAll()) {
|
||||||
|
if (annotation.getVisibility() == AnnotationVisibility.SYSTEM) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (Map.Entry<String, EncodedValue> entry : annotation.getValues().entrySet()) {
|
||||||
|
checkEncodedValue(mth, entry.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private void checkEncodedValue(MethodNode mth, EncodedValue encodedValue) {
|
||||||
|
switch (encodedValue.getType()) {
|
||||||
|
case ENCODED_FIELD:
|
||||||
|
Object fieldData = encodedValue.getValue();
|
||||||
|
FieldInfo fieldInfo;
|
||||||
|
if (fieldData instanceof IFieldRef) {
|
||||||
|
fieldInfo = FieldInfo.fromRef(mth.root(), (IFieldRef) fieldData);
|
||||||
|
} else {
|
||||||
|
fieldInfo = (FieldInfo) fieldData;
|
||||||
|
}
|
||||||
|
FieldNode fieldNode = mth.root().resolveField(fieldInfo);
|
||||||
|
if (fieldNode != null) {
|
||||||
|
fieldNode.addUseIn(mth);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ENCODED_ANNOTATION:
|
||||||
|
IAnnotation annotation = (IAnnotation) encodedValue.getValue();
|
||||||
|
annotation.getValues().forEach((k, v) -> checkEncodedValue(mth, v));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ENCODED_ARRAY:
|
||||||
|
List<EncodedValue> valueList = (List<EncodedValue>) encodedValue.getValue();
|
||||||
|
valueList.forEach(v -> checkEncodedValue(mth, v));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,26 @@
|
|||||||
package jadx.core.dex.visitors;
|
package jadx.core.dex.visitors;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
|
import jadx.core.dex.attributes.AType;
|
||||||
|
import jadx.core.dex.attributes.nodes.AnonymousClassAttr;
|
||||||
|
import jadx.core.dex.info.AccessInfo;
|
||||||
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
import jadx.core.dex.nodes.FieldNode;
|
import jadx.core.dex.nodes.FieldNode;
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
import jadx.core.dex.nodes.RootNode;
|
import jadx.core.dex.nodes.RootNode;
|
||||||
import jadx.core.dex.visitors.usage.UsageInfoVisitor;
|
import jadx.core.dex.visitors.usage.UsageInfoVisitor;
|
||||||
|
import jadx.core.utils.ListUtils;
|
||||||
import jadx.core.utils.exceptions.JadxException;
|
import jadx.core.utils.exceptions.JadxException;
|
||||||
|
|
||||||
@JadxVisitor(
|
@JadxVisitor(
|
||||||
@@ -17,95 +32,267 @@ import jadx.core.utils.exceptions.JadxException;
|
|||||||
)
|
)
|
||||||
public class ProcessAnonymous extends AbstractVisitor {
|
public class ProcessAnonymous extends AbstractVisitor {
|
||||||
|
|
||||||
private boolean inlineAnonymous;
|
private boolean inlineAnonymousClasses;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(RootNode root) {
|
public void init(RootNode root) {
|
||||||
inlineAnonymous = root.getArgs().isInlineAnonymousClasses();
|
inlineAnonymousClasses = root.getArgs().isInlineAnonymousClasses();
|
||||||
|
if (!inlineAnonymousClasses) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (ClassNode cls : root.getClasses()) {
|
||||||
|
markAnonymousClass(cls);
|
||||||
|
}
|
||||||
|
mergeAnonymousDeps(root);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean visit(ClassNode cls) throws JadxException {
|
public boolean visit(ClassNode cls) throws JadxException {
|
||||||
if (!inlineAnonymous) {
|
if (inlineAnonymousClasses && cls.contains(AFlag.CLASS_UNLOADED)) {
|
||||||
return false;
|
// enter only on class reload
|
||||||
|
visitClassAndInners(cls);
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void visitClassAndInners(ClassNode cls) {
|
||||||
markAnonymousClass(cls);
|
markAnonymousClass(cls);
|
||||||
return true;
|
cls.getInnerClasses().forEach(this::visitClassAndInners);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void markAnonymousClass(ClassNode cls) {
|
private static void markAnonymousClass(ClassNode cls) {
|
||||||
if (usedOnlyOnce(cls) || isAnonymous(cls) || isLambdaCls(cls)) {
|
if (!canBeAnonymous(cls)) {
|
||||||
if (isStaticFieldUsedOutside(cls)) {
|
return;
|
||||||
|
}
|
||||||
|
MethodNode anonymousConstructor = checkUsage(cls);
|
||||||
|
if (anonymousConstructor == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ArgType baseType = getBaseType(cls);
|
||||||
|
if (baseType == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ClassNode outerCls = anonymousConstructor.getUseIn().get(0).getParentClass();
|
||||||
|
outerCls.addInlinedClass(cls);
|
||||||
|
cls.addAttr(new AnonymousClassAttr(outerCls, baseType));
|
||||||
|
cls.add(AFlag.DONT_GENERATE);
|
||||||
|
anonymousConstructor.add(AFlag.ANONYMOUS_CONSTRUCTOR);
|
||||||
|
|
||||||
|
// force anonymous class to be processed before outer class,
|
||||||
|
// actual usage of outer class will be removed at anonymous class process,
|
||||||
|
// see ModVisitor.processAnonymousConstructor method
|
||||||
|
ClassNode topOuterCls = outerCls.getTopParentClass();
|
||||||
|
cls.removeDependency(topOuterCls);
|
||||||
|
ListUtils.safeRemove(outerCls.getUseIn(), cls);
|
||||||
|
|
||||||
|
// move dependency to codegen stage
|
||||||
|
if (cls.isTopClass()) {
|
||||||
|
topOuterCls.removeDependency(cls);
|
||||||
|
topOuterCls.addCodegenDep(cls);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void undoAnonymousMark(ClassNode cls) {
|
||||||
|
AnonymousClassAttr attr = cls.get(AType.ANONYMOUS_CLASS);
|
||||||
|
ClassNode outerCls = attr.getOuterCls();
|
||||||
|
cls.setDependencies(ListUtils.safeAdd(cls.getDependencies(), outerCls.getTopParentClass()));
|
||||||
|
outerCls.setUseIn(ListUtils.safeAdd(outerCls.getUseIn(), cls));
|
||||||
|
|
||||||
|
cls.remove(AType.ANONYMOUS_CLASS);
|
||||||
|
cls.remove(AFlag.DONT_GENERATE);
|
||||||
|
for (MethodNode mth : cls.getMethods()) {
|
||||||
|
if (mth.isConstructor()) {
|
||||||
|
mth.remove(AFlag.ANONYMOUS_CONSTRUCTOR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cls.addDebugComment("Anonymous mark cleared");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void mergeAnonymousDeps(RootNode root) {
|
||||||
|
// Collect edges to build bidirectional tree:
|
||||||
|
// inline edge: anonymous -> outer (one-to-one)
|
||||||
|
// use edges: outer -> *anonymous (one-to-many)
|
||||||
|
Map<ClassNode, ClassNode> inlineMap = new HashMap<>();
|
||||||
|
Map<ClassNode, List<ClassNode>> useMap = new HashMap<>();
|
||||||
|
for (ClassNode anonymousCls : root.getClasses()) {
|
||||||
|
AnonymousClassAttr attr = anonymousCls.get(AType.ANONYMOUS_CLASS);
|
||||||
|
if (attr != null) {
|
||||||
|
ClassNode outerCls = attr.getOuterCls();
|
||||||
|
List<ClassNode> list = useMap.get(outerCls);
|
||||||
|
if (list == null || list.isEmpty()) {
|
||||||
|
list = new ArrayList<>(2);
|
||||||
|
useMap.put(outerCls, list);
|
||||||
|
}
|
||||||
|
list.add(anonymousCls);
|
||||||
|
useMap.putIfAbsent(anonymousCls, Collections.emptyList()); // put leaf explicitly
|
||||||
|
inlineMap.put(anonymousCls, outerCls);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (inlineMap.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// starting from leaf process deps in nodes up to root
|
||||||
|
Set<ClassNode> added = new HashSet<>();
|
||||||
|
useMap.forEach((key, list) -> {
|
||||||
|
if (list.isEmpty()) {
|
||||||
|
added.clear();
|
||||||
|
updateDeps(key, inlineMap, added);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
for (ClassNode cls : root.getClasses()) {
|
||||||
|
List<ClassNode> deps = cls.getCodegenDeps();
|
||||||
|
if (deps.size() > 1) {
|
||||||
|
// distinct sorted dep, reusing collections to reduce memory allocations :)
|
||||||
|
added.clear();
|
||||||
|
added.addAll(deps);
|
||||||
|
deps.clear();
|
||||||
|
deps.addAll(added);
|
||||||
|
Collections.sort(deps);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateDeps(ClassNode leafCls, Map<ClassNode, ClassNode> inlineMap, Set<ClassNode> added) {
|
||||||
|
ClassNode topNode;
|
||||||
|
ClassNode current = leafCls;
|
||||||
|
while (true) {
|
||||||
|
if (!added.add(current)) {
|
||||||
|
current.addWarnComment("Loop in anonymous inline: " + current + ", path: " + added);
|
||||||
|
added.forEach(ProcessAnonymous::undoAnonymousMark);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
cls.add(AFlag.ANONYMOUS_CLASS);
|
ClassNode next = inlineMap.get(current);
|
||||||
cls.add(AFlag.DONT_GENERATE);
|
if (next == null) {
|
||||||
|
topNode = current.getTopParentClass();
|
||||||
for (MethodNode mth : cls.getMethods()) {
|
break;
|
||||||
if (mth.isConstructor()) {
|
|
||||||
mth.add(AFlag.ANONYMOUS_CONSTRUCTOR);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
current = next;
|
||||||
|
}
|
||||||
|
if (added.size() <= 2) {
|
||||||
|
// first level deps already processed
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<ClassNode> deps = topNode.getCodegenDeps();
|
||||||
|
if (deps.isEmpty()) {
|
||||||
|
deps = new ArrayList<>(added.size());
|
||||||
|
topNode.setCodegenDeps(deps);
|
||||||
|
}
|
||||||
|
for (ClassNode add : added) {
|
||||||
|
deps.add(add.getTopParentClass());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isStaticFieldUsedOutside(ClassNode cls) {
|
private static boolean canBeAnonymous(ClassNode cls) {
|
||||||
ClassNode topCls = cls.getTopParentClass();
|
if (cls.getAccessFlags().isSynthetic()) {
|
||||||
for (FieldNode field : cls.getFields()) {
|
return true;
|
||||||
if (field.isStatic()) {
|
}
|
||||||
for (MethodNode useMth : field.getUseIn()) {
|
String shortName = cls.getClassInfo().getShortName();
|
||||||
ClassNode useCls = useMth.getParentClass().getTopParentClass();
|
if (shortName.contains("$") || Character.isDigit(shortName.charAt(0))) {
|
||||||
if (!useCls.equals(topCls)) {
|
return true;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean usedOnlyOnce(ClassNode cls) {
|
|
||||||
if (cls.getUseIn().size() == 1 && cls.getUseInMth().size() == 1) {
|
if (cls.getUseIn().size() == 1 && cls.getUseInMth().size() == 1) {
|
||||||
// used only once
|
MethodNode useMth = cls.getUseInMth().get(0);
|
||||||
boolean synthetic = cls.getAccessFlags().isSynthetic() || cls.getClassInfo().getShortName().contains("$");
|
// allow use in enum class init
|
||||||
if (synthetic) {
|
return useMth.getMethodInfo().isClassInit() && useMth.getParentClass().isEnum();
|
||||||
// must have only one constructor which used only once
|
|
||||||
MethodNode ctr = null;
|
|
||||||
for (MethodNode mth : cls.getMethods()) {
|
|
||||||
if (mth.isConstructor()) {
|
|
||||||
if (ctr != null) {
|
|
||||||
ctr = null;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
ctr = mth;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ctr != null && ctr.getUseIn().size() == 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isAnonymous(ClassNode cls) {
|
/**
|
||||||
return cls.getClassInfo().isInner()
|
* Checks:
|
||||||
&& Character.isDigit(cls.getClassInfo().getShortName().charAt(0))
|
* - class have only one constructor which used only once (allow common code for field init)
|
||||||
&& cls.getMethods().stream().filter(MethodNode::isConstructor).count() == 1;
|
* - methods or fields not used outside (allow only nested inner classes with synthetic usage)
|
||||||
}
|
*
|
||||||
|
* @return anonymous constructor method
|
||||||
private static boolean isLambdaCls(ClassNode cls) {
|
*/
|
||||||
return cls.getAccessFlags().isSynthetic()
|
private static MethodNode checkUsage(ClassNode cls) {
|
||||||
&& cls.getAccessFlags().isFinal()
|
MethodNode ctr = ListUtils.filterOnlyOne(cls.getMethods(), MethodNode::isConstructor);
|
||||||
&& cls.getClassInfo().getRawName().contains(".-$$Lambda$")
|
if (ctr == null) {
|
||||||
&& countStaticFields(cls) == 0;
|
return null;
|
||||||
}
|
}
|
||||||
|
if (ctr.getUseIn().size() != 1) {
|
||||||
private static int countStaticFields(ClassNode cls) {
|
// check if used in common field init in all constructors
|
||||||
int c = 0;
|
if (!checkForCommonFieldInit(ctr)) {
|
||||||
for (FieldNode field : cls.getFields()) {
|
return null;
|
||||||
if (field.getAccessFlags().isStatic()) {
|
|
||||||
c++;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return c;
|
MethodNode ctrUseMth = ctr.getUseIn().get(0);
|
||||||
|
ClassNode ctrUseCls = ctrUseMth.getParentClass();
|
||||||
|
if (ctrUseCls.equals(cls)) {
|
||||||
|
// exclude self usage
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (MethodNode mth : cls.getMethods()) {
|
||||||
|
if (mth == ctr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (MethodNode useMth : mth.getUseIn()) {
|
||||||
|
if (useMth.equals(ctrUseMth)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (badMethodUsage(cls, useMth, mth.getAccessFlags())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (FieldNode field : cls.getFields()) {
|
||||||
|
for (MethodNode useMth : field.getUseIn()) {
|
||||||
|
if (badMethodUsage(cls, useMth, field.getAccessFlags())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ctr;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean badMethodUsage(ClassNode cls, MethodNode useMth, AccessInfo accessFlags) {
|
||||||
|
ClassNode useCls = useMth.getParentClass();
|
||||||
|
if (useCls.equals(cls)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (accessFlags.isSynthetic()) {
|
||||||
|
// allow synthetic usage in inner class
|
||||||
|
return !useCls.getParentClass().equals(cls);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks:
|
||||||
|
* + all in constructors
|
||||||
|
* + all usage in one class
|
||||||
|
* - same field put (ignored: methods not loaded yet)
|
||||||
|
*/
|
||||||
|
private static boolean checkForCommonFieldInit(MethodNode ctrMth) {
|
||||||
|
List<MethodNode> ctrUse = ctrMth.getUseIn();
|
||||||
|
if (ctrUse.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ClassNode firstUseCls = ctrUse.get(0).getParentClass();
|
||||||
|
return ListUtils.allMatch(ctrUse, m -> m.isConstructor() && m.getParentClass().equals(firstUseCls));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static ArgType getBaseType(ClassNode cls) {
|
||||||
|
int interfacesCount = cls.getInterfaces().size();
|
||||||
|
if (interfacesCount > 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
ArgType superCls = cls.getSuperClass();
|
||||||
|
if (superCls == null || superCls.equals(ArgType.OBJECT)) {
|
||||||
|
if (interfacesCount == 1) {
|
||||||
|
return cls.getInterfaces().get(0);
|
||||||
|
}
|
||||||
|
return ArgType.OBJECT;
|
||||||
|
}
|
||||||
|
if (interfacesCount == 0) {
|
||||||
|
return superCls;
|
||||||
|
}
|
||||||
|
// check if super class already implement that interface (weird case)
|
||||||
|
ArgType interfaceType = cls.getInterfaces().get(0);
|
||||||
|
if (cls.root().getClsp().isImplements(superCls.getObject(), interfaceType.getObject())) {
|
||||||
|
return superCls;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user