Compare commits
138 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fcd58ae76f | |||
| df380dea27 | |||
| 9d88592391 | |||
| c906c11b0f | |||
| 4fbc56cdb0 | |||
| 98c0416b20 | |||
| fa41874e30 | |||
| 2aa6c99c90 | |||
| 5f60c0f1bb | |||
| cb741db623 | |||
| 1df217c4a0 | |||
| 81f209ba9e | |||
| 34a31aa7df | |||
| 5099e02c9b | |||
| f364b39b29 | |||
| 4cd4746f9a | |||
| 6448f0e32b | |||
| e07332d49a | |||
| bd8a44c4c9 | |||
| 21e94d8d5c | |||
| 7b1c7b967a | |||
| e4b19ab560 | |||
| 49137c9751 | |||
| 0606c90f22 | |||
| 65ade379a6 | |||
| a06df187c9 | |||
| e784c7f7df | |||
| a717392379 | |||
| a71b3a71d8 | |||
| 3366bf3dec | |||
| a505534197 | |||
| 357706b070 | |||
| e02434d135 | |||
| 4586015fc0 | |||
| 1832f2aee3 | |||
| 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 |
@@ -0,0 +1,7 @@
|
||||
version: 2
|
||||
updates:
|
||||
# Set update schedule for GitHub Actions
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
@@ -0,0 +1,86 @@
|
||||
name: Build Artifacts
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master, build-test ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'adopt'
|
||||
java-version: 8
|
||||
|
||||
- name: Set jadx version
|
||||
run: |
|
||||
JADX_LAST_TAG=$(git describe --abbrev=0 --tags)
|
||||
JADX_VERSION="${JADX_LAST_TAG:1}.$GITHUB_RUN_NUMBER-${GITHUB_SHA:0:8}"
|
||||
echo "JADX_VERSION=$JADX_VERSION" >> $GITHUB_ENV
|
||||
|
||||
- uses: burrunan/gradle-cache-action@v1
|
||||
name: Build with Gradle
|
||||
env:
|
||||
TERM: dumb
|
||||
with:
|
||||
arguments: clean dist copyExe
|
||||
|
||||
- name: Save bundle artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ format('jadx-{0}', env.JADX_VERSION) }}
|
||||
# Waiting fix for https://github.com/actions/upload-artifact/issues/39 to upload zip file
|
||||
# Upload unpacked files for now
|
||||
path: build/jadx/**/*
|
||||
if-no-files-found: error
|
||||
retention-days: 30
|
||||
|
||||
- name: Save exe artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ format('jadx-gui-{0}-no-jre-win.exe', env.JADX_VERSION) }}
|
||||
path: build/*.exe
|
||||
if-no-files-found: error
|
||||
retention-days: 30
|
||||
|
||||
build-win-bundle:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up JDK
|
||||
uses: oracle-actions/setup-java@v1 # set latest java version by default
|
||||
|
||||
- name: Print Java version
|
||||
shell: bash
|
||||
run: java -version
|
||||
|
||||
- name: Set jadx version
|
||||
shell: bash
|
||||
run: |
|
||||
JADX_LAST_TAG=$(git describe --abbrev=0 --tags)
|
||||
JADX_VERSION="${JADX_LAST_TAG:1}.$GITHUB_RUN_NUMBER-${GITHUB_SHA:0:8}"
|
||||
echo "JADX_VERSION=$JADX_VERSION" >> $GITHUB_ENV
|
||||
|
||||
- uses: gradle/gradle-build-action@v2
|
||||
name: Build with Gradle
|
||||
env:
|
||||
TERM: dumb
|
||||
with:
|
||||
arguments: clean dist -PbundleJRE=true
|
||||
|
||||
- name: Save exe bundle artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ format('jadx-gui-{0}-with-jre-win', env.JADX_VERSION) }}
|
||||
path: jadx-gui/build/*-with-jre-win/*
|
||||
if-no-files-found: error
|
||||
retention-days: 30
|
||||
@@ -0,0 +1,28 @@
|
||||
name: Build Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master, build-test ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'adopt'
|
||||
java-version: 8
|
||||
|
||||
- uses: burrunan/gradle-cache-action@v1
|
||||
name: Build with Gradle
|
||||
env:
|
||||
TERM: dumb
|
||||
with:
|
||||
arguments: clean build dist copyExe --warning-mode=all
|
||||
@@ -1,52 +0,0 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 8
|
||||
|
||||
- name: Set jadx version
|
||||
run: |
|
||||
JADX_LAST_TAG=$(git describe --abbrev=0 --tags)
|
||||
JADX_VERSION="${JADX_LAST_TAG:1}.$GITHUB_RUN_NUMBER-${GITHUB_SHA:0:8}"
|
||||
echo "JADX_VERSION=$JADX_VERSION" >> $GITHUB_ENV
|
||||
|
||||
- uses: burrunan/gradle-cache-action@v1
|
||||
name: Build with Gradle
|
||||
env:
|
||||
TERM: dumb
|
||||
TEST_INPUT_PLUGIN: dx
|
||||
with:
|
||||
arguments: clean build dist copyExe --warning-mode=all
|
||||
|
||||
- name: Save bundle artifact
|
||||
if: success() && github.event_name == 'push'
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ${{ format('jadx-{0}', env.JADX_VERSION) }}
|
||||
# Waiting fix for https://github.com/actions/upload-artifact/issues/39 to upload zip file
|
||||
# Upload unpacked files for now
|
||||
path: build/jadx/**/*
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Save exe artifact
|
||||
if: success() && github.event_name == 'push'
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ${{ format('jadx-gui-{0}-no-jre-win.exe', env.JADX_VERSION) }}
|
||||
path: build/*.exe
|
||||
if-no-files-found: error
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
queries: +security-extended
|
||||
languages: ${{ matrix.language }}
|
||||
@@ -38,4 +38,4 @@ jobs:
|
||||
./gradlew clean build -x checkstyleTest -x checkstyleMain -x test -x ':jadx-core:testClasses'
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
uses: github/codeql-action/analyze@v2
|
||||
|
||||
+2
-1
@@ -35,4 +35,5 @@ jadx-output/
|
||||
*.orig
|
||||
quark.json
|
||||
|
||||
cliff.toml
|
||||
cliff.toml
|
||||
jadx-gui/src/main/resources/logback.xml
|
||||
|
||||
@@ -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,13 +5,14 @@
|
||||
[](https://github.com/skylot/jadx/actions?query=workflow%3ABuild)
|
||||
[](https://lgtm.com/projects/g/skylot/jadx/alerts/)
|
||||
[](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)
|
||||
|
||||
**jadx** - Dex to Java decompiler
|
||||
|
||||
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
|
||||
: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:**
|
||||
- decompile Dalvik bytecode to java classes from APK, dex, aar, aab and zip files
|
||||
@@ -23,15 +24,18 @@ Command line and GUI tools for producing Java source code from Android Dex and A
|
||||
- jump to declaration
|
||||
- find usage
|
||||
- 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)
|
||||
|
||||
<img src="https://user-images.githubusercontent.com/118523/142730720-839f017e-38db-423e-b53f-39f5f0a0316f.png" width="700"/>
|
||||
|
||||
### Download
|
||||
- release from [github: ](https://github.com/skylot/jadx/releases/latest)
|
||||
- latest [unstable build](https://nightly.link/skylot/jadx/workflows/build/master)
|
||||
- release
|
||||
from [github: ](https://github.com/skylot/jadx/releases/latest)
|
||||
- latest [unstable build](https://nightly.link/skylot/jadx/workflows/build-artifacts/master)
|
||||
|
||||
After download unpack zip file go to `bin` directory and run:
|
||||
- `jadx` - command line version
|
||||
@@ -39,7 +43,7 @@ After download unpack zip file go to `bin` directory and run:
|
||||
|
||||
On Windows run `.bat` files with double-click\
|
||||
**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
|
||||
1. Arch linux
|
||||
@@ -76,10 +80,16 @@ options:
|
||||
-dr, --output-dir-res - output directory for resources
|
||||
-r, --no-res - do not decode resources
|
||||
-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
|
||||
-e, --export-gradle - save as android gradle project
|
||||
-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)
|
||||
--no-imports - disable use of imports, always write entire package name
|
||||
--no-debug-info - disable debug info
|
||||
@@ -93,7 +103,12 @@ options:
|
||||
--deobf-min - min length of name, renamed if shorter, default: 3
|
||||
--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-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-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
|
||||
@@ -106,7 +121,7 @@ options:
|
||||
--fs-case-sensitive - treat filesystem as case sensitive, false by default
|
||||
--cfg - save methods control flow graph to dot file
|
||||
--raw-cfg - save methods control flow graph (use raw instructions)
|
||||
-f, --fallback - make simple dump (using goto instead of 'if', 'for', etc)
|
||||
-f, --fallback - set '--decompilation-mode' to 'fallback' (deprecated)
|
||||
--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
|
||||
@@ -114,11 +129,20 @@ options:
|
||||
-q, --quiet - turn off output (set --log-level to QUIET)
|
||||
--version - print jadx version
|
||||
-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:
|
||||
jadx -d out classes.dex
|
||||
jadx --rename-flags "none" classes.dex
|
||||
jadx --rename-flags "valid, printable" classes.dex
|
||||
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
|
||||
|
||||
|
||||
+7
-13
@@ -1,6 +1,6 @@
|
||||
plugins {
|
||||
id 'com.github.ben-manes.versions' version '0.41.0'
|
||||
id 'com.diffplug.spotless' version '6.2.0'
|
||||
id 'com.github.ben-manes.versions' version '0.42.0'
|
||||
id 'com.diffplug.spotless' version '6.6.1'
|
||||
}
|
||||
|
||||
ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev"
|
||||
@@ -14,7 +14,6 @@ allprojects {
|
||||
version = jadxVersion
|
||||
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
|
||||
compileJava {
|
||||
options.encoding = "UTF-8"
|
||||
@@ -27,23 +26,23 @@ allprojects {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'org.slf4j:slf4j-api:1.7.33'
|
||||
implementation 'org.slf4j:slf4j-api:1.7.36'
|
||||
compileOnly 'org.jetbrains:annotations:23.0.0'
|
||||
|
||||
testImplementation 'ch.qos.logback:logback-classic:1.2.10'
|
||||
testImplementation 'ch.qos.logback:logback-classic:1.2.11'
|
||||
testImplementation 'org.hamcrest:hamcrest-library:2.2'
|
||||
testImplementation 'org.mockito:mockito-core:4.2.0'
|
||||
testImplementation 'org.mockito:mockito-core:4.6.0'
|
||||
testImplementation 'org.assertj:assertj-core:3.22.0'
|
||||
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
|
||||
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'
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
maxParallelForks = Runtime.runtime.availableProcessors()
|
||||
}
|
||||
|
||||
repositories {
|
||||
@@ -64,12 +63,7 @@ spotless {
|
||||
|
||||
importOrderFile 'config/code-formatter/eclipse.importorder'
|
||||
eclipse().configFile 'config/code-formatter/eclipse.xml'
|
||||
if (JavaVersion.current() < JavaVersion.VERSION_16) {
|
||||
removeUnusedImports()
|
||||
} else {
|
||||
// google-format broken on java 16 (https://github.com/diffplug/spotless/issues/834)
|
||||
println('Warning! Unused imports remove is disabled for Java 16')
|
||||
}
|
||||
removeUnusedImports()
|
||||
|
||||
lineEndings(com.diffplug.spotless.LineEnding.UNIX)
|
||||
encoding("UTF-8")
|
||||
|
||||
@@ -66,6 +66,7 @@ publishing {
|
||||
}
|
||||
|
||||
signing {
|
||||
required { gradle.taskGraph.hasTask("publish") }
|
||||
sign publishing.publications.mavenJava
|
||||
}
|
||||
|
||||
|
||||
@@ -1 +1,11 @@
|
||||
org.gradle.warning.mode=all
|
||||
org.gradle.parallel=true
|
||||
|
||||
# Flags for google-java-format (optimize imports by spotless) for Java >= 16.
|
||||
# Java < 9 will ignore unsupported flags (thanks to -XX:+IgnoreUnrecognizedVMOptions)
|
||||
org.gradle.jvmargs=-XX:+IgnoreUnrecognizedVMOptions \
|
||||
--add-exports='jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED' \
|
||||
--add-exports='jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED' \
|
||||
--add-exports='jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED' \
|
||||
--add-exports='jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED' \
|
||||
--add-exports='jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED'
|
||||
|
||||
Vendored
BIN
Binary file not shown.
+2
-2
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionSha256Sum=b586e04868a22fd817c8971330fec37e298f3242eb85c374181b12d637f80302
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
|
||||
distributionSha256Sum=29e49b10984e585d8118b7d0bc452f944e386458df27371b49b4ac1dec4b7fda
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
@@ -11,13 +11,13 @@ dependencies {
|
||||
runtimeOnly(project(':jadx-plugins:jadx-smali-input'))
|
||||
|
||||
implementation 'com.beust:jcommander:1.82'
|
||||
implementation 'ch.qos.logback:logback-classic:1.2.10'
|
||||
implementation 'ch.qos.logback:logback-classic:1.2.11'
|
||||
}
|
||||
|
||||
application {
|
||||
applicationName = 'jadx'
|
||||
mainClass.set('jadx.cli.JadxCLI')
|
||||
applicationDefaultJvmArgs = ['-Xms128M', '-Xmx4g', '-XX:+UseG1GC']
|
||||
applicationDefaultJvmArgs = ['-Xms128M', '-XX:MaxRAMPercentage=70.0', '-XX:+UseG1GC']
|
||||
}
|
||||
|
||||
applicationDistribution.with {
|
||||
|
||||
@@ -8,6 +8,7 @@ import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -17,6 +18,12 @@ import com.beust.jcommander.ParameterException;
|
||||
import com.beust.jcommander.Parameterized;
|
||||
|
||||
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;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
public class JCommanderWrapper<T> {
|
||||
private final JCommander jc;
|
||||
@@ -45,12 +52,24 @@ public class JCommanderWrapper<T> {
|
||||
if (parameter.isAssigned()) {
|
||||
// copy assigned field value to obj
|
||||
Parameterized parameterized = parameter.getParameterized();
|
||||
Object val = parameterized.get(parameter.getObject());
|
||||
parameterized.set(obj, val);
|
||||
Object providedValue = parameterized.get(parameter.getObject());
|
||||
Object newValue = mergeValues(parameterized.getType(), providedValue, () -> parameterized.get(obj));
|
||||
parameterized.set(obj, newValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
private static Object mergeValues(Class<?> type, Object value, Supplier<Object> prevValueProvider) {
|
||||
if (type.isAssignableFrom(Map.class)) {
|
||||
// merge maps instead replacing whole map
|
||||
Map prevMap = (Map) prevValueProvider.get();
|
||||
return Utils.mergeMaps(prevMap, (Map) value); // value map will override keys in prevMap
|
||||
}
|
||||
// simple override
|
||||
return value;
|
||||
}
|
||||
|
||||
public void printUsage() {
|
||||
// print usage in not sorted fields order (by default its sorted by description)
|
||||
PrintStream out = System.out;
|
||||
@@ -70,40 +89,44 @@ public class JCommanderWrapper<T> {
|
||||
maxNamesLen = len;
|
||||
}
|
||||
}
|
||||
maxNamesLen += 3;
|
||||
|
||||
JadxCLIArgs args = (JadxCLIArgs) jc.getObjects().get(0);
|
||||
for (Field f : getFields(args.getClass())) {
|
||||
String name = f.getName();
|
||||
ParameterDescription p = paramsMap.get(name);
|
||||
if (p == null) {
|
||||
if (p == null || p.getParameter().hidden()) {
|
||||
continue;
|
||||
}
|
||||
StringBuilder opt = new StringBuilder();
|
||||
opt.append(" ").append(p.getNames());
|
||||
String description = p.getDescription();
|
||||
addSpaces(opt, maxNamesLen - opt.length() + 3);
|
||||
addSpaces(opt, maxNamesLen - opt.length());
|
||||
if (description.contains("\n")) {
|
||||
String[] lines = description.split("\n");
|
||||
opt.append("- ").append(lines[0]);
|
||||
for (int i = 1; i < lines.length; i++) {
|
||||
opt.append('\n');
|
||||
addSpaces(opt, maxNamesLen + 5);
|
||||
addSpaces(opt, maxNamesLen + 2);
|
||||
opt.append(lines[i]);
|
||||
}
|
||||
} else {
|
||||
opt.append("- ").append(description);
|
||||
}
|
||||
String defaultValue = getDefaultValue(args, f, opt);
|
||||
if (defaultValue != null) {
|
||||
if (defaultValue != null && !description.contains("(default)")) {
|
||||
opt.append(", default: ").append(defaultValue);
|
||||
}
|
||||
out.println(opt);
|
||||
}
|
||||
out.println(appendPluginOptions(maxNamesLen));
|
||||
out.println();
|
||||
out.println("Examples:");
|
||||
out.println(" jadx -d out classes.dex");
|
||||
out.println(" jadx --rename-flags \"none\" classes.dex");
|
||||
out.println(" jadx --rename-flags \"valid, printable\" classes.dex");
|
||||
out.println(" jadx --log-level ERROR app.apk");
|
||||
out.println(" jadx -Pdex-input.verify-checksum=no app.apk");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -145,4 +168,46 @@ public class JCommanderWrapper<T> {
|
||||
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.impl.NoOpCodeCache;
|
||||
import jadx.api.impl.SimpleCodeWriter;
|
||||
import jadx.cli.LogHelper.LogLevelEnum;
|
||||
import jadx.core.utils.exceptions.JadxArgsValidateException;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
|
||||
@@ -20,8 +21,8 @@ public class JadxCLI {
|
||||
} catch (JadxArgsValidateException e) {
|
||||
LOG.error("Incorrect arguments: {}", e.getMessage());
|
||||
result = 1;
|
||||
} catch (Exception e) {
|
||||
LOG.error("jadx error: {}", e.getMessage(), e);
|
||||
} catch (Throwable e) {
|
||||
LOG.error("Process error:", e);
|
||||
result = 1;
|
||||
} finally {
|
||||
FileUtils.deleteTempRootDir();
|
||||
@@ -32,23 +33,25 @@ public class JadxCLI {
|
||||
public static int execute(String[] args) {
|
||||
JadxCLIArgs jadxArgs = new JadxCLIArgs();
|
||||
if (jadxArgs.processArgs(args)) {
|
||||
return processAndSave(jadxArgs.toJadxArgs());
|
||||
return processAndSave(jadxArgs);
|
||||
}
|
||||
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.setCodeWriterProvider(SimpleCodeWriter::new);
|
||||
try (JadxDecompiler jadx = new JadxDecompiler(jadxArgs)) {
|
||||
jadx.load();
|
||||
if (LogHelper.getLogLevel() == LogHelper.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);
|
||||
});
|
||||
if (checkForErrors(jadx)) {
|
||||
return 1;
|
||||
}
|
||||
LogHelper.setLogLevelsForDecompileStage();
|
||||
if (!SingleClassMode.process(jadx, cliArgs)) {
|
||||
save(jadx);
|
||||
}
|
||||
int errorsCount = jadx.getErrorsCount();
|
||||
if (errorsCount != 0) {
|
||||
@@ -60,4 +63,30 @@ public class JadxCLI {
|
||||
}
|
||||
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,20 +2,25 @@ package jadx.cli;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import com.beust.jcommander.DynamicParameter;
|
||||
import com.beust.jcommander.IStringConverter;
|
||||
import com.beust.jcommander.Parameter;
|
||||
|
||||
import jadx.api.CommentsLevel;
|
||||
import jadx.api.DecompilationMode;
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.api.JadxArgs.RenameEnum;
|
||||
import jadx.api.JadxArgs.UseKotlinMethodsForVarNames;
|
||||
import jadx.api.JadxDecompiler;
|
||||
import jadx.api.args.DeobfuscationMapFileMode;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
|
||||
@@ -39,9 +44,12 @@ public class JadxCLIArgs {
|
||||
@Parameter(names = { "-s", "--no-src" }, description = "do not decompile source code")
|
||||
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;
|
||||
|
||||
@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'")
|
||||
protected String outputFormat = "java";
|
||||
|
||||
@@ -51,6 +59,17 @@ public class JadxCLIArgs {
|
||||
@Parameter(names = { "-j", "--threads-count" }, description = "processing 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)")
|
||||
protected boolean showInconsistentCode = false;
|
||||
|
||||
@@ -93,7 +112,18 @@ public class JadxCLIArgs {
|
||||
)
|
||||
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;
|
||||
|
||||
@Parameter(names = { "--deobf-use-sourcename" }, description = "use source file name as class name alias")
|
||||
@@ -130,7 +160,7 @@ public class JadxCLIArgs {
|
||||
@Parameter(names = { "--raw-cfg" }, description = "save methods control flow graph (use raw instructions)")
|
||||
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;
|
||||
|
||||
@Parameter(names = { "--use-dx" }, description = "use dx/d8 to convert java bytecode")
|
||||
@@ -162,6 +192,9 @@ public class JadxCLIArgs {
|
||||
@Parameter(names = { "-h", "--help" }, description = "print this help", help = true)
|
||||
protected boolean printHelp = false;
|
||||
|
||||
@DynamicParameter(names = "-P", description = "Plugin options", hidden = true)
|
||||
protected Map<String, String> pluginOptions = new HashMap<>();
|
||||
|
||||
public boolean processArgs(String[] args) {
|
||||
JCommanderWrapper<JadxCLIArgs> jcw = new JCommanderWrapper<>(this);
|
||||
return jcw.parse(args) && process(jcw);
|
||||
@@ -197,7 +230,6 @@ public class JadxCLIArgs {
|
||||
if (threadsCount <= 0) {
|
||||
throw new JadxException("Threads count must be positive, got: " + threadsCount);
|
||||
}
|
||||
LogHelper.setLogLevelFromArgs(this);
|
||||
} catch (JadxException e) {
|
||||
System.err.println("ERROR: " + e.getMessage());
|
||||
jcw.printUsage();
|
||||
@@ -215,18 +247,23 @@ public class JadxCLIArgs {
|
||||
args.setOutputFormat(JadxArgs.OutputFormatEnum.valueOf(outputFormat.toUpperCase()));
|
||||
args.setThreadsCount(threadsCount);
|
||||
args.setSkipSources(skipSources);
|
||||
if (singleClass != null) {
|
||||
args.setClassFilter(className -> singleClass.equals(className));
|
||||
}
|
||||
args.setSkipResources(skipResources);
|
||||
args.setFallbackMode(fallbackMode);
|
||||
if (fallbackMode) {
|
||||
args.setDecompilationMode(DecompilationMode.FALLBACK);
|
||||
} else {
|
||||
args.setDecompilationMode(decompilationMode);
|
||||
}
|
||||
args.setShowInconsistentCode(showInconsistentCode);
|
||||
args.setCfgOutput(cfgOutput);
|
||||
args.setRawCFGOutput(rawCfgOutput);
|
||||
args.setReplaceConsts(replaceConsts);
|
||||
args.setDeobfuscationOn(deobfuscationOn);
|
||||
args.setDeobfuscationMapFile(FileUtils.toFile(deobfuscationMapFile));
|
||||
args.setDeobfuscationForceSave(deobfuscationForceSave);
|
||||
if (deobfuscationForceSave) {
|
||||
args.setDeobfuscationMapFileMode(DeobfuscationMapFileMode.OVERWRITE);
|
||||
} else {
|
||||
args.setDeobfuscationMapFileMode(deobfuscationMapFileMode);
|
||||
}
|
||||
args.setDeobfuscationMinLength(deobfuscationMinLength);
|
||||
args.setDeobfuscationMaxLength(deobfuscationMaxLength);
|
||||
args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias);
|
||||
@@ -244,6 +281,7 @@ public class JadxCLIArgs {
|
||||
args.setFsCaseSensitive(fsCaseSensitive);
|
||||
args.setCommentsLevel(commentsLevel);
|
||||
args.setUseDxInput(useDx);
|
||||
args.setPluginOptions(pluginOptions);
|
||||
return args;
|
||||
}
|
||||
|
||||
@@ -263,6 +301,14 @@ public class JadxCLIArgs {
|
||||
return outDirRes;
|
||||
}
|
||||
|
||||
public String getSingleClass() {
|
||||
return singleClass;
|
||||
}
|
||||
|
||||
public String getSingleClassOutput() {
|
||||
return singleClassOutput;
|
||||
}
|
||||
|
||||
public boolean isSkipResources() {
|
||||
return skipResources;
|
||||
}
|
||||
@@ -283,6 +329,10 @@ public class JadxCLIArgs {
|
||||
return useDx;
|
||||
}
|
||||
|
||||
public DecompilationMode getDecompilationMode() {
|
||||
return decompilationMode;
|
||||
}
|
||||
|
||||
public boolean isShowInconsistentCode() {
|
||||
return showInconsistentCode;
|
||||
}
|
||||
@@ -323,6 +373,10 @@ public class JadxCLIArgs {
|
||||
return deobfuscationMapFile;
|
||||
}
|
||||
|
||||
public DeobfuscationMapFileMode getDeobfuscationMapFileMode() {
|
||||
return deobfuscationMapFileMode;
|
||||
}
|
||||
|
||||
public boolean isDeobfuscationForceSave() {
|
||||
return deobfuscationForceSave;
|
||||
}
|
||||
@@ -383,6 +437,14 @@ public class JadxCLIArgs {
|
||||
return commentsLevel;
|
||||
}
|
||||
|
||||
public LogHelper.LogLevelEnum getLogLevel() {
|
||||
return logLevel;
|
||||
}
|
||||
|
||||
public Map<String, String> getPluginOptions() {
|
||||
return pluginOptions;
|
||||
}
|
||||
|
||||
static class RenameConverter implements IStringConverter<Set<RenameEnum>> {
|
||||
private final String paramName;
|
||||
|
||||
@@ -438,6 +500,32 @@ public class JadxCLIArgs {
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
return Stream.of(values)
|
||||
.map(v -> v.name().replace('_', '-').toLowerCase(Locale.ROOT))
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package jadx.cli;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -32,33 +33,61 @@ public class LogHelper {
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable("For disable log level control")
|
||||
private static LogLevelEnum logLevelValue;
|
||||
|
||||
public static void setLogLevelFromArgs(JadxCLIArgs args) {
|
||||
if (isCustomLogConfig()) {
|
||||
return;
|
||||
}
|
||||
LogLevelEnum logLevel = args.logLevel;
|
||||
if (args.quiet) {
|
||||
logLevel = LogLevelEnum.QUIET;
|
||||
} else if (args.verbose) {
|
||||
logLevel = LogLevelEnum.DEBUG;
|
||||
}
|
||||
|
||||
applyLogLevel(logLevel);
|
||||
public static void initLogLevel(JadxCLIArgs args) {
|
||||
logLevelValue = getLogLevelFromArgs(args);
|
||||
}
|
||||
|
||||
public static void applyLogLevel(LogLevelEnum logLevel) {
|
||||
logLevelValue = logLevel;
|
||||
private static LogLevelEnum getLogLevelFromArgs(JadxCLIArgs args) {
|
||||
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);
|
||||
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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,14 @@
|
||||
package jadx.cli;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static jadx.core.utils.Utils.newConstStringMap;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
@@ -47,6 +52,40 @@ public class JadxCLIArgsTest {
|
||||
assertThat(override(args, "").isUseImports(), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPluginOptionsOverride() {
|
||||
// add key to empty base map
|
||||
checkPluginOptionsMerge(
|
||||
Collections.emptyMap(),
|
||||
"-Poption=otherValue",
|
||||
newConstStringMap("option", "otherValue"));
|
||||
|
||||
// override one key
|
||||
checkPluginOptionsMerge(
|
||||
newConstStringMap("option", "value"),
|
||||
"-Poption=otherValue",
|
||||
newConstStringMap("option", "otherValue"));
|
||||
|
||||
// merge different keys
|
||||
checkPluginOptionsMerge(
|
||||
Collections.singletonMap("option1", "value1"),
|
||||
"-Poption2=otherValue2",
|
||||
newConstStringMap("option1", "value1", "option2", "otherValue2"));
|
||||
|
||||
// merge and override
|
||||
checkPluginOptionsMerge(
|
||||
newConstStringMap("option1", "value1", "option2", "value2"),
|
||||
"-Poption2=otherValue2",
|
||||
newConstStringMap("option1", "value1", "option2", "otherValue2"));
|
||||
}
|
||||
|
||||
private void checkPluginOptionsMerge(Map<String, String> baseMap, String providedArgs, Map<String, String> expectedMap) {
|
||||
JadxCLIArgs args = new JadxCLIArgs();
|
||||
args.pluginOptions = baseMap;
|
||||
Map<String, String> resultMap = override(args, providedArgs).getPluginOptions();
|
||||
assertThat(resultMap, Matchers.equalTo(expectedMap));
|
||||
}
|
||||
|
||||
private JadxCLIArgs parse(String... args) {
|
||||
return parse(new JadxCLIArgs(), args);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ plugins {
|
||||
dependencies {
|
||||
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'
|
||||
constraints {
|
||||
// Force protobuf version to prevent Java-7 issue
|
||||
@@ -14,13 +14,14 @@ dependencies {
|
||||
|
||||
testImplementation 'org.apache.commons:commons-lang3:3.12.0'
|
||||
|
||||
testRuntimeOnly(project(':jadx-plugins:jadx-dex-input'))
|
||||
testImplementation(project(':jadx-plugins:jadx-dex-input'))
|
||||
testRuntimeOnly(project(':jadx-plugins:jadx-smali-input'))
|
||||
testRuntimeOnly(project(':jadx-plugins:jadx-java-convert'))
|
||||
testRuntimeOnly(project(':jadx-plugins:jadx-java-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 {
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
package jadx.api;
|
||||
|
||||
public final class CodePosition {
|
||||
|
||||
private final int line;
|
||||
private final int offset;
|
||||
private final int pos;
|
||||
|
||||
public CodePosition(int line, int offset, int pos) {
|
||||
this.line = line;
|
||||
this.offset = offset;
|
||||
this.pos = pos;
|
||||
}
|
||||
|
||||
public CodePosition(int line) {
|
||||
this(line, 0, -1);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public CodePosition(int line, int offset) {
|
||||
this(line, offset, -1);
|
||||
}
|
||||
|
||||
public int getPos() {
|
||||
return pos;
|
||||
}
|
||||
|
||||
public int getLine() {
|
||||
return line;
|
||||
}
|
||||
|
||||
public int getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
CodePosition that = (CodePosition) o;
|
||||
return line == that.line && offset == that.offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return line + 31 * offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(line);
|
||||
if (offset != 0) {
|
||||
sb.append(':').append(offset);
|
||||
}
|
||||
if (pos > 0) {
|
||||
sb.append('@').append(pos);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -1,13 +1,21 @@
|
||||
package jadx.api;
|
||||
|
||||
import java.io.Closeable;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public interface ICodeCache {
|
||||
public interface ICodeCache extends Closeable {
|
||||
|
||||
void add(String clsFullName, ICodeInfo codeInfo);
|
||||
|
||||
void remove(String clsFullName);
|
||||
|
||||
@Nullable
|
||||
@NotNull
|
||||
ICodeInfo get(String clsFullName);
|
||||
|
||||
@Nullable
|
||||
String getCode(String clsFullName);
|
||||
|
||||
boolean contains(String clsFullName);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package jadx.api;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import jadx.api.impl.SimpleCodeInfo;
|
||||
import jadx.api.metadata.ICodeMetadata;
|
||||
|
||||
public interface ICodeInfo {
|
||||
|
||||
@@ -10,7 +9,7 @@ public interface ICodeInfo {
|
||||
|
||||
String getCodeStr();
|
||||
|
||||
Map<Integer, Integer> getLineMapping();
|
||||
ICodeMetadata getCodeMetadata();
|
||||
|
||||
Map<CodePosition, Object> getAnnotations();
|
||||
boolean hasMetadata();
|
||||
}
|
||||
|
||||
@@ -2,7 +2,10 @@ package jadx.api;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import jadx.core.dex.attributes.ILineAttributeNode;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import jadx.api.metadata.ICodeAnnotation;
|
||||
import jadx.api.metadata.ICodeNodeRef;
|
||||
|
||||
public interface ICodeWriter {
|
||||
String NL = System.getProperty("line.separator");
|
||||
@@ -38,13 +41,21 @@ public interface ICodeWriter {
|
||||
|
||||
void setIndent(int indent);
|
||||
|
||||
/**
|
||||
* Return current line (only if metadata is supported)
|
||||
*/
|
||||
int getLine();
|
||||
|
||||
void attachDefinition(ILineAttributeNode obj);
|
||||
/**
|
||||
* Return start line position (only if metadata is supported)
|
||||
*/
|
||||
int getLineStartPos();
|
||||
|
||||
void attachAnnotation(Object obj);
|
||||
void attachDefinition(ICodeNodeRef obj);
|
||||
|
||||
void attachLineAnnotation(Object obj);
|
||||
void attachAnnotation(ICodeAnnotation obj);
|
||||
|
||||
void attachLineAnnotation(ICodeAnnotation obj);
|
||||
|
||||
void attachSourceLine(int sourceLine);
|
||||
|
||||
@@ -56,5 +67,6 @@ public interface ICodeWriter {
|
||||
|
||||
StringBuilder getRawBuf();
|
||||
|
||||
Map<CodePosition, Object> getRawAnnotations();
|
||||
@ApiStatus.Internal
|
||||
Map<Integer, ICodeAnnotation> getRawAnnotations();
|
||||
}
|
||||
|
||||
@@ -1,19 +1,28 @@
|
||||
package jadx.api;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.args.DeobfuscationMapFileMode;
|
||||
import jadx.api.data.ICodeData;
|
||||
import jadx.api.impl.AnnotatedCodeWriter;
|
||||
import jadx.api.impl.InMemoryCodeCache;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
|
||||
public class JadxArgs {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JadxArgs.class);
|
||||
|
||||
public static final int DEFAULT_THREADS_COUNT = Math.max(1, Runtime.getRuntime().availableProcessors() / 2);
|
||||
|
||||
@@ -35,7 +44,6 @@ public class JadxArgs {
|
||||
private boolean cfgOutput = false;
|
||||
private boolean rawCFGOutput = false;
|
||||
|
||||
private boolean fallbackMode = false;
|
||||
private boolean showInconsistentCode = false;
|
||||
|
||||
private boolean useImports = true;
|
||||
@@ -53,12 +61,18 @@ public class JadxArgs {
|
||||
*/
|
||||
private Predicate<String> classFilter = null;
|
||||
|
||||
/**
|
||||
* Save dependencies for classes accepted by {@code classFilter}
|
||||
*/
|
||||
private boolean includeDependencies = false;
|
||||
|
||||
private boolean deobfuscationOn = false;
|
||||
private boolean deobfuscationForceSave = false;
|
||||
private boolean useSourceNameAsClassAlias = false;
|
||||
private boolean parseKotlinMetadata = false;
|
||||
private File deobfuscationMapFile = null;
|
||||
|
||||
private DeobfuscationMapFileMode deobfuscationMapFileMode = DeobfuscationMapFileMode.READ;
|
||||
|
||||
private int deobfuscationMinLength = 0;
|
||||
private int deobfuscationMaxLength = Integer.MAX_VALUE;
|
||||
|
||||
@@ -81,6 +95,8 @@ public class JadxArgs {
|
||||
|
||||
private OutputFormatEnum outputFormat = OutputFormatEnum.JAVA;
|
||||
|
||||
private DecompilationMode decompilationMode = DecompilationMode.AUTO;
|
||||
|
||||
private ICodeData codeData;
|
||||
|
||||
private CommentsLevel commentsLevel = CommentsLevel.INFO;
|
||||
@@ -93,6 +109,13 @@ public class JadxArgs {
|
||||
|
||||
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() {
|
||||
// use default options
|
||||
}
|
||||
@@ -103,6 +126,19 @@ public class JadxArgs {
|
||||
setOutDirRes(new File(rootDir, DEFAULT_RES_DIR));
|
||||
}
|
||||
|
||||
public void close() {
|
||||
try {
|
||||
inputFiles.clear();
|
||||
if (codeCache != null) {
|
||||
codeCache.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to close JadxArgs", e);
|
||||
} finally {
|
||||
codeCache = null;
|
||||
}
|
||||
}
|
||||
|
||||
public List<File> getInputFiles() {
|
||||
return inputFiles;
|
||||
}
|
||||
@@ -164,11 +200,17 @@ public class JadxArgs {
|
||||
}
|
||||
|
||||
public boolean isFallbackMode() {
|
||||
return fallbackMode;
|
||||
return decompilationMode == DecompilationMode.FALLBACK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deprecated: use 'decompilation mode' property
|
||||
*/
|
||||
@Deprecated
|
||||
public void setFallbackMode(boolean fallbackMode) {
|
||||
this.fallbackMode = fallbackMode;
|
||||
if (fallbackMode) {
|
||||
this.decompilationMode = DecompilationMode.FALLBACK;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isShowInconsistentCode() {
|
||||
@@ -243,6 +285,14 @@ public class JadxArgs {
|
||||
this.skipSources = skipSources;
|
||||
}
|
||||
|
||||
public void setIncludeDependencies(boolean includeDependencies) {
|
||||
this.includeDependencies = includeDependencies;
|
||||
}
|
||||
|
||||
public boolean isIncludeDependencies() {
|
||||
return includeDependencies;
|
||||
}
|
||||
|
||||
public Predicate<String> getClassFilter() {
|
||||
return classFilter;
|
||||
}
|
||||
@@ -259,12 +309,24 @@ public class JadxArgs {
|
||||
this.deobfuscationOn = deobfuscationOn;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public boolean isDeobfuscationForceSave() {
|
||||
return deobfuscationForceSave;
|
||||
return deobfuscationMapFileMode == DeobfuscationMapFileMode.OVERWRITE;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
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() {
|
||||
@@ -399,6 +461,14 @@ public class JadxArgs {
|
||||
this.outputFormat = outputFormat;
|
||||
}
|
||||
|
||||
public DecompilationMode getDecompilationMode() {
|
||||
return decompilationMode;
|
||||
}
|
||||
|
||||
public void setDecompilationMode(DecompilationMode decompilationMode) {
|
||||
this.decompilationMode = decompilationMode;
|
||||
}
|
||||
|
||||
public ICodeCache getCodeCache() {
|
||||
return codeCache;
|
||||
}
|
||||
@@ -447,6 +517,37 @@ public class JadxArgs {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash of all options that can change result code
|
||||
*/
|
||||
public String makeCodeArgsHash() {
|
||||
String argStr = "args:" + decompilationMode + useImports + showInconsistentCode
|
||||
+ inlineAnonymousClasses + inlineMethods
|
||||
+ deobfuscationOn + deobfuscationMinLength + deobfuscationMaxLength
|
||||
+ parseKotlinMetadata + useKotlinMethodsForVarNames
|
||||
+ insertDebugLines + extractFinally
|
||||
+ debugInfo + useSourceNameAsClassAlias + escapeUnicode + replaceConsts
|
||||
+ respectBytecodeAccModifiers + fsCaseSensitive + renameFlags
|
||||
+ commentsLevel + useDxInput + pluginOptions;
|
||||
return FileUtils.md5Sum(argStr.getBytes(StandardCharsets.US_ASCII));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "JadxArgs{" + "inputFiles=" + inputFiles
|
||||
@@ -454,19 +555,20 @@ public class JadxArgs {
|
||||
+ ", outDirSrc=" + outDirSrc
|
||||
+ ", outDirRes=" + outDirRes
|
||||
+ ", threadsCount=" + threadsCount
|
||||
+ ", cfgOutput=" + cfgOutput
|
||||
+ ", rawCFGOutput=" + rawCFGOutput
|
||||
+ ", fallbackMode=" + fallbackMode
|
||||
+ ", decompilationMode=" + decompilationMode
|
||||
+ ", showInconsistentCode=" + showInconsistentCode
|
||||
+ ", useImports=" + useImports
|
||||
+ ", skipResources=" + skipResources
|
||||
+ ", skipSources=" + skipSources
|
||||
+ ", includeDependencies=" + includeDependencies
|
||||
+ ", deobfuscationOn=" + deobfuscationOn
|
||||
+ ", deobfuscationMapFile=" + deobfuscationMapFile
|
||||
+ ", deobfuscationForceSave=" + deobfuscationForceSave
|
||||
+ ", deobfuscationMapFileMode=" + deobfuscationMapFileMode
|
||||
+ ", useSourceNameAsClassAlias=" + useSourceNameAsClassAlias
|
||||
+ ", parseKotlinMetadata=" + parseKotlinMetadata
|
||||
+ ", useKotlinMethodsForVarNames=" + useKotlinMethodsForVarNames
|
||||
+ ", insertDebugLines=" + insertDebugLines
|
||||
+ ", extractFinally=" + extractFinally
|
||||
+ ", deobfuscationMinLength=" + deobfuscationMinLength
|
||||
+ ", deobfuscationMaxLength=" + deobfuscationMaxLength
|
||||
+ ", escapeUnicode=" + escapeUnicode
|
||||
@@ -480,6 +582,9 @@ public class JadxArgs {
|
||||
+ ", codeCache=" + codeCache
|
||||
+ ", codeWriter=" + codeWriterProvider.apply(this).getClass().getSimpleName()
|
||||
+ ", useDxInput=" + useDxInput
|
||||
+ ", pluginOptions=" + pluginOptions
|
||||
+ ", cfgOutput=" + cfgOutput
|
||||
+ ", rawCFGOutput=" + rawCFGOutput
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,8 +13,9 @@ public class JadxArgsValidator {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JadxArgsValidator.class);
|
||||
|
||||
public static void validate(JadxArgs args) {
|
||||
checkInputFiles(args);
|
||||
public static void validate(JadxDecompiler jadx) {
|
||||
JadxArgs args = jadx.getArgs();
|
||||
checkInputFiles(jadx, args);
|
||||
validateOutDirs(args);
|
||||
|
||||
if (LOG.isDebugEnabled()) {
|
||||
@@ -22,9 +23,9 @@ public class JadxArgsValidator {
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkInputFiles(JadxArgs args) {
|
||||
private static void checkInputFiles(JadxDecompiler jadx, JadxArgs args) {
|
||||
List<File> inputFiles = args.getInputFiles();
|
||||
if (inputFiles.isEmpty()) {
|
||||
if (inputFiles.isEmpty() && jadx.getCustomLoads().isEmpty()) {
|
||||
throw new JadxArgsValidateException("Please specify input file");
|
||||
}
|
||||
for (File inputFile : inputFiles) {
|
||||
@@ -66,19 +67,22 @@ public class JadxArgsValidator {
|
||||
|
||||
@NotNull
|
||||
private static File makeDirFromInput(JadxArgs args) {
|
||||
File outDir;
|
||||
String outDirName;
|
||||
File file = args.getInputFiles().get(0);
|
||||
String name = file.getName();
|
||||
int pos = name.lastIndexOf('.');
|
||||
if (pos != -1) {
|
||||
outDirName = name.substring(0, pos);
|
||||
List<File> inputFiles = args.getInputFiles();
|
||||
if (inputFiles.isEmpty()) {
|
||||
outDirName = JadxArgs.DEFAULT_OUT_DIR;
|
||||
} else {
|
||||
outDirName = name + '-' + JadxArgs.DEFAULT_OUT_DIR;
|
||||
File file = inputFiles.get(0);
|
||||
String name = file.getName();
|
||||
int pos = name.lastIndexOf('.');
|
||||
if (pos != -1) {
|
||||
outDirName = name.substring(0, pos);
|
||||
} else {
|
||||
outDirName = name + '-' + JadxArgs.DEFAULT_OUT_DIR;
|
||||
}
|
||||
}
|
||||
LOG.info("output directory: {}", outDirName);
|
||||
outDir = new File(outDirName);
|
||||
return outDir;
|
||||
return new File(outDirName);
|
||||
}
|
||||
|
||||
private static void checkFile(File file) {
|
||||
|
||||
@@ -25,14 +25,20 @@ import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.data.annotations.VarRef;
|
||||
import jadx.api.metadata.ICodeAnnotation;
|
||||
import jadx.api.metadata.ICodeNodeRef;
|
||||
import jadx.api.metadata.annotations.NodeDeclareRef;
|
||||
import jadx.api.metadata.annotations.VarNode;
|
||||
import jadx.api.metadata.annotations.VarRef;
|
||||
import jadx.api.plugins.JadxPlugin;
|
||||
import jadx.api.plugins.JadxPluginManager;
|
||||
import jadx.api.plugins.input.JadxInputPlugin;
|
||||
import jadx.api.plugins.input.data.ILoadResult;
|
||||
import jadx.api.plugins.options.JadxPluginOptions;
|
||||
import jadx.core.Jadx;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.nodes.LineAttrNode;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.InlinedAttr;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
@@ -93,7 +99,9 @@ public final class JadxDecompiler implements Closeable {
|
||||
private final Map<MethodNode, JavaMethod> methodsMap = new ConcurrentHashMap<>();
|
||||
private final Map<FieldNode, JavaField> fieldsMap = new ConcurrentHashMap<>();
|
||||
|
||||
private final IDecompileScheduler decompileScheduler = new DecompilerScheduler(this);
|
||||
private final IDecompileScheduler decompileScheduler = new DecompilerScheduler();
|
||||
|
||||
private final List<ILoadResult> customLoads = new ArrayList<>();
|
||||
|
||||
public JadxDecompiler() {
|
||||
this(new JadxArgs());
|
||||
@@ -105,7 +113,7 @@ public final class JadxDecompiler implements Closeable {
|
||||
|
||||
public void load() {
|
||||
reset();
|
||||
JadxArgsValidator.validate(args);
|
||||
JadxArgsValidator.validate(this);
|
||||
LOG.info("loading ...");
|
||||
loadPlugins(args);
|
||||
loadInputFiles();
|
||||
@@ -122,12 +130,25 @@ public final class JadxDecompiler implements Closeable {
|
||||
loadedInputs.clear();
|
||||
List<Path> inputPaths = Utils.collectionMap(args.getInputFiles(), File::toPath);
|
||||
List<Path> inputFiles = FileUtils.expandDirs(inputPaths);
|
||||
long start = System.currentTimeMillis();
|
||||
for (JadxInputPlugin inputPlugin : pluginManager.getInputPlugins()) {
|
||||
ILoadResult loadResult = inputPlugin.loadFiles(inputFiles);
|
||||
if (loadResult != null && !loadResult.isEmpty()) {
|
||||
loadedInputs.add(loadResult);
|
||||
}
|
||||
}
|
||||
loadedInputs.addAll(customLoads);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Loaded using {} inputs plugin in {} ms", loadedInputs.size(), System.currentTimeMillis() - start);
|
||||
}
|
||||
}
|
||||
|
||||
public void addCustomLoad(ILoadResult customLoad) {
|
||||
customLoads.add(customLoad);
|
||||
}
|
||||
|
||||
public List<ILoadResult> getCustomLoads() {
|
||||
return customLoads;
|
||||
}
|
||||
|
||||
private void reset() {
|
||||
@@ -140,8 +161,13 @@ public final class JadxDecompiler implements Closeable {
|
||||
classesMap.clear();
|
||||
methodsMap.clear();
|
||||
fieldsMap.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
reset();
|
||||
closeInputs();
|
||||
args.close();
|
||||
}
|
||||
|
||||
private void closeInputs() {
|
||||
@@ -155,11 +181,6 @@ public final class JadxDecompiler implements Closeable {
|
||||
loadedInputs.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
reset();
|
||||
}
|
||||
|
||||
private void loadPlugins(JadxArgs args) {
|
||||
pluginManager.providesSuggestion("java-input", args.isUseDxInput() ? "java-convert" : "java-input");
|
||||
pluginManager.load();
|
||||
@@ -167,8 +188,21 @@ public final class JadxDecompiler implements Closeable {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public void registerPlugin(JadxPlugin plugin) {
|
||||
pluginManager.register(plugin);
|
||||
}
|
||||
@@ -210,6 +244,7 @@ public final class JadxDecompiler implements Closeable {
|
||||
save(false, true);
|
||||
}
|
||||
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
private void save(boolean saveSources, boolean saveResources) {
|
||||
ExecutorService ex = getSaveExecutor(saveSources, saveResources);
|
||||
ex.shutdown();
|
||||
@@ -282,6 +317,9 @@ public final class JadxDecompiler implements Closeable {
|
||||
}
|
||||
|
||||
private void appendResourcesSaveTasks(List<Runnable> tasks, File outDir) {
|
||||
if (args.isSkipFilesSave()) {
|
||||
return;
|
||||
}
|
||||
Set<String> inputFileNames = args.getInputFiles().stream().map(File::getAbsolutePath).collect(Collectors.toSet());
|
||||
for (ResourceFile resourceFile : getResources()) {
|
||||
if (resourceFile.getType() != ResourceType.ARSC
|
||||
@@ -298,15 +336,25 @@ public final class JadxDecompiler implements Closeable {
|
||||
List<JavaClass> classes = getClasses();
|
||||
List<JavaClass> processQueue = new ArrayList<>(classes.size());
|
||||
for (JavaClass cls : classes) {
|
||||
if (cls.getClassNode().contains(AFlag.DONT_GENERATE)) {
|
||||
ClassNode clsNode = cls.getClassNode();
|
||||
if (clsNode.contains(AFlag.DONT_GENERATE)) {
|
||||
continue;
|
||||
}
|
||||
if (classFilter != null && !classFilter.test(cls.getFullName())) {
|
||||
if (classFilter != null && !classFilter.test(clsNode.getClassInfo().getFullName())) {
|
||||
if (!args.isIncludeDependencies()) {
|
||||
clsNode.add(AFlag.DONT_GENERATE);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
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(() -> {
|
||||
for (JavaClass cls : decompileBatch) {
|
||||
try {
|
||||
@@ -325,14 +373,14 @@ public final class JadxDecompiler implements Closeable {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
if (classes == null) {
|
||||
List<ClassNode> classNodeList = root.getClasses(false);
|
||||
List<ClassNode> classNodeList = root.getClasses();
|
||||
List<JavaClass> clsList = new ArrayList<>(classNodeList.size());
|
||||
classesMap.clear();
|
||||
for (ClassNode classNode : classNodeList) {
|
||||
if (!classNode.contains(AFlag.DONT_GENERATE)) {
|
||||
JavaClass javaClass = new JavaClass(classNode, this);
|
||||
clsList.add(javaClass);
|
||||
classesMap.put(classNode, javaClass);
|
||||
if (classNode.contains(AFlag.DONT_GENERATE)) {
|
||||
continue;
|
||||
}
|
||||
if (!classNode.getClassInfo().isInner()) {
|
||||
clsList.add(convertClassNode(classNode));
|
||||
}
|
||||
}
|
||||
classes = Collections.unmodifiableList(clsList);
|
||||
@@ -340,6 +388,10 @@ public final class JadxDecompiler implements Closeable {
|
||||
return classes;
|
||||
}
|
||||
|
||||
public List<JavaClass> getClassesWithInners() {
|
||||
return Utils.collectionMap(root.getClasses(), this::convertClassNode);
|
||||
}
|
||||
|
||||
public List<ResourceFile> getResources() {
|
||||
if (resources == null) {
|
||||
if (root == null) {
|
||||
@@ -397,6 +449,7 @@ public final class JadxDecompiler implements Closeable {
|
||||
/**
|
||||
* Internal API. Not Stable!
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
public RootNode getRoot() {
|
||||
return root;
|
||||
}
|
||||
@@ -415,23 +468,10 @@ public final class JadxDecompiler implements Closeable {
|
||||
return protoXmlParser;
|
||||
}
|
||||
|
||||
private void loadJavaClass(JavaClass javaClass) {
|
||||
javaClass.getMethods().forEach(mth -> methodsMap.put(mth.getMethodNode(), mth));
|
||||
javaClass.getFields().forEach(fld -> fieldsMap.put(fld.getFieldNode(), fld));
|
||||
|
||||
for (JavaClass innerCls : javaClass.getInnerClasses()) {
|
||||
classesMap.put(innerCls.getClassNode(), innerCls);
|
||||
loadJavaClass(innerCls);
|
||||
}
|
||||
for (JavaClass inlinedCls : javaClass.getInlinedClasses()) {
|
||||
classesMap.put(inlinedCls.getClassNode(), inlinedCls);
|
||||
loadJavaClass(inlinedCls);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get JavaClass by ClassNode without loading and decompilation
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
JavaClass convertClassNode(ClassNode cls) {
|
||||
return classesMap.compute(cls, (node, prevJavaCls) -> {
|
||||
if (prevJavaCls != null && prevJavaCls.getClassNode() == cls) {
|
||||
@@ -445,82 +485,34 @@ public final class JadxDecompiler implements Closeable {
|
||||
});
|
||||
}
|
||||
|
||||
@Nullable("For not generated classes")
|
||||
@ApiStatus.Internal
|
||||
public JavaClass getJavaClassByNode(ClassNode cls) {
|
||||
JavaClass javaClass = classesMap.get(cls);
|
||||
if (javaClass != null && javaClass.getClassNode() == cls) {
|
||||
return javaClass;
|
||||
}
|
||||
// load parent class if inner
|
||||
ClassNode parentClass = cls.getTopParentClass();
|
||||
if (parentClass.contains(AFlag.DONT_GENERATE)) {
|
||||
return null;
|
||||
}
|
||||
if (parentClass != cls) {
|
||||
JavaClass parentJavaClass = classesMap.get(parentClass);
|
||||
if (parentJavaClass == null) {
|
||||
getClasses();
|
||||
parentJavaClass = classesMap.get(parentClass);
|
||||
}
|
||||
loadJavaClass(parentJavaClass);
|
||||
javaClass = classesMap.get(cls);
|
||||
if (javaClass != null) {
|
||||
return javaClass;
|
||||
}
|
||||
}
|
||||
// class or parent classes can be excluded from generation
|
||||
if (cls.hasNotGeneratedParent()) {
|
||||
return null;
|
||||
}
|
||||
throw new JadxRuntimeException("JavaClass not found by ClassNode: " + cls);
|
||||
JavaField convertFieldNode(FieldNode field) {
|
||||
return fieldsMap.computeIfAbsent(field, fldNode -> {
|
||||
JavaClass parentCls = convertClassNode(fldNode.getParentClass());
|
||||
return new JavaField(parentCls, fldNode);
|
||||
});
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private JavaMethod getJavaMethodByNode(MethodNode mth) {
|
||||
JavaMethod javaMethod = methodsMap.get(mth);
|
||||
if (javaMethod != null && javaMethod.getMethodNode() == mth) {
|
||||
return javaMethod;
|
||||
}
|
||||
if (mth.contains(AFlag.DONT_GENERATE)) {
|
||||
return null;
|
||||
}
|
||||
// parent class not loaded yet
|
||||
JavaClass javaClass = getJavaClassByNode(mth.getParentClass().getTopParentClass());
|
||||
if (javaClass == null) {
|
||||
return null;
|
||||
}
|
||||
loadJavaClass(javaClass);
|
||||
javaMethod = methodsMap.get(mth);
|
||||
if (javaMethod != null) {
|
||||
return javaMethod;
|
||||
}
|
||||
if (mth.getParentClass().hasNotGeneratedParent()) {
|
||||
return null;
|
||||
}
|
||||
throw new JadxRuntimeException("JavaMethod not found by MethodNode: " + mth);
|
||||
@ApiStatus.Internal
|
||||
JavaMethod convertMethodNode(MethodNode method) {
|
||||
return methodsMap.computeIfAbsent(method, mthNode -> {
|
||||
ClassNode codeCls = getCodeParentClass(mthNode.getParentClass());
|
||||
return new JavaMethod(convertClassNode(codeCls), mthNode);
|
||||
});
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private JavaField getJavaFieldByNode(FieldNode fld) {
|
||||
JavaField javaField = fieldsMap.get(fld);
|
||||
if (javaField != null && javaField.getFieldNode() == fld) {
|
||||
return javaField;
|
||||
private static ClassNode getCodeParentClass(ClassNode cls) {
|
||||
ClassNode codeCls;
|
||||
InlinedAttr inlinedAttr = cls.get(AType.INLINED);
|
||||
if (inlinedAttr != null) {
|
||||
codeCls = inlinedAttr.getInlineCls().getTopParentClass();
|
||||
} else {
|
||||
codeCls = cls.getTopParentClass();
|
||||
}
|
||||
// parent class not loaded yet
|
||||
JavaClass javaClass = getJavaClassByNode(fld.getParentClass().getTopParentClass());
|
||||
if (javaClass == null) {
|
||||
return null;
|
||||
if (codeCls == cls) {
|
||||
return codeCls;
|
||||
}
|
||||
loadJavaClass(javaClass);
|
||||
javaField = fieldsMap.get(fld);
|
||||
if (javaField != null) {
|
||||
return javaField;
|
||||
}
|
||||
if (fld.getParentClass().hasNotGeneratedParent()) {
|
||||
return null;
|
||||
}
|
||||
throw new JadxRuntimeException("JavaField not found by FieldNode: " + fld);
|
||||
return getCodeParentClass(codeCls);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -528,7 +520,7 @@ public final class JadxDecompiler implements Closeable {
|
||||
return getRoot().getClasses().stream()
|
||||
.filter(cls -> cls.getClassInfo().getFullName().equals(fullName))
|
||||
.findFirst()
|
||||
.map(this::getJavaClassByNode)
|
||||
.map(this::convertClassNode)
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
@@ -549,9 +541,9 @@ public final class JadxDecompiler implements Closeable {
|
||||
.orElse(null);
|
||||
if (node != null) {
|
||||
if (node.contains(AFlag.DONT_GENERATE)) {
|
||||
return getJavaClassByNode(node.getTopParentClass());
|
||||
return convertClassNode(node.getTopParentClass());
|
||||
} else {
|
||||
return getJavaClassByNode(node);
|
||||
return convertClassNode(node);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@@ -562,86 +554,92 @@ public final class JadxDecompiler implements Closeable {
|
||||
return getRoot().getClasses().stream()
|
||||
.filter(cls -> cls.getClassInfo().getAliasFullName().equals(fullName))
|
||||
.findFirst()
|
||||
.map(this::getJavaClassByNode)
|
||||
.map(this::convertClassNode)
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
JavaNode convertNode(Object obj) {
|
||||
if (obj instanceof VarRef) {
|
||||
VarRef varRef = (VarRef) obj;
|
||||
MethodNode mthNode = varRef.getMth();
|
||||
JavaMethod mth = getJavaMethodByNode(mthNode);
|
||||
if (mth == null) {
|
||||
public JavaNode getJavaNodeByRef(ICodeNodeRef ann) {
|
||||
return getJavaNodeByCodeAnnotation(null, ann);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public JavaNode getJavaNodeByCodeAnnotation(@Nullable ICodeInfo codeInfo, @Nullable ICodeAnnotation ann) {
|
||||
if (ann == null) {
|
||||
return null;
|
||||
}
|
||||
switch (ann.getAnnType()) {
|
||||
case CLASS:
|
||||
return convertClassNode((ClassNode) ann);
|
||||
case METHOD:
|
||||
return convertMethodNode((MethodNode) ann);
|
||||
case FIELD:
|
||||
return convertFieldNode((FieldNode) ann);
|
||||
case DECLARATION:
|
||||
return getJavaNodeByCodeAnnotation(codeInfo, ((NodeDeclareRef) ann).getNode());
|
||||
case VAR:
|
||||
return resolveVarNode((VarNode) ann);
|
||||
case VAR_REF:
|
||||
return resolveVarRef(codeInfo, (VarRef) ann);
|
||||
case OFFSET:
|
||||
// offset annotation don't have java node object
|
||||
return null;
|
||||
default:
|
||||
throw new JadxRuntimeException("Unknown annotation type: " + ann.getAnnType() + ", class: " + ann.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private JavaVariable resolveVarNode(VarNode varNode) {
|
||||
MethodNode mthNode = varNode.getMth();
|
||||
JavaMethod mth = convertMethodNode(mthNode);
|
||||
if (mth == null) {
|
||||
return null;
|
||||
}
|
||||
return new JavaVariable(mth, varNode);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private JavaVariable resolveVarRef(ICodeInfo codeInfo, VarRef varRef) {
|
||||
if (codeInfo == null) {
|
||||
throw new JadxRuntimeException("Missing code info for resolve VarRef: " + varRef);
|
||||
}
|
||||
ICodeAnnotation varNodeAnn = codeInfo.getCodeMetadata().getAt(varRef.getRefPos());
|
||||
if (varNodeAnn != null && varNodeAnn.getAnnType() == ICodeAnnotation.AnnType.DECLARATION) {
|
||||
ICodeNodeRef nodeRef = ((NodeDeclareRef) varNodeAnn).getNode();
|
||||
if (nodeRef.getAnnType() == ICodeAnnotation.AnnType.VAR) {
|
||||
return resolveVarNode((VarNode) nodeRef);
|
||||
}
|
||||
return new JavaVariable(mth, varRef);
|
||||
}
|
||||
if (!(obj instanceof LineAttrNode)) {
|
||||
return null;
|
||||
}
|
||||
LineAttrNode node = (LineAttrNode) obj;
|
||||
if (node.contains(AFlag.DONT_GENERATE)) {
|
||||
return null;
|
||||
}
|
||||
if (obj instanceof ClassNode) {
|
||||
return convertClassNode((ClassNode) obj);
|
||||
}
|
||||
if (obj instanceof MethodNode) {
|
||||
return getJavaMethodByNode(((MethodNode) obj));
|
||||
}
|
||||
if (obj instanceof FieldNode) {
|
||||
return getJavaFieldByNode((FieldNode) obj);
|
||||
}
|
||||
throw new JadxRuntimeException("Unexpected node type: " + obj);
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO: make interface for all nodes in code annotations and add common method instead this
|
||||
Object getInternalNode(JavaNode javaNode) {
|
||||
if (javaNode instanceof JavaClass) {
|
||||
return ((JavaClass) javaNode).getClassNode();
|
||||
}
|
||||
if (javaNode instanceof JavaMethod) {
|
||||
return ((JavaMethod) javaNode).getMethodNode();
|
||||
}
|
||||
if (javaNode instanceof JavaField) {
|
||||
return ((JavaField) javaNode).getFieldNode();
|
||||
}
|
||||
if (javaNode instanceof JavaVariable) {
|
||||
return ((JavaVariable) javaNode).getVarRef();
|
||||
}
|
||||
throw new JadxRuntimeException("Unexpected node type: " + javaNode);
|
||||
}
|
||||
|
||||
List<JavaNode> convertNodes(Collection<?> nodesList) {
|
||||
List<JavaNode> convertNodes(Collection<? extends ICodeNodeRef> nodesList) {
|
||||
return nodesList.stream()
|
||||
.map(this::convertNode)
|
||||
.map(this::getJavaNodeByRef)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public JavaNode getJavaNodeAtPosition(ICodeInfo codeInfo, int line, int offset) {
|
||||
Map<CodePosition, Object> map = codeInfo.getAnnotations();
|
||||
if (map.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
Object obj = map.get(new CodePosition(line, offset));
|
||||
if (obj == null) {
|
||||
return null;
|
||||
}
|
||||
return convertNode(obj);
|
||||
public JavaNode getJavaNodeAtPosition(ICodeInfo codeInfo, int pos) {
|
||||
ICodeAnnotation ann = codeInfo.getCodeMetadata().getAt(pos);
|
||||
return getJavaNodeByCodeAnnotation(codeInfo, ann);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public CodePosition getDefinitionPosition(JavaNode javaNode) {
|
||||
JavaClass jCls = javaNode.getTopParentClass();
|
||||
jCls.decompile();
|
||||
int defLine = javaNode.getDecompiledLine();
|
||||
if (defLine == 0) {
|
||||
public JavaNode getClosestJavaNode(ICodeInfo codeInfo, int pos) {
|
||||
ICodeAnnotation ann = codeInfo.getCodeMetadata().getClosestUp(pos);
|
||||
return getJavaNodeByCodeAnnotation(codeInfo, ann);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public JavaNode getEnclosingNode(ICodeInfo codeInfo, int pos) {
|
||||
ICodeNodeRef obj = codeInfo.getCodeMetadata().getNodeAt(pos);
|
||||
if (obj == null) {
|
||||
return null;
|
||||
}
|
||||
return new CodePosition(defLine, 0, javaNode.getDefPos());
|
||||
return getJavaNodeByRef(obj);
|
||||
}
|
||||
|
||||
public void reloadCodeData() {
|
||||
|
||||
@@ -8,15 +8,24 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.metadata.ICodeAnnotation;
|
||||
import jadx.api.metadata.ICodeNodeRef;
|
||||
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.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.utils.ListUtils;
|
||||
|
||||
public final class JavaClass implements JavaNode {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JavaClass.class);
|
||||
|
||||
private final JadxDecompiler decompiler;
|
||||
private final ClassNode cls;
|
||||
@@ -44,24 +53,21 @@ public final class JavaClass implements JavaNode {
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
ICodeInfo code = getCodeInfo();
|
||||
if (code == null) {
|
||||
return "";
|
||||
}
|
||||
return code.getCodeStr();
|
||||
return getCodeInfo().getCodeStr();
|
||||
}
|
||||
|
||||
public ICodeInfo getCodeInfo() {
|
||||
public @NotNull ICodeInfo getCodeInfo() {
|
||||
load();
|
||||
return cls.decompile();
|
||||
}
|
||||
|
||||
public void decompile() {
|
||||
cls.decompile();
|
||||
load();
|
||||
}
|
||||
|
||||
public synchronized void reload() {
|
||||
public synchronized ICodeInfo reload() {
|
||||
listsLoaded = false;
|
||||
cls.reloadCode();
|
||||
return cls.reloadCode();
|
||||
}
|
||||
|
||||
public void unload() {
|
||||
@@ -69,10 +75,26 @@ public final class JavaClass implements JavaNode {
|
||||
cls.unloadCode();
|
||||
}
|
||||
|
||||
public boolean isNoCode() {
|
||||
return cls.contains(AFlag.DONT_GENERATE);
|
||||
}
|
||||
|
||||
public boolean isInner() {
|
||||
return cls.isInner();
|
||||
}
|
||||
|
||||
public synchronized String getSmali() {
|
||||
return cls.getDisassembledCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOwnCodeAnnotation(ICodeAnnotation ann) {
|
||||
if (ann.getAnnType() == ICodeAnnotation.AnnType.CLASS) {
|
||||
return ann.equals(cls);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal API. Not Stable!
|
||||
*/
|
||||
@@ -81,13 +103,21 @@ public final class JavaClass implements JavaNode {
|
||||
return cls;
|
||||
}
|
||||
|
||||
private synchronized void loadLists() {
|
||||
/**
|
||||
* Decompile class and loads internal lists of fields, methods, etc.
|
||||
* Do nothing if already loaded.
|
||||
*/
|
||||
@Nullable
|
||||
private synchronized void load() {
|
||||
if (listsLoaded) {
|
||||
return;
|
||||
}
|
||||
listsLoaded = true;
|
||||
decompile();
|
||||
JadxDecompiler rootDecompiler = getRootDecompiler();
|
||||
ICodeCache codeCache = rootDecompiler.getArgs().getCodeCache();
|
||||
if (!codeCache.contains(cls.getRawName())) {
|
||||
cls.decompile();
|
||||
}
|
||||
|
||||
int inClsCount = cls.getInnerClasses().size();
|
||||
if (inClsCount != 0) {
|
||||
@@ -95,7 +125,7 @@ public final class JavaClass implements JavaNode {
|
||||
for (ClassNode inner : cls.getInnerClasses()) {
|
||||
if (!inner.contains(AFlag.DONT_GENERATE)) {
|
||||
JavaClass javaClass = rootDecompiler.convertClassNode(inner);
|
||||
javaClass.loadLists();
|
||||
javaClass.load();
|
||||
list.add(javaClass);
|
||||
}
|
||||
}
|
||||
@@ -106,7 +136,7 @@ public final class JavaClass implements JavaNode {
|
||||
List<JavaClass> list = new ArrayList<>(inlinedClsCount);
|
||||
for (ClassNode inner : cls.getInlinedClasses()) {
|
||||
JavaClass javaClass = rootDecompiler.convertClassNode(inner);
|
||||
javaClass.loadLists();
|
||||
javaClass.load();
|
||||
list.add(javaClass);
|
||||
}
|
||||
this.inlinedClasses = Collections.unmodifiableList(list);
|
||||
@@ -117,8 +147,7 @@ public final class JavaClass implements JavaNode {
|
||||
List<JavaField> flds = new ArrayList<>(fieldsCount);
|
||||
for (FieldNode f : cls.getFields()) {
|
||||
if (!f.contains(AFlag.DONT_GENERATE)) {
|
||||
JavaField javaField = new JavaField(this, f);
|
||||
flds.add(javaField);
|
||||
flds.add(rootDecompiler.convertFieldNode(f));
|
||||
}
|
||||
}
|
||||
this.fields = Collections.unmodifiableList(flds);
|
||||
@@ -129,8 +158,7 @@ public final class JavaClass implements JavaNode {
|
||||
List<JavaMethod> mths = new ArrayList<>(methodsCount);
|
||||
for (MethodNode m : cls.getMethods()) {
|
||||
if (!m.contains(AFlag.DONT_GENERATE)) {
|
||||
JavaMethod javaMethod = new JavaMethod(this, m);
|
||||
mths.add(javaMethod);
|
||||
mths.add(rootDecompiler.convertMethodNode(m));
|
||||
}
|
||||
}
|
||||
mths.sort(Comparator.comparing(JavaMethod::getName));
|
||||
@@ -138,56 +166,47 @@ public final class JavaClass implements JavaNode {
|
||||
}
|
||||
}
|
||||
|
||||
protected JadxDecompiler getRootDecompiler() {
|
||||
JadxDecompiler getRootDecompiler() {
|
||||
if (parent != null) {
|
||||
return parent.getRootDecompiler();
|
||||
}
|
||||
return decompiler;
|
||||
}
|
||||
|
||||
public Map<CodePosition, Object> getCodeAnnotations() {
|
||||
ICodeInfo code = getCodeInfo();
|
||||
if (code == null) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
return code.getAnnotations();
|
||||
public ICodeAnnotation getAnnotationAt(int pos) {
|
||||
return getCodeInfo().getCodeMetadata().getAt(pos);
|
||||
}
|
||||
|
||||
public Object getAnnotationAt(CodePosition pos) {
|
||||
return getCodeAnnotations().get(pos);
|
||||
}
|
||||
|
||||
public Map<CodePosition, JavaNode> getUsageMap() {
|
||||
Map<CodePosition, Object> map = getCodeAnnotations();
|
||||
public Map<Integer, JavaNode> getUsageMap() {
|
||||
Map<Integer, ICodeAnnotation> map = getCodeInfo().getCodeMetadata().getAsMap();
|
||||
if (map.isEmpty() || decompiler == null) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
Map<CodePosition, JavaNode> resultMap = new HashMap<>(map.size());
|
||||
for (Map.Entry<CodePosition, Object> entry : map.entrySet()) {
|
||||
CodePosition codePosition = entry.getKey();
|
||||
Object obj = entry.getValue();
|
||||
JavaNode node = getRootDecompiler().convertNode(obj);
|
||||
if (node != null) {
|
||||
resultMap.put(codePosition, node);
|
||||
Map<Integer, JavaNode> resultMap = new HashMap<>(map.size());
|
||||
for (Map.Entry<Integer, ICodeAnnotation> entry : map.entrySet()) {
|
||||
int codePosition = entry.getKey();
|
||||
ICodeAnnotation obj = entry.getValue();
|
||||
if (obj instanceof ICodeNodeRef) {
|
||||
JavaNode node = getRootDecompiler().getJavaNodeByRef((ICodeNodeRef) obj);
|
||||
if (node != null) {
|
||||
resultMap.put(codePosition, node);
|
||||
}
|
||||
}
|
||||
}
|
||||
return resultMap;
|
||||
}
|
||||
|
||||
public List<CodePosition> getUsageFor(JavaNode javaNode) {
|
||||
Map<CodePosition, Object> map = getCodeAnnotations();
|
||||
if (map.isEmpty() || decompiler == null) {
|
||||
public List<Integer> getUsePlacesFor(ICodeInfo codeInfo, JavaNode javaNode) {
|
||||
if (!codeInfo.hasMetadata()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
Object internalNode = getRootDecompiler().getInternalNode(javaNode);
|
||||
List<CodePosition> result = new ArrayList<>();
|
||||
for (Map.Entry<CodePosition, Object> entry : map.entrySet()) {
|
||||
CodePosition codePosition = entry.getKey();
|
||||
Object obj = entry.getValue();
|
||||
if (internalNode.equals(obj)) {
|
||||
result.add(codePosition);
|
||||
List<Integer> result = new ArrayList<>();
|
||||
codeInfo.getCodeMetadata().searchDown(0, (pos, ann) -> {
|
||||
if (javaNode.isOwnCodeAnnotation(ann)) {
|
||||
result.add(pos);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -196,20 +215,8 @@ public final class JavaClass implements JavaNode {
|
||||
return getRootDecompiler().convertNodes(cls.getUseIn());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Deprecated
|
||||
public JavaNode getJavaNodeAtPosition(int line, int offset) {
|
||||
return getRootDecompiler().getJavaNodeAtPosition(getCodeInfo(), line, offset);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Deprecated
|
||||
public CodePosition getDefinitionPosition() {
|
||||
return getRootDecompiler().getDefinitionPosition(this);
|
||||
}
|
||||
|
||||
public Integer getSourceLine(int decompiledLine) {
|
||||
return getCodeInfo().getLineMapping().get(decompiledLine);
|
||||
return getCodeInfo().getCodeMetadata().getLineMapping().get(decompiledLine);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -237,7 +244,7 @@ public final class JavaClass implements JavaNode {
|
||||
|
||||
@Override
|
||||
public JavaClass getTopParentClass() {
|
||||
if (cls.contains(AFlag.ANONYMOUS_CLASS)) {
|
||||
if (cls.contains(AType.ANONYMOUS_CLASS)) {
|
||||
// moved to usage class
|
||||
return getParentForAnonymousClass();
|
||||
}
|
||||
@@ -245,15 +252,9 @@ public final class JavaClass implements JavaNode {
|
||||
}
|
||||
|
||||
private JavaClass getParentForAnonymousClass() {
|
||||
List<JavaNode> useIn = getUseIn();
|
||||
if (useIn.isEmpty()) {
|
||||
return this;
|
||||
}
|
||||
JavaNode useNode = useIn.get(0);
|
||||
if (useNode.equals(this)) {
|
||||
return this;
|
||||
}
|
||||
return useNode.getTopParentClass();
|
||||
AnonymousClassAttr attr = cls.get(AType.ANONYMOUS_CLASS);
|
||||
ClassNode topParentClass = attr.getOuterCls().getTopParentClass();
|
||||
return getRootDecompiler().convertClassNode(topParentClass);
|
||||
}
|
||||
|
||||
public AccessInfo getAccessInfo() {
|
||||
@@ -261,22 +262,22 @@ public final class JavaClass implements JavaNode {
|
||||
}
|
||||
|
||||
public List<JavaClass> getInnerClasses() {
|
||||
loadLists();
|
||||
load();
|
||||
return innerClasses;
|
||||
}
|
||||
|
||||
public List<JavaClass> getInlinedClasses() {
|
||||
loadLists();
|
||||
load();
|
||||
return inlinedClasses;
|
||||
}
|
||||
|
||||
public List<JavaField> getFields() {
|
||||
loadLists();
|
||||
load();
|
||||
return fields;
|
||||
}
|
||||
|
||||
public List<JavaMethod> getMethods() {
|
||||
loadLists();
|
||||
load();
|
||||
return methods;
|
||||
}
|
||||
|
||||
@@ -286,7 +287,16 @@ public final class JavaClass implements JavaNode {
|
||||
if (methodNode == null) {
|
||||
return null;
|
||||
}
|
||||
return new JavaMethod(this, methodNode);
|
||||
return getRootDecompiler().convertMethodNode(methodNode);
|
||||
}
|
||||
|
||||
public List<JavaClass> getDependencies() {
|
||||
JadxDecompiler d = getRootDecompiler();
|
||||
return ListUtils.map(cls.getDependencies(), d::convertClassNode);
|
||||
}
|
||||
|
||||
public int getTotalDepsCount() {
|
||||
return cls.getTotalDepsCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -294,11 +304,6 @@ public final class JavaClass implements JavaNode {
|
||||
this.cls.getClassInfo().removeAlias();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDecompiledLine() {
|
||||
return cls.getDecompiledLine();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDefPos() {
|
||||
return cls.getDefPosition();
|
||||
|
||||
@@ -4,6 +4,7 @@ import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import jadx.api.metadata.ICodeAnnotation;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
@@ -28,6 +29,10 @@ public final class JavaField implements JavaNode {
|
||||
return parent.getFullName() + '.' + getName();
|
||||
}
|
||||
|
||||
public String getRawName() {
|
||||
return field.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaClass getDeclaringClass() {
|
||||
return parent;
|
||||
@@ -46,11 +51,6 @@ public final class JavaField implements JavaNode {
|
||||
return ArgType.tryToResolveClassAlias(field.root(), field.getType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDecompiledLine() {
|
||||
return field.getDecompiledLine();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDefPos() {
|
||||
return field.getDefPosition();
|
||||
@@ -66,6 +66,14 @@ public final class JavaField implements JavaNode {
|
||||
this.field.getFieldInfo().removeAlias();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOwnCodeAnnotation(ICodeAnnotation ann) {
|
||||
if (ann.getAnnType() == ICodeAnnotation.AnnType.FIELD) {
|
||||
return ann.equals(field);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal API. Not Stable!
|
||||
*/
|
||||
|
||||
@@ -2,10 +2,14 @@ package jadx.api;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.metadata.ICodeAnnotation;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
@@ -14,6 +18,8 @@ import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
public final class JavaMethod implements JavaNode {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JavaMethod.class);
|
||||
|
||||
private final MethodNode mth;
|
||||
private final JavaClass parent;
|
||||
|
||||
@@ -73,7 +79,14 @@ public final class JavaMethod implements JavaNode {
|
||||
}
|
||||
JadxDecompiler decompiler = getDeclaringClass().getRootDecompiler();
|
||||
return ovrdAttr.getRelatedMthNodes().stream()
|
||||
.map(m -> ((JavaMethod) decompiler.convertNode(m)))
|
||||
.map(m -> {
|
||||
JavaMethod javaMth = decompiler.convertMethodNode(m);
|
||||
if (javaMth == null) {
|
||||
LOG.warn("Failed convert to java method: {}", m);
|
||||
}
|
||||
return javaMth;
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@@ -85,11 +98,6 @@ public final class JavaMethod implements JavaNode {
|
||||
return mth.getMethodInfo().isClassInit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDecompiledLine() {
|
||||
return mth.getDecompiledLine();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDefPos() {
|
||||
return mth.getDefPosition();
|
||||
@@ -100,6 +108,14 @@ public final class JavaMethod implements JavaNode {
|
||||
this.mth.getMethodInfo().removeAlias();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOwnCodeAnnotation(ICodeAnnotation ann) {
|
||||
if (ann.getAnnType() == ICodeAnnotation.AnnType.METHOD) {
|
||||
return ann.equals(mth);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal API. Not Stable!
|
||||
*/
|
||||
|
||||
@@ -2,6 +2,8 @@ package jadx.api;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import jadx.api.metadata.ICodeAnnotation;
|
||||
|
||||
public interface JavaNode {
|
||||
|
||||
String getName();
|
||||
@@ -12,12 +14,12 @@ public interface JavaNode {
|
||||
|
||||
JavaClass getTopParentClass();
|
||||
|
||||
int getDecompiledLine();
|
||||
|
||||
int getDefPos();
|
||||
|
||||
List<JavaNode> getUseIn();
|
||||
|
||||
default void removeAlias() {
|
||||
}
|
||||
|
||||
boolean isOwnCodeAnnotation(ICodeAnnotation ann);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import jadx.api.metadata.ICodeAnnotation;
|
||||
|
||||
public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
|
||||
private final String name;
|
||||
private final List<JavaClass> classes;
|
||||
@@ -39,11 +41,6 @@ public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDecompiledLine() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDefPos() {
|
||||
return 0;
|
||||
@@ -54,6 +51,11 @@ public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOwnCodeAnnotation(ICodeAnnotation ann) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull JavaPackage o) {
|
||||
return name.compareTo(o.name);
|
||||
|
||||
@@ -5,16 +5,18 @@ import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import jadx.api.data.annotations.VarDeclareRef;
|
||||
import jadx.api.data.annotations.VarRef;
|
||||
import jadx.api.metadata.ICodeAnnotation;
|
||||
import jadx.api.metadata.annotations.VarNode;
|
||||
import jadx.api.metadata.annotations.VarRef;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
|
||||
public class JavaVariable implements JavaNode {
|
||||
private final JavaMethod mth;
|
||||
private final VarRef varRef;
|
||||
private final VarNode varNode;
|
||||
|
||||
public JavaVariable(JavaMethod mth, VarRef varRef) {
|
||||
public JavaVariable(JavaMethod mth, VarNode varNode) {
|
||||
this.mth = mth;
|
||||
this.varRef = varRef;
|
||||
this.varNode = varNode;
|
||||
}
|
||||
|
||||
public JavaMethod getMth() {
|
||||
@@ -22,26 +24,30 @@ public class JavaVariable implements JavaNode {
|
||||
}
|
||||
|
||||
public int getReg() {
|
||||
return varRef.getReg();
|
||||
return varNode.getReg();
|
||||
}
|
||||
|
||||
public int getSsa() {
|
||||
return varRef.getSsa();
|
||||
return varNode.getSsa();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return varRef.getName();
|
||||
return varNode.getName();
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public VarRef getVarRef() {
|
||||
return varRef;
|
||||
public VarNode getVarNode() {
|
||||
return varNode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFullName() {
|
||||
return varRef.getType() + " " + varRef.getName() + " (r" + varRef.getReg() + "v" + varRef.getSsa() + ")";
|
||||
return varNode.getType() + " " + varNode.getName() + " (r" + varNode.getReg() + "v" + varNode.getSsa() + ")";
|
||||
}
|
||||
|
||||
public ArgType getType() {
|
||||
return ArgType.tryToResolveClassAlias(mth.getMethodNode().root(), varNode.getType());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -54,20 +60,9 @@ public class JavaVariable implements JavaNode {
|
||||
return mth.getTopParentClass();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDecompiledLine() {
|
||||
if (varRef instanceof VarDeclareRef) {
|
||||
return ((VarDeclareRef) varRef).getDecompiledLine();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDefPos() {
|
||||
if (varRef instanceof VarDeclareRef) {
|
||||
return ((VarDeclareRef) varRef).getDefPosition();
|
||||
}
|
||||
return 0;
|
||||
return varNode.getDefPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -75,9 +70,18 @@ public class JavaVariable implements JavaNode {
|
||||
return Collections.singletonList(mth);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOwnCodeAnnotation(ICodeAnnotation ann) {
|
||||
if (ann.getAnnType() == ICodeAnnotation.AnnType.VAR_REF) {
|
||||
VarRef varRef = (VarRef) ann;
|
||||
return varRef.getRefPos() == getDefPos();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return varRef.hashCode();
|
||||
return varNode.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -88,6 +92,6 @@ public class JavaVariable implements JavaNode {
|
||||
if (!(o instanceof JavaVariable)) {
|
||||
return false;
|
||||
}
|
||||
return varRef.equals(((JavaVariable) o).varRef);
|
||||
return varNode.equals(((JavaVariable) o).varNode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,8 +126,9 @@ public final class ResourcesLoader {
|
||||
if (name.endsWith(".9.png")) {
|
||||
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
|
||||
Res9patchStreamDecoder decoder = new Res9patchStreamDecoder();
|
||||
decoder.decode(inputStream, os);
|
||||
return ResContainer.decodedData(rf.getDeobfName(), os.toByteArray());
|
||||
if (decoder.decode(inputStream, os)) {
|
||||
return ResContainer.decodedData(rf.getDeobfName(), os.toByteArray());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to decode 9-patch png image, path: {}", name, e);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
package jadx.api.data.annotations;
|
||||
|
||||
public interface ICodeRawOffset {
|
||||
int getOffset();
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package jadx.api.data.annotations;
|
||||
|
||||
import jadx.core.dex.attributes.ILineAttributeNode;
|
||||
import jadx.core.dex.instructions.args.CodeVar;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
|
||||
public class VarDeclareRef extends VarRef implements ILineAttributeNode {
|
||||
|
||||
public static VarDeclareRef get(MethodNode mth, CodeVar codeVar) {
|
||||
VarDeclareRef ref = new VarDeclareRef(mth, codeVar);
|
||||
codeVar.setCachedVarRef(ref);
|
||||
return ref;
|
||||
}
|
||||
|
||||
private int sourceLine;
|
||||
private int decompiledLine;
|
||||
private int defPosition;
|
||||
|
||||
private VarDeclareRef(MethodNode mth, CodeVar codeVar) {
|
||||
super(mth, codeVar.getAnySsaVar());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSourceLine() {
|
||||
return sourceLine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSourceLine(int sourceLine) {
|
||||
this.sourceLine = sourceLine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDecompiledLine() {
|
||||
return decompiledLine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDecompiledLine(int decompiledLine) {
|
||||
this.decompiledLine = decompiledLine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDefPosition() {
|
||||
return defPosition;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDefPosition(int pos) {
|
||||
this.defPosition = pos;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "VarDeclareRef{r" + getReg() + 'v' + getSsa() + '}';
|
||||
}
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
package jadx.api.data.annotations;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.CodeVar;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
|
||||
public class VarRef {
|
||||
|
||||
@Nullable
|
||||
public static VarRef get(MethodNode mth, RegisterArg reg) {
|
||||
SSAVar ssaVar = reg.getSVar();
|
||||
if (ssaVar == null) {
|
||||
return null;
|
||||
}
|
||||
CodeVar codeVar = ssaVar.getCodeVar();
|
||||
VarRef cachedVarRef = codeVar.getCachedVarRef();
|
||||
if (cachedVarRef != null) {
|
||||
if (cachedVarRef.getName() == null) {
|
||||
cachedVarRef.setName(codeVar.getName());
|
||||
}
|
||||
return cachedVarRef;
|
||||
}
|
||||
VarRef newVarRef = new VarRef(mth, ssaVar);
|
||||
codeVar.setCachedVarRef(newVarRef);
|
||||
return newVarRef;
|
||||
}
|
||||
|
||||
private final MethodNode mth;
|
||||
private final int reg;
|
||||
private final int ssa;
|
||||
private final ArgType type;
|
||||
private String name;
|
||||
|
||||
protected VarRef(MethodNode mth, SSAVar ssaVar) {
|
||||
this(mth, ssaVar.getRegNum(), ssaVar.getVersion(),
|
||||
ssaVar.getCodeVar().getType(), ssaVar.getCodeVar().getName());
|
||||
}
|
||||
|
||||
private VarRef(MethodNode mth, int reg, int ssa, ArgType type, String name) {
|
||||
this.mth = mth;
|
||||
this.reg = reg;
|
||||
this.ssa = ssa;
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public MethodNode getMth() {
|
||||
return mth;
|
||||
}
|
||||
|
||||
public int getReg() {
|
||||
return reg;
|
||||
}
|
||||
|
||||
public int getSsa() {
|
||||
return ssa;
|
||||
}
|
||||
|
||||
public ArgType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof VarRef)) {
|
||||
return false;
|
||||
}
|
||||
VarRef other = (VarRef) o;
|
||||
return getReg() == other.getReg()
|
||||
&& getSsa() == other.getSsa()
|
||||
&& getMth().equals(other.getMth());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 31 * getReg() + getSsa();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "VarUseRef{r" + reg + 'v' + ssa + '}';
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ package jadx.api.data.impl;
|
||||
import jadx.api.JavaVariable;
|
||||
import jadx.api.data.CodeRefType;
|
||||
import jadx.api.data.IJavaCodeRef;
|
||||
import jadx.api.data.annotations.VarRef;
|
||||
import jadx.api.metadata.annotations.VarNode;
|
||||
|
||||
public class JadxCodeRef implements IJavaCodeRef {
|
||||
|
||||
@@ -23,8 +23,8 @@ public class JadxCodeRef implements IJavaCodeRef {
|
||||
return forVar(javaVariable.getReg(), javaVariable.getSsa());
|
||||
}
|
||||
|
||||
public static JadxCodeRef forVar(VarRef varRef) {
|
||||
return forVar(varRef.getReg(), varRef.getSsa());
|
||||
public static JadxCodeRef forVar(VarNode varNode) {
|
||||
return forVar(varNode.getReg(), varNode.getSsa());
|
||||
}
|
||||
|
||||
public static JadxCodeRef forCatch(int handlerOffset) {
|
||||
|
||||
@@ -2,23 +2,19 @@ package jadx.api.impl;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import jadx.api.CodePosition;
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.api.metadata.ICodeAnnotation;
|
||||
import jadx.api.metadata.ICodeMetadata;
|
||||
import jadx.api.metadata.impl.CodeMetadataStorage;
|
||||
|
||||
public class AnnotatedCodeInfo implements ICodeInfo {
|
||||
|
||||
private final String code;
|
||||
private final Map<Integer, Integer> lineMapping;
|
||||
private final Map<CodePosition, Object> annotations;
|
||||
private final ICodeMetadata metadata;
|
||||
|
||||
public AnnotatedCodeInfo(ICodeInfo codeInfo) {
|
||||
this(codeInfo.getCodeStr(), codeInfo.getLineMapping(), codeInfo.getAnnotations());
|
||||
}
|
||||
|
||||
public AnnotatedCodeInfo(String code, Map<Integer, Integer> lineMapping, Map<CodePosition, Object> annotations) {
|
||||
public AnnotatedCodeInfo(String code, Map<Integer, Integer> lineMapping, Map<Integer, ICodeAnnotation> annotations) {
|
||||
this.code = code;
|
||||
this.lineMapping = lineMapping;
|
||||
this.annotations = annotations;
|
||||
this.metadata = CodeMetadataStorage.build(lineMapping, annotations);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -27,13 +23,13 @@ public class AnnotatedCodeInfo implements ICodeInfo {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Integer, Integer> getLineMapping() {
|
||||
return lineMapping;
|
||||
public ICodeMetadata getCodeMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<CodePosition, Object> getAnnotations() {
|
||||
return annotations;
|
||||
public boolean hasMetadata() {
|
||||
return metadata != ICodeMetadata.EMPTY;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -5,18 +5,20 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import jadx.api.CodePosition;
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.core.dex.attributes.ILineAttributeNode;
|
||||
import jadx.api.metadata.ICodeAnnotation;
|
||||
import jadx.api.metadata.ICodeNodeRef;
|
||||
import jadx.api.metadata.annotations.NodeDeclareRef;
|
||||
import jadx.api.metadata.annotations.VarRef;
|
||||
import jadx.core.utils.StringUtils;
|
||||
|
||||
public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter {
|
||||
|
||||
private int line = 1;
|
||||
private int offset;
|
||||
private Map<CodePosition, Object> annotations = Collections.emptyMap();
|
||||
private Map<Integer, ICodeAnnotation> annotations = Collections.emptyMap();
|
||||
private Map<Integer, Integer> lineMap = Collections.emptyMap();
|
||||
|
||||
public AnnotatedCodeWriter() {
|
||||
@@ -59,19 +61,17 @@ public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter
|
||||
|
||||
@Override
|
||||
public ICodeWriter add(ICodeWriter cw) {
|
||||
if ((!(cw instanceof AnnotatedCodeWriter))) {
|
||||
if (!cw.isMetadataSupported()) {
|
||||
buf.append(cw.getCodeStr());
|
||||
return this;
|
||||
}
|
||||
AnnotatedCodeWriter code = ((AnnotatedCodeWriter) cw);
|
||||
line--;
|
||||
int startLine = line;
|
||||
int startPos = getLength();
|
||||
for (Map.Entry<CodePosition, Object> entry : code.annotations.entrySet()) {
|
||||
CodePosition codePos = entry.getKey();
|
||||
int newLine = startLine + codePos.getLine();
|
||||
int newPos = startPos + codePos.getPos();
|
||||
attachAnnotation(entry.getValue(), new CodePosition(newLine, codePos.getOffset(), newPos));
|
||||
for (Map.Entry<Integer, ICodeAnnotation> entry : code.annotations.entrySet()) {
|
||||
int pos = entry.getKey();
|
||||
int newPos = startPos + pos;
|
||||
attachAnnotation(entry.getValue(), newPos);
|
||||
}
|
||||
for (Map.Entry<Integer, Integer> entry : code.lineMap.entrySet()) {
|
||||
attachSourceLine(line + entry.getKey(), entry.getValue());
|
||||
@@ -101,44 +101,36 @@ public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter
|
||||
return line;
|
||||
}
|
||||
|
||||
private static final class DefinitionWrapper {
|
||||
private final ILineAttributeNode node;
|
||||
|
||||
private DefinitionWrapper(ILineAttributeNode node) {
|
||||
this.node = node;
|
||||
}
|
||||
|
||||
public ILineAttributeNode getNode() {
|
||||
return node;
|
||||
}
|
||||
@Override
|
||||
public int getLineStartPos() {
|
||||
return getLength() - offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attachDefinition(ILineAttributeNode obj) {
|
||||
public void attachDefinition(ICodeNodeRef obj) {
|
||||
if (obj == null) {
|
||||
return;
|
||||
}
|
||||
attachAnnotation(obj);
|
||||
attachAnnotation(new DefinitionWrapper(obj), new CodePosition(line, offset, getLength()));
|
||||
attachAnnotation(new NodeDeclareRef(obj));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attachAnnotation(Object obj) {
|
||||
public void attachAnnotation(ICodeAnnotation obj) {
|
||||
if (obj == null) {
|
||||
return;
|
||||
}
|
||||
attachAnnotation(obj, new CodePosition(line, offset + 1, getLength()));
|
||||
attachAnnotation(obj, getLength());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attachLineAnnotation(Object obj) {
|
||||
public void attachLineAnnotation(ICodeAnnotation obj) {
|
||||
if (obj == null) {
|
||||
return;
|
||||
}
|
||||
attachAnnotation(obj, new CodePosition(line, 0, getLength() - offset));
|
||||
attachAnnotation(obj, getLineStartPos());
|
||||
}
|
||||
|
||||
private void attachAnnotation(Object obj, CodePosition pos) {
|
||||
private void attachAnnotation(ICodeAnnotation obj, int pos) {
|
||||
if (annotations.isEmpty()) {
|
||||
annotations = new HashMap<>();
|
||||
}
|
||||
@@ -164,29 +156,39 @@ public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter
|
||||
public ICodeInfo finish() {
|
||||
removeFirstEmptyLine();
|
||||
processDefinitionAnnotations();
|
||||
validateAnnotations();
|
||||
String code = buf.toString();
|
||||
buf = null;
|
||||
return new AnnotatedCodeInfo(code, lineMap, annotations);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<CodePosition, Object> getRawAnnotations() {
|
||||
public Map<Integer, ICodeAnnotation> getRawAnnotations() {
|
||||
return annotations;
|
||||
}
|
||||
|
||||
private void processDefinitionAnnotations() {
|
||||
if (!annotations.isEmpty()) {
|
||||
annotations.entrySet().removeIf(entry -> {
|
||||
Object v = entry.getValue();
|
||||
if (v instanceof DefinitionWrapper) {
|
||||
ILineAttributeNode l = ((DefinitionWrapper) v).getNode();
|
||||
CodePosition codePos = entry.getKey();
|
||||
l.setDecompiledLine(codePos.getLine());
|
||||
l.setDefPosition(codePos.getPos());
|
||||
return true;
|
||||
annotations.forEach((k, v) -> {
|
||||
if (v instanceof NodeDeclareRef) {
|
||||
NodeDeclareRef declareRef = (NodeDeclareRef) v;
|
||||
declareRef.setDefPos(k);
|
||||
declareRef.getNode().setDefPosition(k);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void validateAnnotations() {
|
||||
if (annotations.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
annotations.values().removeIf(v -> {
|
||||
if (v.getAnnType() == ICodeAnnotation.AnnType.VAR_REF) {
|
||||
VarRef varRef = (VarRef) v;
|
||||
return varRef.getRefPos() == 0;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package jadx.api.impl;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.ICodeCache;
|
||||
import jadx.api.ICodeInfo;
|
||||
|
||||
public abstract class DelegateCodeCache implements ICodeCache {
|
||||
|
||||
protected final ICodeCache backCache;
|
||||
|
||||
public DelegateCodeCache(ICodeCache backCache) {
|
||||
this.backCache = backCache;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(String clsFullName, ICodeInfo codeInfo) {
|
||||
backCache.add(clsFullName, codeInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(String clsFullName) {
|
||||
backCache.remove(clsFullName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ICodeInfo get(String clsFullName) {
|
||||
return backCache.get(clsFullName);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public String getCode(String clsFullName) {
|
||||
return backCache.getCode(clsFullName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(String clsFullName) {
|
||||
return backCache.contains(clsFullName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
backCache.close();
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
package jadx.api.impl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.ICodeCache;
|
||||
@@ -22,9 +24,33 @@ public class InMemoryCodeCache implements ICodeCache {
|
||||
storage.remove(clsFullName);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public @Nullable ICodeInfo get(String clsFullName) {
|
||||
return storage.get(clsFullName);
|
||||
public ICodeInfo get(String clsFullName) {
|
||||
ICodeInfo codeInfo = storage.get(clsFullName);
|
||||
if (codeInfo == null) {
|
||||
return ICodeInfo.EMPTY;
|
||||
}
|
||||
return codeInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String getCode(String clsFullName) {
|
||||
ICodeInfo codeInfo = storage.get(clsFullName);
|
||||
if (codeInfo == null) {
|
||||
return null;
|
||||
}
|
||||
return codeInfo.getCodeStr();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(String clsFullName) {
|
||||
return storage.containsKey(clsFullName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
storage.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package jadx.api.impl;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.ICodeCache;
|
||||
@@ -7,6 +8,8 @@ import jadx.api.ICodeInfo;
|
||||
|
||||
public class NoOpCodeCache implements ICodeCache {
|
||||
|
||||
public static final NoOpCodeCache INSTANCE = new NoOpCodeCache();
|
||||
|
||||
@Override
|
||||
public void add(String clsFullName, ICodeInfo codeInfo) {
|
||||
// do nothing
|
||||
@@ -18,10 +21,26 @@ public class NoOpCodeCache implements ICodeCache {
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ICodeInfo get(String clsFullName) {
|
||||
@NotNull
|
||||
public ICodeInfo get(String clsFullName) {
|
||||
return ICodeInfo.EMPTY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String getCode(String clsFullName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(String clsFullName) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "NoOpCodeCache";
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
package jadx.api.impl;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import jadx.api.CodePosition;
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.api.metadata.ICodeMetadata;
|
||||
|
||||
public class SimpleCodeInfo implements ICodeInfo {
|
||||
|
||||
@@ -20,13 +17,13 @@ public class SimpleCodeInfo implements ICodeInfo {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Integer, Integer> getLineMapping() {
|
||||
return Collections.emptyMap();
|
||||
public ICodeMetadata getCodeMetadata() {
|
||||
return ICodeMetadata.EMPTY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<CodePosition, Object> getAnnotations() {
|
||||
return Collections.emptyMap();
|
||||
public boolean hasMetadata() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -6,11 +6,11 @@ import java.util.Map;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.CodePosition;
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.core.dex.attributes.ILineAttributeNode;
|
||||
import jadx.api.metadata.ICodeAnnotation;
|
||||
import jadx.api.metadata.ICodeNodeRef;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
/**
|
||||
@@ -193,17 +193,22 @@ public class SimpleCodeWriter implements ICodeWriter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attachDefinition(ILineAttributeNode obj) {
|
||||
public int getLineStartPos() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attachDefinition(ICodeNodeRef obj) {
|
||||
// no op
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attachAnnotation(Object obj) {
|
||||
public void attachAnnotation(ICodeAnnotation obj) {
|
||||
// no op
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attachLineAnnotation(Object obj) {
|
||||
public void attachLineAnnotation(ICodeAnnotation obj) {
|
||||
// no op
|
||||
}
|
||||
|
||||
@@ -238,7 +243,7 @@ public class SimpleCodeWriter implements ICodeWriter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<CodePosition, Object> getRawAnnotations() {
|
||||
public Map<Integer, ICodeAnnotation> getRawAnnotations() {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package jadx.api.metadata;
|
||||
|
||||
public interface ICodeAnnotation {
|
||||
|
||||
enum AnnType {
|
||||
CLASS,
|
||||
FIELD,
|
||||
METHOD,
|
||||
VAR,
|
||||
VAR_REF,
|
||||
DECLARATION,
|
||||
OFFSET
|
||||
}
|
||||
|
||||
AnnType getAnnType();
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package jadx.api.metadata;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.metadata.impl.CodeMetadataStorage;
|
||||
|
||||
public interface ICodeMetadata {
|
||||
|
||||
ICodeMetadata EMPTY = CodeMetadataStorage.empty();
|
||||
|
||||
@Nullable
|
||||
ICodeAnnotation getAt(int position);
|
||||
|
||||
@Nullable
|
||||
ICodeAnnotation getClosestUp(int position);
|
||||
|
||||
@Nullable
|
||||
ICodeAnnotation searchUp(int position, ICodeAnnotation.AnnType annType);
|
||||
|
||||
@Nullable
|
||||
ICodeAnnotation searchUp(int position, int limitPos, ICodeAnnotation.AnnType annType);
|
||||
|
||||
/**
|
||||
* Iterate code annotations from {@code startPos} to smaller positions.
|
||||
*
|
||||
* @param visitor
|
||||
* return not null value to stop iterations
|
||||
*/
|
||||
@Nullable
|
||||
<T> T searchUp(int startPos, BiFunction<Integer, ICodeAnnotation, T> visitor);
|
||||
|
||||
/**
|
||||
* Iterate code annotations from {@code startPos} to higher positions.
|
||||
*
|
||||
* @param visitor
|
||||
* return not null value to stop iterations
|
||||
*/
|
||||
@Nullable
|
||||
<T> T searchDown(int startPos, BiFunction<Integer, ICodeAnnotation, T> visitor);
|
||||
|
||||
/**
|
||||
* Get current node at position (can be enclosing class or method)
|
||||
*/
|
||||
@Nullable
|
||||
ICodeNodeRef getNodeAt(int position);
|
||||
|
||||
/**
|
||||
* Any definition of class or method below position
|
||||
*/
|
||||
@Nullable
|
||||
ICodeNodeRef getNodeBelow(int position);
|
||||
|
||||
Map<Integer, ICodeAnnotation> getAsMap();
|
||||
|
||||
Map<Integer, Integer> getLineMapping();
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package jadx.api.metadata;
|
||||
|
||||
public interface ICodeNodeRef extends ICodeAnnotation {
|
||||
int getDefPosition();
|
||||
|
||||
void setDefPosition(int pos);
|
||||
}
|
||||
+8
-3
@@ -1,11 +1,12 @@
|
||||
package jadx.api.data.annotations;
|
||||
package jadx.api.metadata.annotations;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.api.metadata.ICodeAnnotation;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
|
||||
public class InsnCodeOffset implements ICodeRawOffset {
|
||||
public class InsnCodeOffset implements ICodeAnnotation {
|
||||
|
||||
public static void attach(ICodeWriter code, InsnNode insn) {
|
||||
if (insn == null) {
|
||||
@@ -40,11 +41,15 @@ public class InsnCodeOffset implements ICodeRawOffset {
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnnType getAnnType() {
|
||||
return AnnType.OFFSET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "offset=" + offset;
|
||||
@@ -0,0 +1,39 @@
|
||||
package jadx.api.metadata.annotations;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import jadx.api.metadata.ICodeAnnotation;
|
||||
import jadx.api.metadata.ICodeNodeRef;
|
||||
|
||||
public class NodeDeclareRef implements ICodeAnnotation {
|
||||
|
||||
private final ICodeNodeRef node;
|
||||
|
||||
private int defPos;
|
||||
|
||||
public NodeDeclareRef(ICodeNodeRef node) {
|
||||
this.node = Objects.requireNonNull(node);
|
||||
}
|
||||
|
||||
public ICodeNodeRef getNode() {
|
||||
return node;
|
||||
}
|
||||
|
||||
public int getDefPos() {
|
||||
return defPos;
|
||||
}
|
||||
|
||||
public void setDefPos(int defPos) {
|
||||
this.defPos = defPos;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnnType getAnnType() {
|
||||
return AnnType.DECLARATION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "NodeDeclareRef{" + node + '}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
package jadx.api.metadata.annotations;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.metadata.ICodeAnnotation;
|
||||
import jadx.api.metadata.ICodeNodeRef;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.CodeVar;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
|
||||
/**
|
||||
* Variable info
|
||||
*/
|
||||
public class VarNode implements ICodeNodeRef {
|
||||
|
||||
@Nullable
|
||||
public static VarNode get(MethodNode mth, RegisterArg reg) {
|
||||
SSAVar ssaVar = reg.getSVar();
|
||||
if (ssaVar == null) {
|
||||
return null;
|
||||
}
|
||||
return get(mth, ssaVar);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static VarNode get(MethodNode mth, CodeVar codeVar) {
|
||||
return get(mth, codeVar.getAnySsaVar());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static VarNode get(MethodNode mth, SSAVar ssaVar) {
|
||||
CodeVar codeVar = ssaVar.getCodeVar();
|
||||
if (codeVar.isThis()) {
|
||||
return null;
|
||||
}
|
||||
VarNode cachedVarNode = codeVar.getCachedVarNode();
|
||||
if (cachedVarNode != null) {
|
||||
return cachedVarNode;
|
||||
}
|
||||
VarNode newVarNode = new VarNode(mth, ssaVar);
|
||||
codeVar.setCachedVarNode(newVarNode);
|
||||
return newVarNode;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static ICodeAnnotation getRef(MethodNode mth, RegisterArg reg) {
|
||||
VarNode varNode = get(mth, reg);
|
||||
if (varNode == null) {
|
||||
return null;
|
||||
}
|
||||
return varNode.getVarRef();
|
||||
}
|
||||
|
||||
private final MethodNode mth;
|
||||
private final int reg;
|
||||
private final int ssa;
|
||||
private final ArgType type;
|
||||
private @Nullable String name;
|
||||
private int defPos;
|
||||
|
||||
private final VarRef varRef;
|
||||
|
||||
protected VarNode(MethodNode mth, SSAVar ssaVar) {
|
||||
this(mth, ssaVar.getRegNum(), ssaVar.getVersion(),
|
||||
ssaVar.getCodeVar().getType(), ssaVar.getCodeVar().getName());
|
||||
}
|
||||
|
||||
public VarNode(MethodNode mth, int reg, int ssa, ArgType type, String name) {
|
||||
this.mth = mth;
|
||||
this.reg = reg;
|
||||
this.ssa = ssa;
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
this.varRef = VarRef.fromVarNode(this);
|
||||
}
|
||||
|
||||
public MethodNode getMth() {
|
||||
return mth;
|
||||
}
|
||||
|
||||
public int getReg() {
|
||||
return reg;
|
||||
}
|
||||
|
||||
public int getSsa() {
|
||||
return ssa;
|
||||
}
|
||||
|
||||
public ArgType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public VarRef getVarRef() {
|
||||
return varRef;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDefPosition() {
|
||||
return defPos;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDefPosition(int pos) {
|
||||
this.defPos = pos;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnnType getAnnType() {
|
||||
return AnnType.VAR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int h = 31 * getReg() + getSsa();
|
||||
return 31 * h + mth.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof VarNode)) {
|
||||
return false;
|
||||
}
|
||||
VarNode other = (VarNode) o;
|
||||
return getReg() == other.getReg()
|
||||
&& getSsa() == other.getSsa()
|
||||
&& getMth().equals(other.getMth());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "VarNode{r" + reg + 'v' + ssa + '}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package jadx.api.metadata.annotations;
|
||||
|
||||
import jadx.api.metadata.ICodeAnnotation;
|
||||
|
||||
/**
|
||||
* Variable reference by position of VarNode in code metadata.
|
||||
* <br>
|
||||
* Because on creation position not yet known,
|
||||
* VarRef created using VarNode as a source of ref pos during serialization.
|
||||
* <br>
|
||||
* On metadata deserialization created with ref pos directly.
|
||||
*/
|
||||
public abstract class VarRef implements ICodeAnnotation {
|
||||
|
||||
public static VarRef fromPos(int refPos) {
|
||||
if (refPos == 0) {
|
||||
throw new IllegalArgumentException("Zero refPos");
|
||||
}
|
||||
return new FixedVarRef(refPos);
|
||||
}
|
||||
|
||||
public static VarRef fromVarNode(VarNode varNode) {
|
||||
return new RelatedVarRef(varNode);
|
||||
}
|
||||
|
||||
public abstract int getRefPos();
|
||||
|
||||
@Override
|
||||
public AnnType getAnnType() {
|
||||
return AnnType.VAR_REF;
|
||||
}
|
||||
|
||||
public static final class FixedVarRef extends VarRef {
|
||||
private final int refPos;
|
||||
|
||||
public FixedVarRef(int refPos) {
|
||||
this.refPos = refPos;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRefPos() {
|
||||
return refPos;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class RelatedVarRef extends VarRef {
|
||||
private final VarNode varNode;
|
||||
|
||||
public RelatedVarRef(VarNode varNode) {
|
||||
this.varNode = varNode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRefPos() {
|
||||
return varNode.getDefPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "VarRef{" + varNode + ", name=" + varNode.getName() + ", mth=" + varNode.getMth() + '}';
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "VarRef{" + getRefPos() + '}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
package jadx.api.metadata.impl;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
import java.util.NavigableMap;
|
||||
import java.util.TreeMap;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.metadata.ICodeAnnotation;
|
||||
import jadx.api.metadata.ICodeMetadata;
|
||||
import jadx.api.metadata.ICodeNodeRef;
|
||||
import jadx.api.metadata.annotations.NodeDeclareRef;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
public class CodeMetadataStorage implements ICodeMetadata {
|
||||
|
||||
public static ICodeMetadata build(Map<Integer, Integer> lines, Map<Integer, ICodeAnnotation> map) {
|
||||
if (map.isEmpty() && lines.isEmpty()) {
|
||||
return ICodeMetadata.EMPTY;
|
||||
}
|
||||
Comparator<Integer> reverseCmp = Comparator.comparingInt(Integer::intValue).reversed();
|
||||
NavigableMap<Integer, ICodeAnnotation> navMap = new TreeMap<>(reverseCmp);
|
||||
navMap.putAll(map);
|
||||
return new CodeMetadataStorage(lines, navMap);
|
||||
}
|
||||
|
||||
public static ICodeMetadata empty() {
|
||||
return new CodeMetadataStorage(Collections.emptyMap(), Collections.emptyNavigableMap());
|
||||
}
|
||||
|
||||
private final Map<Integer, Integer> lines;
|
||||
|
||||
private final NavigableMap<Integer, ICodeAnnotation> navMap;
|
||||
|
||||
private CodeMetadataStorage(Map<Integer, Integer> lines, NavigableMap<Integer, ICodeAnnotation> navMap) {
|
||||
this.lines = lines;
|
||||
this.navMap = navMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ICodeAnnotation getAt(int position) {
|
||||
return navMap.get(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ICodeAnnotation getClosestUp(int position) {
|
||||
Map.Entry<Integer, ICodeAnnotation> entryBefore = navMap.higherEntry(position);
|
||||
return entryBefore != null ? entryBefore.getValue() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ICodeAnnotation searchUp(int position, ICodeAnnotation.AnnType annType) {
|
||||
for (ICodeAnnotation v : navMap.tailMap(position, true).values()) {
|
||||
if (v.getAnnType() == annType) {
|
||||
return v;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ICodeAnnotation searchUp(int position, int limitPos, ICodeAnnotation.AnnType annType) {
|
||||
for (ICodeAnnotation v : navMap.subMap(position, true, limitPos, true).values()) {
|
||||
if (v.getAnnType() == annType) {
|
||||
return v;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> @Nullable T searchUp(int startPos, BiFunction<Integer, ICodeAnnotation, T> visitor) {
|
||||
for (Map.Entry<Integer, ICodeAnnotation> entry : navMap.tailMap(startPos, true).entrySet()) {
|
||||
T value = visitor.apply(entry.getKey(), entry.getValue());
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> @Nullable T searchDown(int startPos, BiFunction<Integer, ICodeAnnotation, T> visitor) {
|
||||
NavigableMap<Integer, ICodeAnnotation> map = navMap.headMap(startPos, true).descendingMap();
|
||||
for (Map.Entry<Integer, ICodeAnnotation> entry : map.entrySet()) {
|
||||
T value = visitor.apply(entry.getKey(), entry.getValue());
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ICodeNodeRef getNodeAt(int position) {
|
||||
return navMap.tailMap(position, true)
|
||||
.values().stream()
|
||||
.flatMap(CodeMetadataStorage::mapEnclosingNode)
|
||||
.findFirst().orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ICodeNodeRef getNodeBelow(int position) {
|
||||
return navMap.headMap(position, true).descendingMap()
|
||||
.values().stream()
|
||||
.flatMap(CodeMetadataStorage::mapEnclosingNode)
|
||||
.findFirst().orElse(null);
|
||||
}
|
||||
|
||||
private static Stream<ICodeNodeRef> mapEnclosingNode(ICodeAnnotation ann) {
|
||||
if (ann instanceof NodeDeclareRef) {
|
||||
ICodeNodeRef node = ((NodeDeclareRef) ann).getNode();
|
||||
if (node instanceof ClassNode || node instanceof MethodNode) {
|
||||
return Stream.of(node);
|
||||
}
|
||||
}
|
||||
return Stream.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public NavigableMap<Integer, ICodeAnnotation> getAsMap() {
|
||||
return navMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Integer, Integer> getLineMapping() {
|
||||
return lines;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CodeMetadata{lines=" + lines
|
||||
+ ", annotations=\n" + Utils.listToString(navMap.entrySet(), "\n") + "\n}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package jadx.api.utils;
|
||||
|
||||
import jadx.api.ICodeWriter;
|
||||
|
||||
public class CodeUtils {
|
||||
|
||||
public static String getLineForPos(String code, int pos) {
|
||||
int start = getLineStartForPos(code, pos);
|
||||
int end = getLineEndForPos(code, pos);
|
||||
return code.substring(start, end);
|
||||
}
|
||||
|
||||
public static int getLineStartForPos(String code, int pos) {
|
||||
String newLine = ICodeWriter.NL;
|
||||
int start = code.lastIndexOf(newLine, pos);
|
||||
return start == -1 ? 0 : start + newLine.length();
|
||||
}
|
||||
|
||||
public static int getLineEndForPos(String code, int pos) {
|
||||
int end = code.indexOf(ICodeWriter.NL, pos);
|
||||
return end == -1 ? code.length() : end;
|
||||
}
|
||||
|
||||
public static int getLineNumForPos(String code, int pos) {
|
||||
String newLine = ICodeWriter.NL;
|
||||
int newLineLen = newLine.length();
|
||||
int line = 1;
|
||||
int prev = 0;
|
||||
while (true) {
|
||||
int next = code.indexOf(newLine, prev);
|
||||
if (next >= pos) {
|
||||
return line;
|
||||
}
|
||||
prev = next + newLineLen;
|
||||
line++;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.CommentsLevel;
|
||||
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.AttachMethodDetails;
|
||||
@@ -32,6 +33,7 @@ import jadx.core.dex.visitors.InitCodeVariables;
|
||||
import jadx.core.dex.visitors.InlineMethods;
|
||||
import jadx.core.dex.visitors.MarkMethodsForInline;
|
||||
import jadx.core.dex.visitors.MethodInvokeVisitor;
|
||||
import jadx.core.dex.visitors.MethodVisitor;
|
||||
import jadx.core.dex.visitors.ModVisitor;
|
||||
import jadx.core.dex.visitors.MoveInlineVisitor;
|
||||
import jadx.core.dex.visitors.OverrideMethodVisitor;
|
||||
@@ -60,8 +62,10 @@ import jadx.core.dex.visitors.rename.CodeRenameVisitor;
|
||||
import jadx.core.dex.visitors.rename.RenameVisitor;
|
||||
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
|
||||
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.usage.UsageInfoVisitor;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public class Jadx {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Jadx.class);
|
||||
@@ -69,21 +73,20 @@ public class Jadx {
|
||||
private Jadx() {
|
||||
}
|
||||
|
||||
static {
|
||||
if (Consts.DEBUG) {
|
||||
LOG.info("debug enabled");
|
||||
public static List<IDexTreeVisitor> getPassesList(JadxArgs args) {
|
||||
switch (args.getDecompilationMode()) {
|
||||
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() {
|
||||
List<IDexTreeVisitor> passes = new ArrayList<>();
|
||||
passes.add(new SignatureProcessor());
|
||||
@@ -95,12 +98,8 @@ public class Jadx {
|
||||
return passes;
|
||||
}
|
||||
|
||||
public static List<IDexTreeVisitor> getPassesList(JadxArgs args) {
|
||||
if (args.isFallbackMode()) {
|
||||
return getFallbackPassesList();
|
||||
}
|
||||
public static List<IDexTreeVisitor> getRegionsModePasses(JadxArgs args) {
|
||||
List<IDexTreeVisitor> passes = new ArrayList<>();
|
||||
|
||||
// instructions IR
|
||||
passes.add(new CheckCode());
|
||||
if (args.isDebugInfo()) {
|
||||
@@ -132,6 +131,7 @@ public class Jadx {
|
||||
if (args.isDebugInfo()) {
|
||||
passes.add(new DebugInfoApplyVisitor());
|
||||
}
|
||||
passes.add(new FinishTypeInference());
|
||||
if (args.getUseKotlinMethodsForVarNames() != JadxArgs.UseKotlinMethodsForVarNames.DISABLE) {
|
||||
passes.add(new ProcessKotlinInternals());
|
||||
}
|
||||
@@ -178,7 +178,69 @@ public class Jadx {
|
||||
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() {
|
||||
if (version != null) {
|
||||
return version;
|
||||
}
|
||||
try {
|
||||
ClassLoader classLoader = Jadx.class.getClassLoader();
|
||||
if (classLoader != null) {
|
||||
@@ -188,6 +250,7 @@ public class Jadx {
|
||||
Manifest manifest = new Manifest(is);
|
||||
String ver = manifest.getMainAttributes().getValue("jadx-version");
|
||||
if (ver != null) {
|
||||
version = ver;
|
||||
return ver;
|
||||
}
|
||||
}
|
||||
@@ -196,6 +259,6 @@ public class Jadx {
|
||||
} catch (Exception e) {
|
||||
LOG.error("Can't get manifest file", e);
|
||||
}
|
||||
return "dev";
|
||||
return VERSION_DEV;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
package jadx.core;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.core.codegen.CodeGen;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.LoadStage;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.dex.visitors.DepthTraversal;
|
||||
import jadx.core.dex.visitors.IDexTreeVisitor;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
@@ -18,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_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
|
||||
private static ICodeInfo process(ClassNode cls, boolean codegen) {
|
||||
private ICodeInfo process(ClassNode cls, boolean codegen) {
|
||||
if (!codegen && cls.getState() == PROCESS_COMPLETE) {
|
||||
// nothing to do
|
||||
return null;
|
||||
@@ -34,17 +44,17 @@ public final class ProcessClass {
|
||||
if (cls.contains(AFlag.CLASS_DEEP_RELOAD)) {
|
||||
cls.remove(AFlag.CLASS_DEEP_RELOAD);
|
||||
cls.deepUnload();
|
||||
cls.root().runPreDecompileStageForClass(cls);
|
||||
cls.add(AFlag.CLASS_UNLOADED);
|
||||
}
|
||||
if (cls.contains(AFlag.CLASS_UNLOADED)) {
|
||||
cls.remove(AFlag.CLASS_UNLOADED);
|
||||
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 (cls.getState() == GENERATED_AND_UNLOADED) {
|
||||
// allow to run code generation again
|
||||
cls.setState(NOT_LOADED);
|
||||
}
|
||||
cls.setLoadStage(LoadStage.CODEGEN_STAGE);
|
||||
if (cls.contains(AFlag.RELOAD_AT_CODEGEN_STAGE)) {
|
||||
cls.remove(AFlag.RELOAD_AT_CODEGEN_STAGE);
|
||||
@@ -58,7 +68,7 @@ public final class ProcessClass {
|
||||
}
|
||||
if (cls.getState() == LOADED) {
|
||||
cls.setState(PROCESS_STARTED);
|
||||
for (IDexTreeVisitor visitor : cls.root().getPasses()) {
|
||||
for (IDexTreeVisitor visitor : passes) {
|
||||
DepthTraversal.visit(visitor, cls);
|
||||
}
|
||||
cls.setState(PROCESS_COMPLETE);
|
||||
@@ -83,12 +93,16 @@ public final class ProcessClass {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static ICodeInfo generateCode(ClassNode cls) {
|
||||
public ICodeInfo generateCode(ClassNode cls) {
|
||||
ClassNode topParentClass = cls.getTopParentClass();
|
||||
if (topParentClass != cls) {
|
||||
return generateCode(topParentClass);
|
||||
}
|
||||
try {
|
||||
if (cls.contains(AFlag.DONT_GENERATE)) {
|
||||
process(cls, false);
|
||||
return ICodeInfo.EMPTY;
|
||||
}
|
||||
for (ClassNode depCls : cls.getDependencies()) {
|
||||
process(depCls, false);
|
||||
}
|
||||
@@ -107,4 +121,19 @@ public final class ProcessClass {
|
||||
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.attributes.JadxAttrType;
|
||||
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.MethodParamsAttr;
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.dex.attributes.IAttributeNode;
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
@@ -48,7 +48,7 @@ public class AnnotationGen {
|
||||
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();
|
||||
if (n >= paramList.size()) {
|
||||
return;
|
||||
|
||||
@@ -285,7 +285,7 @@ public class ClassGen {
|
||||
|
||||
private boolean isInnerClassesPresents() {
|
||||
for (ClassNode innerCls : cls.getInnerClasses()) {
|
||||
if (!innerCls.contains(AFlag.ANONYMOUS_CLASS)) {
|
||||
if (!innerCls.contains(AType.ANONYMOUS_CLASS)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -356,7 +356,7 @@ public class ClassGen {
|
||||
badCode = false;
|
||||
}
|
||||
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);
|
||||
} else {
|
||||
mthGen = new MethodGen(this, mth);
|
||||
@@ -459,7 +459,7 @@ public class ClassGen {
|
||||
}
|
||||
if (f.getCls() != null) {
|
||||
code.add(' ');
|
||||
new ClassGen(f.getCls(), this).addClassBody(code);
|
||||
new ClassGen(f.getCls(), this).addClassBody(code, true);
|
||||
}
|
||||
if (it.hasNext()) {
|
||||
code.add(',');
|
||||
@@ -526,12 +526,42 @@ public class ClassGen {
|
||||
if (outerType != null) {
|
||||
useClass(code, outerType);
|
||||
code.add('.');
|
||||
// import not needed, force use short name
|
||||
useClassShortName(code, type.getObject());
|
||||
addInnerType(code, type);
|
||||
return;
|
||||
}
|
||||
|
||||
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();
|
||||
if (generics != null) {
|
||||
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) {
|
||||
ClassNode classNode = cls.root().resolveClass(classInfo);
|
||||
if (classNode != null) {
|
||||
@@ -590,6 +611,9 @@ public class ClassGen {
|
||||
return fullName;
|
||||
}
|
||||
String shortName = extClsInfo.getAliasShortName();
|
||||
if (useCls.equals(extClsInfo)) {
|
||||
return shortName;
|
||||
}
|
||||
if (extClsInfo.getPackage().equals("java.lang") && extClsInfo.getParentClass() == null) {
|
||||
return shortName;
|
||||
}
|
||||
@@ -599,6 +623,9 @@ public class ClassGen {
|
||||
if (extClsInfo.isInner()) {
|
||||
return expandInnerClassName(useCls, extClsInfo);
|
||||
}
|
||||
if (searchCollision(cls.root(), useCls, extClsInfo)) {
|
||||
return fullName;
|
||||
}
|
||||
if (isBothClassesInOneTopClass(useCls, extClsInfo)) {
|
||||
return shortName;
|
||||
}
|
||||
@@ -606,9 +633,6 @@ public class ClassGen {
|
||||
if (extClsInfo.getPackage().equals(useCls.getPackage()) && !extClsInfo.isInner()) {
|
||||
return shortName;
|
||||
}
|
||||
if (searchCollision(cls.root(), useCls, extClsInfo)) {
|
||||
return fullName;
|
||||
}
|
||||
// ignore classes from default package
|
||||
if (extClsInfo.isDefaultPackage()) {
|
||||
return shortName;
|
||||
|
||||
@@ -11,15 +11,15 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.CommentsLevel;
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.api.data.annotations.InsnCodeOffset;
|
||||
import jadx.api.data.annotations.VarDeclareRef;
|
||||
import jadx.api.data.annotations.VarRef;
|
||||
import jadx.api.metadata.annotations.InsnCodeOffset;
|
||||
import jadx.api.metadata.annotations.VarNode;
|
||||
import jadx.api.plugins.input.data.MethodHandleType;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
|
||||
import jadx.core.dex.attributes.nodes.GenericInfoAttr;
|
||||
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
||||
import jadx.core.dex.attributes.nodes.MethodReplaceAttr;
|
||||
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
@@ -50,6 +50,7 @@ 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.instructions.mods.TernaryInsn;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
@@ -107,7 +108,7 @@ public class InsnGen {
|
||||
if (arg.isRegister()) {
|
||||
RegisterArg reg = (RegisterArg) arg;
|
||||
if (code.isMetadataSupported()) {
|
||||
code.attachAnnotation(VarRef.get(mth, reg));
|
||||
code.attachAnnotation(VarNode.getRef(mth, reg));
|
||||
}
|
||||
code.add(mgen.getNameGen().useArg(reg));
|
||||
} else if (arg.isLiteral()) {
|
||||
@@ -160,10 +161,18 @@ public class InsnGen {
|
||||
}
|
||||
useType(code, codeVar.getType());
|
||||
code.add(' ');
|
||||
defVar(code, codeVar);
|
||||
}
|
||||
|
||||
/**
|
||||
* Variable definition without type, only var name
|
||||
*/
|
||||
private void defVar(ICodeWriter code, CodeVar codeVar) {
|
||||
String varName = mgen.getNameGen().assignArg(codeVar);
|
||||
if (code.isMetadataSupported()) {
|
||||
code.attachDefinition(VarDeclareRef.get(mth, codeVar));
|
||||
code.attachDefinition(VarNode.get(mth, codeVar));
|
||||
}
|
||||
code.add(mgen.getNameGen().assignArg(codeVar));
|
||||
code.add(varName);
|
||||
}
|
||||
|
||||
private String lit(LiteralArg arg) {
|
||||
@@ -172,7 +181,7 @@ public class InsnGen {
|
||||
|
||||
private void instanceField(ICodeWriter code, FieldInfo field, InsnArg arg) throws CodegenException {
|
||||
ClassNode pCls = mth.getParentClass();
|
||||
FieldNode fieldNode = pCls.root().deepResolveField(field);
|
||||
FieldNode fieldNode = pCls.root().resolveField(field);
|
||||
if (fieldNode != null) {
|
||||
FieldReplaceAttr replace = fieldNode.get(AType.FIELD_REPLACE);
|
||||
if (replace != null) {
|
||||
@@ -210,7 +219,7 @@ public class InsnGen {
|
||||
}
|
||||
code.add('.');
|
||||
}
|
||||
FieldNode fieldNode = clsGen.getClassNode().root().deepResolveField(field);
|
||||
FieldNode fieldNode = clsGen.getClassNode().root().resolveField(field);
|
||||
if (fieldNode != null) {
|
||||
code.attachAnnotation(fieldNode);
|
||||
}
|
||||
@@ -517,7 +526,7 @@ public class InsnGen {
|
||||
code.add(' ');
|
||||
code.add(ifInsn.getOp().getSymbol()).add(' ');
|
||||
addArg(code, insn.getArg(1));
|
||||
code.add(") goto ").add(MethodGen.getLabelName(ifInsn.getTarget()));
|
||||
code.add(") goto ").add(MethodGen.getLabelName(ifInsn));
|
||||
break;
|
||||
|
||||
case GOTO:
|
||||
@@ -538,13 +547,24 @@ public class InsnGen {
|
||||
code.add(") {");
|
||||
code.incIndent();
|
||||
int[] keys = sw.getKeys();
|
||||
int[] targets = sw.getTargets();
|
||||
for (int i = 0; i < keys.length; i++) {
|
||||
code.startLine("case ").add(Integer.toString(keys[i])).add(": goto ");
|
||||
code.add(MethodGen.getLabelName(targets[i])).add(';');
|
||||
int size = keys.length;
|
||||
BlockNode[] targetBlocks = sw.getTargetBlocks();
|
||||
if (targetBlocks != null) {
|
||||
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.startLine('}');
|
||||
break;
|
||||
@@ -682,19 +702,27 @@ public class InsnGen {
|
||||
throw new JadxRuntimeException("Constructor 'self' invoke must be removed!");
|
||||
}
|
||||
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()) {
|
||||
code.attachAnnotation(callMth);
|
||||
code.attachAnnotation(refMth);
|
||||
code.add("super");
|
||||
} else if (insn.isThis()) {
|
||||
code.attachAnnotation(callMth);
|
||||
code.attachAnnotation(refMth);
|
||||
code.add("this");
|
||||
} else {
|
||||
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)
|
||||
code.attachAnnotation(mth.root().resolveClass(insn.getCallMth().getDeclClass()));
|
||||
} else {
|
||||
code.attachAnnotation(callMth);
|
||||
code.attachAnnotation(refMth);
|
||||
}
|
||||
mgen.getClassGen().addClsName(code, insn.getClassType());
|
||||
GenericInfoAttr genericInfoAttr = insn.get(AType.GENERIC_INFO);
|
||||
@@ -719,13 +747,13 @@ public class InsnGen {
|
||||
|
||||
private void inlineAnonymousConstructor(ICodeWriter code, ClassNode cls, ConstructorInsn insn) throws CodegenException {
|
||||
if (this.mth.getParentClass() == cls) {
|
||||
cls.remove(AFlag.ANONYMOUS_CLASS);
|
||||
cls.remove(AType.ANONYMOUS_CLASS);
|
||||
cls.remove(AFlag.DONT_GENERATE);
|
||||
mth.getParentClass().getTopParentClass().add(AFlag.RESTART_CODEGEN);
|
||||
throw new CodegenException("Anonymous inner class unlimited recursion detected."
|
||||
+ " Convert class to inner: " + cls.getClassInfo().getFullName());
|
||||
}
|
||||
ArgType parent = cls.get(AType.ANONYMOUS_CLASS_BASE).getBaseType();
|
||||
ArgType parent = cls.get(AType.ANONYMOUS_CLASS).getBaseType();
|
||||
// hide empty anonymous constructors
|
||||
for (MethodNode ctor : cls.getMethods()) {
|
||||
if (ctor.contains(AFlag.ANONYMOUS_CONSTRUCTOR)
|
||||
@@ -764,7 +792,7 @@ public class InsnGen {
|
||||
return;
|
||||
}
|
||||
MethodInfo callMth = insn.getCallMth();
|
||||
MethodNode callMthNode = mth.root().deepResolveMethod(callMth);
|
||||
MethodNode callMthNode = mth.root().resolveMethod(callMth);
|
||||
|
||||
int k = 0;
|
||||
switch (type) {
|
||||
@@ -918,7 +946,7 @@ public class InsnGen {
|
||||
code.add(", ");
|
||||
}
|
||||
CodeVar argCodeVar = callArgs.get(i).getSVar().getCodeVar();
|
||||
code.add(nameGen.assignArg(argCodeVar));
|
||||
defVar(code, argCodeVar);
|
||||
}
|
||||
}
|
||||
// force set external arg names into call method args
|
||||
@@ -926,7 +954,8 @@ public class InsnGen {
|
||||
int startArg = customNode.getHandleType() == MethodHandleType.INVOKE_STATIC ? 0 : 1; // skip 'this' arg
|
||||
for (int i = startArg; i < extArgsCount; i++) {
|
||||
RegisterArg extArg = (RegisterArg) customNode.getArg(i);
|
||||
callArgs.get(i).setName(extArg.getName());
|
||||
RegisterArg callRegArg = callArgs.get(i);
|
||||
callRegArg.getSVar().setCodeVar(extArg.getSVar().getCodeVar());
|
||||
}
|
||||
code.add(" -> {");
|
||||
code.incIndent();
|
||||
|
||||
@@ -6,17 +6,19 @@ import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.CommentsLevel;
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.api.data.annotations.InsnCodeOffset;
|
||||
import jadx.api.data.annotations.VarDeclareRef;
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.api.metadata.annotations.InsnCodeOffset;
|
||||
import jadx.api.metadata.annotations.VarNode;
|
||||
import jadx.api.plugins.input.data.AccessFlags;
|
||||
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||
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.Jadx;
|
||||
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.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.trycatch.CatchAttr;
|
||||
import jadx.core.dex.trycatch.ExceptionHandler;
|
||||
import jadx.core.dex.visitors.DepthTraversal;
|
||||
import jadx.core.dex.visitors.IDexTreeVisitor;
|
||||
import jadx.core.utils.CodeGenUtils;
|
||||
import jadx.core.utils.InsnUtils;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
import jadx.core.utils.exceptions.JadxOverflowException;
|
||||
@@ -195,7 +198,7 @@ public class MethodGen {
|
||||
}
|
||||
|
||||
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;
|
||||
Iterator<RegisterArg> it = args.iterator();
|
||||
while (it.hasNext()) {
|
||||
@@ -238,10 +241,11 @@ public class MethodGen {
|
||||
classGen.useType(code, argType);
|
||||
}
|
||||
code.add(' ');
|
||||
if (code.isMetadataSupported() && ssaVar != null) {
|
||||
code.attachDefinition(VarDeclareRef.get(mth, var));
|
||||
String varName = nameGen.assignArg(var);
|
||||
if (code.isMetadataSupported() && ssaVar != null /* for fallback mode */) {
|
||||
code.attachDefinition(VarNode.get(mth, var));
|
||||
}
|
||||
code.add(nameGen.assignArg(var));
|
||||
code.add(varName);
|
||||
|
||||
i++;
|
||||
if (it.hasNext()) {
|
||||
@@ -251,12 +255,28 @@ public class MethodGen {
|
||||
}
|
||||
|
||||
public void addInstructions(ICodeWriter code) throws CodegenException {
|
||||
if (mth.root().getArgs().isFallbackMode()) {
|
||||
addFallbackMethodCode(code, FALLBACK_MODE);
|
||||
} else if (classGen.isFallbackMode()) {
|
||||
dumpInstructions(code);
|
||||
} else {
|
||||
addRegionInsns(code);
|
||||
JadxArgs args = mth.root().getArgs();
|
||||
switch (args.getDecompilationMode()) {
|
||||
case AUTO:
|
||||
if (classGen.isFallbackMode()) {
|
||||
// TODO: try simple mode first
|
||||
dumpInstructions(code);
|
||||
} else {
|
||||
addRegionInsns(code);
|
||||
}
|
||||
break;
|
||||
|
||||
case RESTRUCTURE:
|
||||
addRegionInsns(code);
|
||||
break;
|
||||
|
||||
case SIMPLE:
|
||||
addSimpleMethodCode(code);
|
||||
break;
|
||||
|
||||
case FALLBACK:
|
||||
addFallbackMethodCode(code, FALLBACK_MODE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -278,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) {
|
||||
if (mth.checkCommentsLevel(CommentsLevel.ERROR)) {
|
||||
code.startLine("/*");
|
||||
@@ -352,60 +425,81 @@ public class MethodGen {
|
||||
|
||||
public static void addFallbackInsns(ICodeWriter code, MethodNode mth, InsnNode[] insnArr, FallbackOption option) {
|
||||
int startIndent = code.getIndent();
|
||||
InsnGen insnGen = new InsnGen(getFallbackMethodGen(mth), true);
|
||||
MethodGen methodGen = getFallbackMethodGen(mth);
|
||||
InsnGen insnGen = new InsnGen(methodGen, true);
|
||||
InsnNode prevInsn = null;
|
||||
for (InsnNode insn : insnArr) {
|
||||
if (insn == null) {
|
||||
continue;
|
||||
}
|
||||
if (insn.contains(AType.JADX_ERROR)) {
|
||||
for (JadxError error : insn.getAll(AType.JADX_ERROR)) {
|
||||
code.startLine("// ").add(error.getError());
|
||||
}
|
||||
continue;
|
||||
methodGen.dumpInsn(code, insnGen, option, startIndent, prevInsn, insn);
|
||||
prevInsn = insn;
|
||||
}
|
||||
}
|
||||
|
||||
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.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();
|
||||
}
|
||||
if (insn.getType() == InsnType.NOP) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
boolean escapeComment = isCommentEscapeNeeded(insn, option);
|
||||
if (escapeComment) {
|
||||
code.decIndent();
|
||||
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();
|
||||
}
|
||||
addCatchComment(code, insn, true);
|
||||
CodeGenUtils.addCodeComments(code, mth, insn);
|
||||
} catch (Exception e) {
|
||||
LOG.debug("Error generate fallback instruction: ", e.getCause());
|
||||
code.setIndent(startIndent);
|
||||
code.startLine("// error: " + insn);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
CatchAttr catchAttr = insn.get(AType.EXC_CATCH);
|
||||
if (catchAttr != null) {
|
||||
code.add(" // " + catchAttr);
|
||||
}
|
||||
CodeGenUtils.addCodeComments(code, mth, insn);
|
||||
} catch (Exception e) {
|
||||
LOG.debug("Error generate fallback instruction: ", e.getCause());
|
||||
code.setIndent(startIndent);
|
||||
code.startLine("// error: " + insn);
|
||||
private void addCatchComment(ICodeWriter code, InsnNode insn, boolean raw) {
|
||||
CatchAttr catchAttr = insn.get(AType.EXC_CATCH);
|
||||
if (catchAttr == null) {
|
||||
return;
|
||||
}
|
||||
code.add(" // Catch:");
|
||||
for (ExceptionHandler handler : catchAttr.getHandlers()) {
|
||||
code.add(' ');
|
||||
classGen.useClass(code, handler.getArgType());
|
||||
code.add(" -> ");
|
||||
if (raw) {
|
||||
code.add(getLabelName(handler.getHandlerOffset()));
|
||||
} else {
|
||||
code.add(getLabelName(handler.getHandlerBlock()));
|
||||
}
|
||||
prevInsn = insn;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -448,7 +542,22 @@ public class MethodGen {
|
||||
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) {
|
||||
return "L_" + InsnUtils.formatOffset(offset);
|
||||
if (offset < 0) {
|
||||
return String.format("LB_%x", -offset);
|
||||
}
|
||||
return String.format("L%x", offset);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import java.util.Set;
|
||||
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.deobf.NameMapper;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
@@ -173,7 +174,7 @@ public class NameGen {
|
||||
|
||||
private String makeNameForType(ArgType type) {
|
||||
if (type.isPrimitive()) {
|
||||
return makeNameForPrimitive(type);
|
||||
return type.getPrimitiveType().getShortName().toLowerCase();
|
||||
}
|
||||
if (type.isArray()) {
|
||||
return makeNameForType(type.getArrayRootElement()) + "Arr";
|
||||
@@ -181,10 +182,6 @@ public class NameGen {
|
||||
return makeNameForObject(type);
|
||||
}
|
||||
|
||||
private static String makeNameForPrimitive(ArgType type) {
|
||||
return type.getPrimitiveType().getShortName().toLowerCase();
|
||||
}
|
||||
|
||||
private String makeNameForObject(ArgType type) {
|
||||
if (type.isGenericType()) {
|
||||
return StringUtils.escape(type.getObject().toLowerCase());
|
||||
@@ -194,23 +191,32 @@ public class NameGen {
|
||||
if (alias != null) {
|
||||
return alias;
|
||||
}
|
||||
ClassInfo extClsInfo = ClassInfo.fromType(mth.root(), type);
|
||||
String shortName = extClsInfo.getShortName();
|
||||
String vName = fromName(shortName);
|
||||
if (vName != null) {
|
||||
return vName;
|
||||
}
|
||||
if (shortName != null) {
|
||||
String lower = StringUtils.escape(shortName.toLowerCase());
|
||||
if (shortName.equals(lower)) {
|
||||
return lower + "Var";
|
||||
}
|
||||
return lower;
|
||||
}
|
||||
return makeNameForCheckedClass(ClassInfo.fromType(mth.root(), type));
|
||||
}
|
||||
return StringUtils.escape(type.toString());
|
||||
}
|
||||
|
||||
private String makeNameForCheckedClass(ClassInfo classInfo) {
|
||||
String shortName = classInfo.getAliasShortName();
|
||||
String vName = fromName(shortName);
|
||||
if (vName != null) {
|
||||
return vName;
|
||||
}
|
||||
String lower = StringUtils.escape(shortName.toLowerCase());
|
||||
if (shortName.equals(lower)) {
|
||||
return lower + "Var";
|
||||
}
|
||||
return lower;
|
||||
}
|
||||
|
||||
private String makeNameForClass(ClassInfo classInfo) {
|
||||
String alias = getAliasForObject(classInfo.getFullName());
|
||||
if (alias != null) {
|
||||
return alias;
|
||||
}
|
||||
return makeNameForCheckedClass(classInfo);
|
||||
}
|
||||
|
||||
private static String fromName(String name) {
|
||||
if (name == null || name.isEmpty()) {
|
||||
return null;
|
||||
@@ -241,7 +247,12 @@ public class NameGen {
|
||||
|
||||
case CONSTRUCTOR:
|
||||
ConstructorInsn co = (ConstructorInsn) insn;
|
||||
return makeNameForObject(co.getClassType().getType());
|
||||
MethodNode callMth = mth.root().getMethodUtils().resolveMethod(co);
|
||||
if (callMth != null && callMth.contains(AFlag.ANONYMOUS_CONSTRUCTOR)) {
|
||||
// don't use name of anonymous class
|
||||
return null;
|
||||
}
|
||||
return makeNameForClass(co.getClassType());
|
||||
|
||||
case ARRAY_LENGTH:
|
||||
return "length";
|
||||
@@ -267,18 +278,22 @@ public class NameGen {
|
||||
}
|
||||
|
||||
private String makeNameFromInvoke(MethodInfo callMth) {
|
||||
String name = callMth.getName();
|
||||
String name = callMth.getAlias();
|
||||
ClassInfo declClass = callMth.getDeclClass();
|
||||
if ("getInstance".equals(name)) {
|
||||
// e.g. Cipher.getInstance
|
||||
return makeNameForClass(declClass);
|
||||
}
|
||||
if (name.startsWith("get") || name.startsWith("set")) {
|
||||
return fromName(name.substring(3));
|
||||
}
|
||||
if ("iterator".equals(name)) {
|
||||
return "it";
|
||||
}
|
||||
ArgType declType = callMth.getDeclClass().getType();
|
||||
if ("toString".equals(name)) {
|
||||
return makeNameForType(declType);
|
||||
return makeNameForClass(declClass);
|
||||
}
|
||||
if ("forName".equals(name) && declType.equals(ArgType.CLASS)) {
|
||||
if ("forName".equals(name) && declClass.getType().equals(ArgType.CLASS)) {
|
||||
return OBJ_ALIAS.get(Consts.CLASS_CLASS);
|
||||
}
|
||||
if (name.startsWith("to")) {
|
||||
|
||||
@@ -10,8 +10,8 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.CommentsLevel;
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.api.data.annotations.InsnCodeOffset;
|
||||
import jadx.api.data.annotations.VarDeclareRef;
|
||||
import jadx.api.metadata.annotations.InsnCodeOffset;
|
||||
import jadx.api.metadata.annotations.VarNode;
|
||||
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||
import jadx.api.plugins.input.data.attributes.JadxAttrType;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
@@ -26,6 +26,7 @@ import jadx.core.dex.instructions.args.CodeVar;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.NamedArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.IBlock;
|
||||
@@ -346,11 +347,11 @@ public class RegionGen extends InsnGen {
|
||||
if (arg == null) {
|
||||
code.add("unknown"); // throwing exception is too late at this point
|
||||
} else if (arg instanceof RegisterArg) {
|
||||
CodeVar codeVar = ((RegisterArg) arg).getSVar().getCodeVar();
|
||||
SSAVar ssaVar = ((RegisterArg) arg).getSVar();
|
||||
if (code.isMetadataSupported()) {
|
||||
code.attachDefinition(VarDeclareRef.get(mth, codeVar));
|
||||
code.attachDefinition(VarNode.get(mth, ssaVar));
|
||||
}
|
||||
code.add(mgen.getNameGen().assignArg(codeVar));
|
||||
code.add(mgen.getNameGen().assignArg(ssaVar.getCodeVar()));
|
||||
} else if (arg instanceof NamedArg) {
|
||||
code.add(mgen.getNameGen().assignNamedArg((NamedArg) arg));
|
||||
} else {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -12,13 +12,13 @@ import com.google.gson.FieldNamingPolicy;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import jadx.api.CodePosition;
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.api.data.annotations.InsnCodeOffset;
|
||||
import jadx.api.impl.AnnotatedCodeWriter;
|
||||
import jadx.api.impl.SimpleCodeWriter;
|
||||
import jadx.api.metadata.ICodeMetadata;
|
||||
import jadx.api.metadata.annotations.InsnCodeOffset;
|
||||
import jadx.core.codegen.ClassGen;
|
||||
import jadx.core.codegen.MethodGen;
|
||||
import jadx.core.codegen.json.cls.JsonClass;
|
||||
@@ -180,24 +180,27 @@ public class JsonCodeGen {
|
||||
}
|
||||
|
||||
String[] lines = codeStr.split(ICodeWriter.NL);
|
||||
Map<Integer, Integer> lineMapping = code.getLineMapping();
|
||||
Map<CodePosition, Object> annotations = code.getAnnotations();
|
||||
Map<Integer, Integer> lineMapping = code.getCodeMetadata().getLineMapping();
|
||||
ICodeMetadata metadata = code.getCodeMetadata();
|
||||
long mthCodeOffset = mth.getMethodCodeOffset() + 16;
|
||||
|
||||
int linesCount = lines.length;
|
||||
List<JsonCodeLine> codeLines = new ArrayList<>(linesCount);
|
||||
int lineStartPos = 0;
|
||||
int newLineLen = ICodeWriter.NL.length();
|
||||
for (int i = 0; i < linesCount; i++) {
|
||||
String codeLine = lines[i];
|
||||
int line = i + 2;
|
||||
JsonCodeLine jsonCodeLine = new JsonCodeLine();
|
||||
jsonCodeLine.setCode(codeLine);
|
||||
jsonCodeLine.setSourceLine(lineMapping.get(line));
|
||||
Object obj = annotations.get(new CodePosition(line));
|
||||
Object obj = metadata.getAt(lineStartPos);
|
||||
if (obj instanceof InsnCodeOffset) {
|
||||
long offset = ((InsnCodeOffset) obj).getOffset();
|
||||
jsonCodeLine.setOffset("0x" + Long.toHexString(mthCodeOffset + offset * 2));
|
||||
}
|
||||
codeLines.add(jsonCodeLine);
|
||||
lineStartPos += codeLine.length() + newLineLen;
|
||||
}
|
||||
return codeLines;
|
||||
}
|
||||
|
||||
@@ -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.Map;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.api.args.DeobfuscationMapFileMode;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
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> mthPresetMap = new HashMap<>();
|
||||
|
||||
@Nullable
|
||||
public static DeobfPresets build(RootNode root) {
|
||||
Path deobfMapPath = getPathDeobfMapPath(root);
|
||||
if (deobfMapPath == null) {
|
||||
return null;
|
||||
if (root.getArgs().getDeobfuscationMapFileMode() != DeobfuscationMapFileMode.IGNORE) {
|
||||
LOG.debug("Deobfuscation map file set to: {}", deobfMapPath);
|
||||
}
|
||||
LOG.debug("Deobfuscation map file set to: {}", deobfMapPath);
|
||||
return new DeobfPresets(deobfMapPath);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Path getPathDeobfMapPath(RootNode root) {
|
||||
JadxArgs jadxArgs = root.getArgs();
|
||||
File deobfMapFile = jadxArgs.getDeobfuscationMapFile();
|
||||
if (deobfMapFile != null) {
|
||||
return deobfMapFile.toPath();
|
||||
}
|
||||
List<File> inputFiles = jadxArgs.getInputFiles();
|
||||
if (inputFiles.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
Path inputFilePath = inputFiles.get(0).toPath().toAbsolutePath();
|
||||
Path inputFilePath = jadxArgs.getInputFiles().get(0).toPath().toAbsolutePath();
|
||||
String baseName = FileUtils.getPathBaseName(inputFilePath);
|
||||
return inputFilePath.getParent().resolve(baseName + ".jobf");
|
||||
}
|
||||
@@ -70,9 +63,9 @@ public class DeobfPresets {
|
||||
/**
|
||||
* Loads deobfuscator presets
|
||||
*/
|
||||
public void load() {
|
||||
public boolean load() {
|
||||
if (!Files.exists(deobfMapFile)) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
LOG.info("Loading obfuscation map from: {}", deobfMapFile.toAbsolutePath());
|
||||
try {
|
||||
@@ -106,8 +99,10 @@ public class DeobfPresets {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} catch (Exception 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,
|
||||
StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Deobfuscation map file saved as: {}", deobfMapFile);
|
||||
}
|
||||
LOG.info("Deobfuscation map file saved as: {}", deobfMapFile);
|
||||
}
|
||||
|
||||
public String getForCls(ClassInfo cls) {
|
||||
|
||||
@@ -16,6 +16,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.api.args.DeobfuscationMapFileMode;
|
||||
import jadx.api.plugins.input.data.attributes.JadxAttrType;
|
||||
import jadx.api.plugins.input.data.attributes.types.SourceFileAttr;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
@@ -76,22 +77,25 @@ public class Deobfuscator {
|
||||
}
|
||||
|
||||
public void execute() {
|
||||
if (!args.isDeobfuscationForceSave()) {
|
||||
deobfPresets.load();
|
||||
for (Map.Entry<String, String> pkgEntry : deobfPresets.getPkgPresetMap().entrySet()) {
|
||||
addPackagePreset(pkgEntry.getKey(), pkgEntry.getValue());
|
||||
if (args.getDeobfuscationMapFileMode().shouldRead()) {
|
||||
if (deobfPresets.load()) {
|
||||
for (Map.Entry<String, String> pkgEntry : deobfPresets.getPkgPresetMap().entrySet()) {
|
||||
addPackagePreset(pkgEntry.getKey(), pkgEntry.getValue());
|
||||
}
|
||||
deobfPresets.getPkgPresetMap().clear(); // not needed anymore
|
||||
initIndexes();
|
||||
}
|
||||
deobfPresets.getPkgPresetMap().clear(); // not needed anymore
|
||||
initIndexes();
|
||||
}
|
||||
process();
|
||||
}
|
||||
|
||||
public void savePresets() {
|
||||
DeobfuscationMapFileMode mode = args.getDeobfuscationMapFileMode();
|
||||
if (!mode.shouldWrite()) {
|
||||
return;
|
||||
}
|
||||
Path deobfMapFile = deobfPresets.getDeobfMapFile();
|
||||
if (Files.exists(deobfMapFile) && !args.isDeobfuscationForceSave()) {
|
||||
LOG.info("Deobfuscation map file '{}' exists. Use command line option '--deobf-rewrite-cfg' to rewrite it",
|
||||
deobfMapFile.toAbsolutePath());
|
||||
if (mode == DeobfuscationMapFileMode.READ_OR_SAVE && Files.exists(deobfMapFile)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
@@ -112,16 +116,25 @@ public class Deobfuscator {
|
||||
deobfPresets.getPkgPresetMap().put(p.getName(), p.getAlias());
|
||||
}
|
||||
}
|
||||
for (DeobfClsInfo deobfClsInfo : clsMap.values()) {
|
||||
if (deobfClsInfo.getAlias() != null) {
|
||||
deobfPresets.getClsPresetMap().put(deobfClsInfo.getCls().getClassInfo().makeRawFullName(), deobfClsInfo.getAlias());
|
||||
for (ClassNode cls : root.getClasses()) {
|
||||
ClassInfo classInfo = cls.getClassInfo();
|
||||
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 pkgName = null;
|
||||
if (this.parseKotlinMetadata) {
|
||||
ClassInfo kotlinCls = KotlinMetadataUtils.getClassName(cls);
|
||||
ClsAliasPair kotlinCls = KotlinMetadataUtils.getClassAlias(cls);
|
||||
if (kotlinCls != null) {
|
||||
alias = prepareNameFull(kotlinCls.getShortName(), "C");
|
||||
pkgName = kotlinCls.getPackage();
|
||||
alias = kotlinCls.getName();
|
||||
pkgName = kotlinCls.getPkg();
|
||||
}
|
||||
}
|
||||
if (alias == null && this.useSourceNameAsAlias) {
|
||||
@@ -572,6 +585,7 @@ public class Deobfuscator {
|
||||
if (!pkg.hasAlias()) {
|
||||
String pkgName = pkg.getName();
|
||||
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.isRenamePrintable() && !NameMapper.isAllCharsPrintable(pkgName))) {
|
||||
String pkgAlias = String.format("p%03d%s", pkgIndex++, prepareNamePart(pkg.getName()));
|
||||
@@ -592,20 +606,6 @@ public class Deobfuscator {
|
||||
return NameMapper.removeInvalidCharsMiddle(name);
|
||||
}
|
||||
|
||||
private String prepareNameFull(String name, String prefix) {
|
||||
if (name.length() > maxLength) {
|
||||
return makeHashName(name, prefix);
|
||||
}
|
||||
String result = NameMapper.removeInvalidChars(name, prefix);
|
||||
if (result.isEmpty()) {
|
||||
return makeHashName(name, prefix);
|
||||
}
|
||||
if (NameMapper.isReserved(result)) {
|
||||
return prefix + result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static String makeHashName(String name, String invalidPrefix) {
|
||||
return invalidPrefix + 'x' + Integer.toHexString(name.hashCode());
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -35,7 +35,6 @@ public enum AFlag {
|
||||
SKIP_ARG, // skip argument in invoke call
|
||||
NO_SKIP_ARGS,
|
||||
ANONYMOUS_CONSTRUCTOR,
|
||||
ANONYMOUS_CLASS,
|
||||
|
||||
THIS,
|
||||
SUPER,
|
||||
@@ -77,9 +76,13 @@ public enum AFlag {
|
||||
INCONSISTENT_CODE, // warning about incorrect decompilation
|
||||
|
||||
REQUEST_IF_REGION_OPTIMIZE, // run if region visitor again
|
||||
REQUEST_CODE_SHRINK,
|
||||
RERUN_SSA_TRANSFORM,
|
||||
|
||||
METHOD_CANDIDATE_FOR_INLINE,
|
||||
USE_LINES_HINTS, // source lines info in methods can be trusted
|
||||
|
||||
DISABLE_BLOCKS_LOCK,
|
||||
|
||||
// Class processing flags
|
||||
RESTART_CODEGEN, // codegen must be executed again
|
||||
|
||||
@@ -2,7 +2,7 @@ package jadx.core.dex.attributes;
|
||||
|
||||
import jadx.api.plugins.input.data.attributes.IJadxAttrType;
|
||||
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
|
||||
import jadx.core.dex.attributes.nodes.AnonymousClassBaseAttr;
|
||||
import jadx.core.dex.attributes.nodes.AnonymousClassAttr;
|
||||
import jadx.core.dex.attributes.nodes.ClassTypeVarsAttr;
|
||||
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
|
||||
import jadx.core.dex.attributes.nodes.EdgeInsnAttr;
|
||||
@@ -11,6 +11,7 @@ import jadx.core.dex.attributes.nodes.EnumMapAttr;
|
||||
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
|
||||
import jadx.core.dex.attributes.nodes.ForceReturnAttr;
|
||||
import jadx.core.dex.attributes.nodes.GenericInfoAttr;
|
||||
import jadx.core.dex.attributes.nodes.InlinedAttr;
|
||||
import jadx.core.dex.attributes.nodes.JadxCommentsAttr;
|
||||
import jadx.core.dex.attributes.nodes.JadxError;
|
||||
import jadx.core.dex.attributes.nodes.JumpInfo;
|
||||
@@ -20,11 +21,13 @@ 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.MethodOverrideAttr;
|
||||
import jadx.core.dex.attributes.nodes.MethodReplaceAttr;
|
||||
import jadx.core.dex.attributes.nodes.MethodTypeVarsAttr;
|
||||
import jadx.core.dex.attributes.nodes.PhiListAttr;
|
||||
import jadx.core.dex.attributes.nodes.RegDebugInfoAttr;
|
||||
import jadx.core.dex.attributes.nodes.RenameReasonAttr;
|
||||
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
|
||||
import jadx.core.dex.attributes.nodes.SpecialEdgeAttr;
|
||||
import jadx.core.dex.attributes.nodes.TmpEdgeAttr;
|
||||
import jadx.core.dex.nodes.IMethodDetails;
|
||||
import jadx.core.dex.trycatch.CatchAttr;
|
||||
@@ -53,7 +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<EnumMapAttr> ENUM_MAP = new AType<>();
|
||||
public static final AType<ClassTypeVarsAttr> CLASS_TYPE_VARS = new AType<>();
|
||||
public static final AType<AnonymousClassBaseAttr> ANONYMOUS_CLASS_BASE = new AType<>();
|
||||
public static final AType<AnonymousClassAttr> ANONYMOUS_CLASS = new AType<>();
|
||||
public static final AType<InlinedAttr> INLINED = new AType<>();
|
||||
|
||||
// field
|
||||
public static final AType<FieldInitInsnAttr> FIELD_INIT_INSN = new AType<>();
|
||||
@@ -62,11 +66,12 @@ public final class AType<T extends IJadxAttribute> implements IJadxAttrType<T> {
|
||||
// method
|
||||
public static final AType<LocalVarsDebugInfoAttr> LOCAL_VARS_DEBUG_INFO = new AType<>();
|
||||
public static final AType<MethodInlineAttr> METHOD_INLINE = new AType<>();
|
||||
public static final AType<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<MethodOverrideAttr> METHOD_OVERRIDE = new AType<>();
|
||||
public static final AType<MethodTypeVarsAttr> METHOD_TYPE_VARS = new AType<>();
|
||||
public static final AType<AttrList<TryCatchBlockAttr>> TRY_BLOCKS_LIST = new AType<>();
|
||||
public static final AType<MethodBridgeAttr> BRIDGED_BY = new AType<>();
|
||||
|
||||
// region
|
||||
public static final AType<DeclareVariablesAttr> DECLARE_VARIABLES = new AType<>();
|
||||
@@ -76,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<AttrList<LoopInfo>> LOOP = 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<TryCatchBlockAttr> TRY_BLOCK = new AType<>();
|
||||
|
||||
|
||||
@@ -5,10 +5,6 @@ public interface ILineAttributeNode {
|
||||
|
||||
void setSourceLine(int sourceLine);
|
||||
|
||||
int getDecompiledLine();
|
||||
|
||||
void setDecompiledLine(int line);
|
||||
|
||||
int getDefPosition();
|
||||
|
||||
void setDefPosition(int pos);
|
||||
|
||||
@@ -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 + '}';
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
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;
|
||||
|
||||
public class AnonymousClassBaseAttr extends PinnedAttribute {
|
||||
|
||||
private final ArgType baseType;
|
||||
|
||||
public AnonymousClassBaseAttr(ArgType baseType) {
|
||||
this.baseType = baseType;
|
||||
}
|
||||
|
||||
public ArgType getBaseType() {
|
||||
return baseType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AType<AnonymousClassBaseAttr> getAttrType() {
|
||||
return AType.ANONYMOUS_CLASS_BASE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "AnonymousClassBaseAttr{" + 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;
|
||||
}
|
||||
}
|
||||
@@ -50,11 +50,7 @@ public class JadxError implements Comparable<JadxError> {
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder str = new StringBuilder();
|
||||
str.append("JadxError: ");
|
||||
if (error != null) {
|
||||
str.append(error);
|
||||
str.append(' ');
|
||||
}
|
||||
str.append("JadxError: ").append(error).append(' ');
|
||||
if (cause != null) {
|
||||
str.append(cause.getClass());
|
||||
str.append(':');
|
||||
|
||||
@@ -7,21 +7,11 @@ public abstract class LineAttrNode extends AttrNode implements ILineAttributeNod
|
||||
|
||||
private int sourceLine;
|
||||
|
||||
private int decompiledLine;
|
||||
|
||||
// the position exactly where a node declared at in decompiled java code.
|
||||
/**
|
||||
* Position where a node declared at in decompiled code
|
||||
*/
|
||||
private int defPosition;
|
||||
|
||||
@Override
|
||||
public int getDefPosition() {
|
||||
return this.defPosition;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDefPosition(int defPosition) {
|
||||
this.defPosition = defPosition;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSourceLine() {
|
||||
return sourceLine;
|
||||
@@ -33,13 +23,13 @@ public abstract class LineAttrNode extends AttrNode implements ILineAttributeNod
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDecompiledLine() {
|
||||
return decompiledLine;
|
||||
public int getDefPosition() {
|
||||
return this.defPosition;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDecompiledLine(int decompiledLine) {
|
||||
this.decompiledLine = decompiledLine;
|
||||
public void setDefPosition(int defPosition) {
|
||||
this.defPosition = defPosition;
|
||||
}
|
||||
|
||||
public void addSourceLineFrom(LineAttrNode lineAttrNode) {
|
||||
@@ -50,6 +40,6 @@ public abstract class LineAttrNode extends AttrNode implements ILineAttributeNod
|
||||
|
||||
public void copyLines(LineAttrNode lineAttrNode) {
|
||||
setSourceLine(lineAttrNode.getSourceLine());
|
||||
setDecompiledLine(lineAttrNode.getDecompiledLine());
|
||||
setDefPosition(lineAttrNode.getDefPosition());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package jadx.core.dex.attributes.nodes;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
@@ -20,10 +19,10 @@ public class LoopInfo {
|
||||
private int id;
|
||||
private LoopInfo parentLoop;
|
||||
|
||||
public LoopInfo(BlockNode start, BlockNode end) {
|
||||
public LoopInfo(BlockNode start, BlockNode end, Set<BlockNode> loopBlocks) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
this.loopBlocks = Collections.unmodifiableSet(BlockUtils.getAllPathsBlocks(start, end));
|
||||
this.loopBlocks = loopBlocks;
|
||||
}
|
||||
|
||||
public BlockNode getStart() {
|
||||
|
||||
@@ -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 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;
|
||||
|
||||
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) {
|
||||
this.parentClass = null;
|
||||
splitAndApplyNames(root, type, false);
|
||||
this.parentClass = null;
|
||||
}
|
||||
|
||||
public void convertToInner(ClassNode parent) {
|
||||
this.parentClass = parent.getClassInfo();
|
||||
splitAndApplyNames(parent.root(), type, true);
|
||||
this.parentClass = parent.getClassInfo();
|
||||
}
|
||||
|
||||
public void updateNames(RootNode root) {
|
||||
|
||||
@@ -176,6 +176,9 @@ public class ConstStorage {
|
||||
|
||||
@Nullable
|
||||
public FieldNode getConstFieldByLiteralArg(ClassNode cls, LiteralArg arg) {
|
||||
if (!replaceEnabled) {
|
||||
return null;
|
||||
}
|
||||
PrimitiveType type = arg.getType().getPrimitiveType();
|
||||
if (type == null) {
|
||||
return null;
|
||||
|
||||
@@ -5,6 +5,7 @@ import java.util.List;
|
||||
import jadx.api.plugins.input.insns.InsnData;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.LiteralArg;
|
||||
import jadx.core.dex.instructions.args.PrimitiveType;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
@@ -70,6 +71,15 @@ public class IfNode extends GotoNode {
|
||||
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) {
|
||||
this.op = op;
|
||||
setArg(0, arg1);
|
||||
|
||||
@@ -389,11 +389,11 @@ public class InsnDecoder {
|
||||
return arrLenInsn;
|
||||
|
||||
case AGET:
|
||||
return arrayGet(insn, ArgType.INT_FLOAT);
|
||||
return arrayGet(insn, ArgType.INT_FLOAT, ArgType.NARROW_NUMBERS_NO_BOOL);
|
||||
case AGET_BOOLEAN:
|
||||
return arrayGet(insn, ArgType.BOOLEAN);
|
||||
case AGET_BYTE:
|
||||
return arrayGet(insn, ArgType.BYTE);
|
||||
return arrayGet(insn, ArgType.BYTE, ArgType.NARROW_INTEGRAL);
|
||||
case AGET_BYTE_BOOLEAN:
|
||||
return arrayGet(insn, ArgType.BYTE_BOOLEAN);
|
||||
case AGET_CHAR:
|
||||
@@ -406,7 +406,7 @@ public class InsnDecoder {
|
||||
return arrayGet(insn, ArgType.UNKNOWN_OBJECT);
|
||||
|
||||
case APUT:
|
||||
return arrayPut(insn, ArgType.INT_FLOAT);
|
||||
return arrayPut(insn, ArgType.INT_FLOAT, ArgType.NARROW_NUMBERS_NO_BOOL);
|
||||
case APUT_BOOLEAN:
|
||||
return arrayPut(insn, ArgType.BOOLEAN);
|
||||
case APUT_BYTE:
|
||||
@@ -607,16 +607,24 @@ public class InsnDecoder {
|
||||
}
|
||||
|
||||
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);
|
||||
inode.setResult(InsnArg.typeImmutableIfKnownReg(insn, 0, argType));
|
||||
inode.addArg(InsnArg.typeImmutableIfKnownReg(insn, 1, ArgType.array(argType)));
|
||||
inode.setResult(InsnArg.typeImmutableIfKnownReg(insn, 0, resType));
|
||||
inode.addArg(InsnArg.typeImmutableIfKnownReg(insn, 1, ArgType.array(arrElemType)));
|
||||
inode.addArg(InsnArg.reg(insn, 2, ArgType.NARROW_INTEGRAL));
|
||||
return inode;
|
||||
}
|
||||
|
||||
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);
|
||||
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.typeImmutableIfKnownReg(insn, 0, argType));
|
||||
return inode;
|
||||
|
||||
@@ -12,9 +12,9 @@ import org.jetbrains.annotations.TestOnly;
|
||||
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.dex.visitors.typeinference.TypeCompareEnum;
|
||||
import jadx.core.utils.ListUtils;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
@@ -874,28 +874,37 @@ public abstract class ArgType {
|
||||
}
|
||||
|
||||
public static ArgType tryToResolveClassAlias(RootNode root, ArgType type) {
|
||||
if (!type.isObject() || type.isGenericType()) {
|
||||
if (type.isGenericType()) {
|
||||
return type;
|
||||
}
|
||||
if (type.isArray()) {
|
||||
ArgType rootType = type.getArrayRootElement();
|
||||
ArgType aliasType = tryToResolveClassAlias(root, rootType);
|
||||
if (aliasType == rootType) {
|
||||
return type;
|
||||
}
|
||||
return ArgType.array(aliasType, type.getArrayDimension());
|
||||
}
|
||||
if (type.isObject()) {
|
||||
ArgType wildcardType = type.getWildcardType();
|
||||
if (wildcardType != null) {
|
||||
return new WildcardType(tryToResolveClassAlias(root, wildcardType), type.getWildcardBound());
|
||||
}
|
||||
ClassInfo clsInfo = ClassInfo.fromName(root, type.getObject());
|
||||
ArgType baseType = clsInfo.hasAlias() ? ArgType.object(clsInfo.getAliasFullName()) : type;
|
||||
if (!type.isGeneric()) {
|
||||
return baseType;
|
||||
}
|
||||
List<ArgType> genericTypes = type.getGenericTypes();
|
||||
if (genericTypes != null) {
|
||||
return new GenericObject(baseType.getObject(), tryToResolveClassAlias(root, genericTypes));
|
||||
}
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
ClassNode cls = root.resolveClass(type);
|
||||
if (cls == null) {
|
||||
return type;
|
||||
}
|
||||
ClassInfo clsInfo = cls.getClassInfo();
|
||||
if (!clsInfo.hasAlias()) {
|
||||
return type;
|
||||
}
|
||||
String aliasFullName = clsInfo.getAliasFullName();
|
||||
if (type.isGeneric()) {
|
||||
if (type instanceof GenericObject) {
|
||||
return new GenericObject(aliasFullName, type.getGenericTypes());
|
||||
}
|
||||
if (type instanceof WildcardType) {
|
||||
return new WildcardType(ArgType.object(aliasFullName), type.getWildcardBound());
|
||||
}
|
||||
}
|
||||
return ArgType.object(aliasFullName);
|
||||
public static List<ArgType> tryToResolveClassAlias(RootNode root, List<ArgType> types) {
|
||||
return ListUtils.map(types, t -> tryToResolveClassAlias(root, t));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -4,7 +4,7 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.api.data.annotations.VarRef;
|
||||
import jadx.api.metadata.annotations.VarNode;
|
||||
|
||||
public class CodeVar {
|
||||
private String name;
|
||||
@@ -15,7 +15,7 @@ public class CodeVar {
|
||||
private boolean isThis;
|
||||
private boolean isDeclared;
|
||||
|
||||
private VarRef cachedVarRef; // set and used at codegen stage
|
||||
private VarNode cachedVarNode; // set and used at codegen stage
|
||||
|
||||
public static CodeVar fromMthArg(RegisterArg mthArg, boolean linkRegister) {
|
||||
CodeVar var = new CodeVar();
|
||||
@@ -94,12 +94,12 @@ public class CodeVar {
|
||||
isDeclared = declared;
|
||||
}
|
||||
|
||||
public VarRef getCachedVarRef() {
|
||||
return cachedVarRef;
|
||||
public VarNode getCachedVarNode() {
|
||||
return cachedVarNode;
|
||||
}
|
||||
|
||||
public void setCachedVarRef(VarRef cachedVarRef) {
|
||||
this.cachedVarRef = cachedVarRef;
|
||||
public void setCachedVarNode(VarNode varNode) {
|
||||
this.cachedVarNode = varNode;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -192,19 +192,13 @@ public class SSAVar {
|
||||
return usedInPhi;
|
||||
}
|
||||
|
||||
public boolean isUsedInPhi() {
|
||||
return usedInPhi != null && !usedInPhi.isEmpty();
|
||||
public boolean isAssignInPhi() {
|
||||
InsnNode assignInsn = getAssignInsn();
|
||||
return assignInsn != null && assignInsn.getType() == InsnType.PHI;
|
||||
}
|
||||
|
||||
public int getVariableUseCount() {
|
||||
int count = useList.size();
|
||||
if (usedInPhi == null) {
|
||||
return count;
|
||||
}
|
||||
for (PhiInsn phiInsn : usedInPhi) {
|
||||
count += phiInsn.getResult().getSVar().getUseCount();
|
||||
}
|
||||
return count;
|
||||
public boolean isUsedInPhi() {
|
||||
return usedInPhi != null && !usedInPhi.isEmpty();
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
|
||||
@@ -16,9 +16,11 @@ import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.DecompilationMode;
|
||||
import jadx.api.ICodeCache;
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.api.plugins.input.data.IClassData;
|
||||
import jadx.api.plugins.input.data.IFieldData;
|
||||
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.ProcessClass;
|
||||
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.info.AccessInfo;
|
||||
import jadx.core.dex.info.AccessInfo.AFType;
|
||||
@@ -42,6 +46,7 @@ import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.LiteralArg;
|
||||
import jadx.core.dex.nodes.utils.TypeUtils;
|
||||
import jadx.core.utils.ListUtils;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
@@ -302,6 +307,26 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
||||
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() {
|
||||
return decompile(true);
|
||||
}
|
||||
@@ -349,12 +374,25 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
||||
String clsRawName = getRawName();
|
||||
if (searchInCache) {
|
||||
ICodeInfo code = codeCache.get(clsRawName);
|
||||
if (code != null && code != ICodeInfo.EMPTY) {
|
||||
if (code != ICodeInfo.EMPTY) {
|
||||
return code;
|
||||
}
|
||||
}
|
||||
ICodeInfo codeInfo = ProcessClass.generateCode(this);
|
||||
codeCache.add(clsRawName, codeInfo);
|
||||
ICodeInfo codeInfo = root.getProcessClasses().generateCode(this);
|
||||
if (codeInfo != ICodeInfo.EMPTY) {
|
||||
codeCache.add(clsRawName, 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;
|
||||
}
|
||||
|
||||
@@ -598,6 +636,7 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
||||
if (inlinedClasses.isEmpty()) {
|
||||
inlinedClasses = new ArrayList<>(5);
|
||||
}
|
||||
cls.addAttr(new InlinedAttr(this));
|
||||
inlinedClasses.add(cls);
|
||||
}
|
||||
|
||||
@@ -608,7 +647,7 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
||||
}
|
||||
|
||||
public boolean isAnonymous() {
|
||||
return contains(AFlag.ANONYMOUS_CLASS);
|
||||
return contains(AType.ANONYMOUS_CLASS);
|
||||
}
|
||||
|
||||
public boolean isInner() {
|
||||
@@ -739,6 +778,10 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
||||
this.dependencies = dependencies;
|
||||
}
|
||||
|
||||
public void removeDependency(ClassNode dep) {
|
||||
this.dependencies = ListUtils.safeRemoveAndTrim(this.dependencies, dep);
|
||||
}
|
||||
|
||||
public List<ClassNode> getCodegenDeps() {
|
||||
return codegenDeps;
|
||||
}
|
||||
@@ -747,6 +790,14 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
||||
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() {
|
||||
return useIn;
|
||||
}
|
||||
@@ -768,6 +819,11 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
||||
return clsData == null ? "synthetic" : clsData.getInputFileName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnnType getAnnType() {
|
||||
return AnnType.CLASS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return clsInfo.hashCode();
|
||||
|
||||
@@ -9,6 +9,7 @@ import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.info.AccessInfo.AFType;
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.utils.ListUtils;
|
||||
|
||||
public class FieldNode extends NotificationAttrNode implements ICodeNode {
|
||||
|
||||
@@ -80,6 +81,10 @@ public class FieldNode extends NotificationAttrNode implements ICodeNode {
|
||||
this.useIn = useIn;
|
||||
}
|
||||
|
||||
public synchronized void addUseIn(MethodNode mth) {
|
||||
useIn = ListUtils.safeAdd(useIn, mth);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String typeName() {
|
||||
return "field";
|
||||
@@ -95,6 +100,11 @@ public class FieldNode extends NotificationAttrNode implements ICodeNode {
|
||||
return parentClass.root();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnnType getAnnType() {
|
||||
return AnnType.FIELD;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return fieldInfo.hashCode();
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package jadx.core.dex.nodes;
|
||||
|
||||
import jadx.api.metadata.ICodeNodeRef;
|
||||
import jadx.core.dex.attributes.IAttributeNode;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
|
||||
public interface ICodeNode extends IDexNode, IAttributeNode, IUsageInfoNode {
|
||||
public interface ICodeNode extends IDexNode, IAttributeNode, IUsageInfoNode, ICodeNodeRef {
|
||||
AccessInfo getAccessFlags();
|
||||
|
||||
void setAccessFlags(AccessInfo newAccessFlags);
|
||||
|
||||
@@ -300,20 +300,6 @@ public class InsnNode extends LineAttrNode {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 this instruction and all inner (wrapped) instructions
|
||||
* To terminate visiting return non-null value
|
||||
@@ -336,6 +322,40 @@ public class InsnNode extends LineAttrNode {
|
||||
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.
|
||||
*/
|
||||
|
||||
@@ -99,9 +99,6 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
|
||||
@Override
|
||||
public void unload() {
|
||||
loaded = false;
|
||||
if (noCode) {
|
||||
return;
|
||||
}
|
||||
// don't unload retType, argTypes, typeParameters
|
||||
thisArg = null;
|
||||
argsList = null;
|
||||
@@ -339,6 +336,14 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
|
||||
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) {
|
||||
if (loops.isEmpty()) {
|
||||
loops = new ArrayList<>(5);
|
||||
@@ -587,6 +592,11 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
|
||||
this.useIn = useIn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnnType getAnnType() {
|
||||
return AnnType.METHOD;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return mthInfo.hashCode();
|
||||
|
||||
@@ -22,6 +22,7 @@ import jadx.api.data.ICodeData;
|
||||
import jadx.api.plugins.input.data.IClassData;
|
||||
import jadx.api.plugins.input.data.ILoadResult;
|
||||
import jadx.core.Jadx;
|
||||
import jadx.core.ProcessClass;
|
||||
import jadx.core.clsp.ClspGraph;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.ConstStorage;
|
||||
@@ -51,9 +52,9 @@ public class RootNode {
|
||||
|
||||
private final JadxArgs args;
|
||||
private final List<IDexTreeVisitor> preDecompilePasses;
|
||||
private final List<IDexTreeVisitor> passes;
|
||||
private final List<ICodeDataUpdateListener> codeDataUpdateListeners = new ArrayList<>();
|
||||
|
||||
private final ProcessClass processClasses;
|
||||
private final ErrorsCounter errorsCounter = new ErrorsCounter();
|
||||
private final StringUtils stringUtils;
|
||||
private final ConstStorage constValues;
|
||||
@@ -76,7 +77,7 @@ public class RootNode {
|
||||
public RootNode(JadxArgs args) {
|
||||
this.args = args;
|
||||
this.preDecompilePasses = Jadx.getPreDecompilePassesList();
|
||||
this.passes = Jadx.getPassesList(args);
|
||||
this.processClasses = new ProcessClass(this.getArgs());
|
||||
this.stringUtils = new StringUtils(args);
|
||||
this.constValues = new ConstStorage(args);
|
||||
this.typeUpdate = new TypeUpdate(this);
|
||||
@@ -114,21 +115,25 @@ public class RootNode {
|
||||
}
|
||||
|
||||
private void addDummyClass(IClassData classData, Exception exc) {
|
||||
String typeStr = classData.getType();
|
||||
String name = null;
|
||||
try {
|
||||
ClassInfo clsInfo = ClassInfo.fromName(this, typeStr);
|
||||
if (clsInfo != null) {
|
||||
name = clsInfo.getShortName();
|
||||
String typeStr = classData.getType();
|
||||
String name = null;
|
||||
try {
|
||||
ClassInfo clsInfo = ClassInfo.fromName(this, typeStr);
|
||||
if (clsInfo != null) {
|
||||
name = clsInfo.getShortName();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to get name for class with type {}", typeStr, e);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to get name for class with type {}", typeStr, e);
|
||||
if (name == null || name.isEmpty()) {
|
||||
name = "CLASS_" + typeStr;
|
||||
}
|
||||
ClassNode clsNode = ClassNode.addSyntheticClass(this, name, classData.getAccessFlags());
|
||||
ErrorsCounter.error(clsNode, "Load error", exc);
|
||||
} catch (Exception innerExc) {
|
||||
LOG.error("Failed to load class from file: {}", classData.getInputFileName(), exc);
|
||||
}
|
||||
if (name == null || name.isEmpty()) {
|
||||
name = "CLASS_" + typeStr;
|
||||
}
|
||||
ClassNode clsNode = ClassNode.addSyntheticClass(this, name, classData.getAccessFlags());
|
||||
ErrorsCounter.error(clsNode, "Load error", exc);
|
||||
}
|
||||
|
||||
private static void markDuplicatedClasses(List<ClassNode> classes) {
|
||||
@@ -378,15 +383,6 @@ public class RootNode {
|
||||
|
||||
@Nullable
|
||||
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());
|
||||
if (cls == null) {
|
||||
return null;
|
||||
@@ -430,19 +426,14 @@ public class RootNode {
|
||||
|
||||
@Nullable
|
||||
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());
|
||||
if (cls == null) {
|
||||
return null;
|
||||
}
|
||||
FieldNode fieldNode = cls.searchField(field);
|
||||
if (fieldNode != null) {
|
||||
return fieldNode;
|
||||
}
|
||||
return deepResolveField(cls, field);
|
||||
}
|
||||
|
||||
@@ -474,18 +465,16 @@ public class RootNode {
|
||||
return null;
|
||||
}
|
||||
|
||||
public ProcessClass getProcessClasses() {
|
||||
return processClasses;
|
||||
}
|
||||
|
||||
public List<IDexTreeVisitor> getPasses() {
|
||||
return passes;
|
||||
return processClasses.getPasses();
|
||||
}
|
||||
|
||||
public void initPasses() {
|
||||
for (IDexTreeVisitor pass : passes) {
|
||||
try {
|
||||
pass.init(this);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Visitor init failed: {}", pass.getClass().getSimpleName(), e);
|
||||
}
|
||||
}
|
||||
processClasses.initPasses(this);
|
||||
}
|
||||
|
||||
public ICodeWriter makeCodeWriter() {
|
||||
|
||||
@@ -2,7 +2,6 @@ package jadx.core.dex.nodes.parser;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -197,6 +196,8 @@ public class SignatureParser {
|
||||
String obj = slice();
|
||||
if (!innerType) {
|
||||
obj += ';';
|
||||
} else {
|
||||
obj = obj.replace('/', '.');
|
||||
}
|
||||
List<ArgType> typeVars = consumeGenericArgs();
|
||||
consume('>');
|
||||
@@ -227,7 +228,7 @@ public class SignatureParser {
|
||||
}
|
||||
|
||||
private List<ArgType> consumeGenericArgs() {
|
||||
List<ArgType> list = new LinkedList<>();
|
||||
List<ArgType> list = new ArrayList<>();
|
||||
ArgType type;
|
||||
do {
|
||||
if (lookAhead('*')) {
|
||||
|
||||
@@ -38,13 +38,22 @@ public class MethodUtils {
|
||||
|
||||
@Nullable
|
||||
public IMethodDetails getMethodDetails(MethodInfo callMth) {
|
||||
MethodNode mthNode = root.deepResolveMethod(callMth);
|
||||
MethodNode mthNode = root.resolveMethod(callMth);
|
||||
if (mthNode != null) {
|
||||
return mthNode;
|
||||
}
|
||||
return root.getClsp().getMethodDetails(callMth);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public MethodNode resolveMethod(BaseInvokeNode invokeNode) {
|
||||
IMethodDetails methodDetails = getMethodDetails(invokeNode);
|
||||
if (methodDetails instanceof MethodNode) {
|
||||
return ((MethodNode) methodDetails);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search methods with same name and args count in class hierarchy starting from {@code startCls}
|
||||
* Beware {@code startCls} can be different from {@code mthInfo.getDeclClass()}
|
||||
|
||||
@@ -63,7 +63,7 @@ public class TypeUtils {
|
||||
|
||||
public ArgType expandTypeVariables(ClassNode cls, ArgType type) {
|
||||
if (type.containsTypeVariable()) {
|
||||
expandTypeVar(cls, type, cls.getGenericTypeParameters());
|
||||
expandTypeVar(cls, type, getKnownTypeVarsAtClass(cls));
|
||||
}
|
||||
return type;
|
||||
}
|
||||
@@ -115,11 +115,18 @@ public class TypeUtils {
|
||||
return varsAttr.getTypeVars();
|
||||
}
|
||||
|
||||
private static Set<ArgType> collectKnownTypeVarsAtMethod(MethodNode mth) {
|
||||
ClassNode declCls = mth.getParentClass();
|
||||
Set<ArgType> typeVars = new HashSet<>(declCls.getGenericTypeParameters());
|
||||
declCls.visitParentClasses(parent -> typeVars.addAll(parent.getGenericTypeParameters()));
|
||||
private static Collection<ArgType> getKnownTypeVarsAtClass(ClassNode cls) {
|
||||
if (cls.isInner()) {
|
||||
Set<ArgType> typeVars = new HashSet<>(cls.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());
|
||||
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.IfOp;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.LiteralArg;
|
||||
|
||||
public final class Compare {
|
||||
private final IfNode insn;
|
||||
@@ -35,13 +34,8 @@ public final class Compare {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change 'a != false' to 'a == true'
|
||||
*/
|
||||
public void normalize() {
|
||||
if (getOp() == IfOp.NE && getB().isFalse()) {
|
||||
insn.changeCondition(IfOp.EQ, getA(), LiteralArg.litTrue());
|
||||
}
|
||||
insn.normalize();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user