Compare commits
45 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 18070eb7a6 | |||
| 8486891728 | |||
| 4679172d4f | |||
| 92a6c333d8 | |||
| 358adbdd65 | |||
| 65f7c80222 | |||
| d2e6bb236e | |||
| eaeb114258 | |||
| 1533b7fe6e | |||
| a2cd8e1ead | |||
| 4edb512121 | |||
| 702b88228c | |||
| 14fd88b2f8 | |||
| 20657e8bb5 | |||
| 93d3194e3b | |||
| 39331d9120 | |||
| b4fa6644bc | |||
| 0b2e2ed034 | |||
| 81231206f3 | |||
| 49d0e76272 | |||
| 0809993b37 | |||
| 0c3afcc24c | |||
| d6c851eed4 | |||
| dcf4a7c4e3 | |||
| 9ba07b986b | |||
| e6b6b93cbb | |||
| fcd58ae76f | |||
| df380dea27 | |||
| 9d88592391 | |||
| c906c11b0f | |||
| 4fbc56cdb0 | |||
| 98c0416b20 | |||
| fa41874e30 | |||
| 2aa6c99c90 | |||
| 5f60c0f1bb | |||
| cb741db623 | |||
| 1df217c4a0 | |||
| 81f209ba9e | |||
| 34a31aa7df | |||
| 5099e02c9b | |||
| f364b39b29 | |||
| 4cd4746f9a | |||
| 6448f0e32b | |||
| e07332d49a | |||
| bd8a44c4c9 |
@@ -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
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v1
|
uses: github/codeql-action/init@v2
|
||||||
with:
|
with:
|
||||||
queries: +security-extended
|
queries: +security-extended
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
@@ -38,4 +38,4 @@ jobs:
|
|||||||
./gradlew clean build -x checkstyleTest -x checkstyleMain -x test -x ':jadx-core:testClasses'
|
./gradlew clean build -x checkstyleTest -x checkstyleMain -x test -x ':jadx-core:testClasses'
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v1
|
uses: github/codeql-action/analyze@v2
|
||||||
|
|||||||
@@ -3,8 +3,10 @@
|
|||||||
## JADX
|
## JADX
|
||||||
|
|
||||||
[](https://github.com/skylot/jadx/actions?query=workflow%3ABuild)
|
[](https://github.com/skylot/jadx/actions?query=workflow%3ABuild)
|
||||||
[](https://lgtm.com/projects/g/skylot/jadx/alerts/)
|

|
||||||
[](https://github.com/semantic-release/semantic-release)
|

|
||||||
|

|
||||||
|

|
||||||
[](https://search.maven.org/search?q=g:io.github.skylot%20AND%20jadx)
|
[](https://search.maven.org/search?q=g:io.github.skylot%20AND%20jadx)
|
||||||
[](http://www.apache.org/licenses/LICENSE-2.0.html)
|
[](http://www.apache.org/licenses/LICENSE-2.0.html)
|
||||||
|
|
||||||
@@ -33,8 +35,9 @@ See these features in action here: [jadx-gui features overview](https://github.c
|
|||||||
<img src="https://user-images.githubusercontent.com/118523/142730720-839f017e-38db-423e-b53f-39f5f0a0316f.png" width="700"/>
|
<img src="https://user-images.githubusercontent.com/118523/142730720-839f017e-38db-423e-b53f-39f5f0a0316f.png" width="700"/>
|
||||||
|
|
||||||
### Download
|
### Download
|
||||||
- release from [github: ](https://github.com/skylot/jadx/releases/latest)
|
- release
|
||||||
- latest [unstable build](https://nightly.link/skylot/jadx/workflows/build/master)
|
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:
|
After download unpack zip file go to `bin` directory and run:
|
||||||
- `jadx` - command line version
|
- `jadx` - command line version
|
||||||
@@ -45,14 +48,18 @@ On Windows run `.bat` files with double-click\
|
|||||||
For Windows, you can download it from [oracle.com](https://www.oracle.com/java/technologies/downloads/#jdk17-windows) (select x64 Installer).
|
For Windows, you can download it from [oracle.com](https://www.oracle.com/java/technologies/downloads/#jdk17-windows) (select x64 Installer).
|
||||||
|
|
||||||
### Install
|
### Install
|
||||||
1. Arch linux
|
1. Arch linux 
|
||||||
```bash
|
```bash
|
||||||
sudo pacman -S jadx
|
sudo pacman -S jadx
|
||||||
```
|
```
|
||||||
2. macOS
|
2. macOS 
|
||||||
```bash
|
```bash
|
||||||
brew install jadx
|
brew install jadx
|
||||||
```
|
```
|
||||||
|
3. [Flathub ](https://flathub.org/apps/details/com.github.skylot.jadx)
|
||||||
|
```bash
|
||||||
|
flatpak install flathub com.github.skylot.jadx
|
||||||
|
```
|
||||||
|
|
||||||
### Use jadx as a library
|
### Use jadx as a library
|
||||||
You can use jadx in your java projects, check details on [wiki page](https://github.com/skylot/jadx/wiki/Use-jadx-as-a-library)
|
You can use jadx in your java projects, check details on [wiki page](https://github.com/skylot/jadx/wiki/Use-jadx-as-a-library)
|
||||||
@@ -107,7 +114,6 @@ options:
|
|||||||
'read-or-save' - read if found, save otherwise (don't overwrite)
|
'read-or-save' - read if found, save otherwise (don't overwrite)
|
||||||
'overwrite' - don't read, always save
|
'overwrite' - don't read, always save
|
||||||
'ignore' - don't read and don't save
|
'ignore' - don't read and don't save
|
||||||
--deobf-rewrite-cfg - set '--deobf-cfg-file-mode' to 'overwrite' (deprecated)
|
|
||||||
--deobf-use-sourcename - use source file name as class name alias
|
--deobf-use-sourcename - use source file name as class name alias
|
||||||
--deobf-parse-kotlin-metadata - parse kotlin metadata to class and package names
|
--deobf-parse-kotlin-metadata - parse kotlin metadata to class and package names
|
||||||
--use-kotlin-methods-for-var-names - use kotlin intrinsic methods to rename variables, values: disable, apply, apply-and-hide, default: apply
|
--use-kotlin-methods-for-var-names - use kotlin intrinsic methods to rename variables, values: disable, apply, apply-and-hide, default: apply
|
||||||
@@ -130,11 +136,11 @@ options:
|
|||||||
-h, --help - print this help
|
-h, --help - print this help
|
||||||
|
|
||||||
Plugin options (-P<name>=<value>):
|
Plugin options (-P<name>=<value>):
|
||||||
1) dex-input (Load .dex and .apk files)
|
1) dex-input: Load .dex and .apk files
|
||||||
-Pdex-input.verify-checksum - Verify dex file checksum before load, values: [yes, no], default: yes
|
- dex-input.verify-checksum - verify dex file checksum before load, values: [yes, no], default: yes
|
||||||
2) java-convert (Convert .jar and .class files to dex)
|
2) java-convert: Convert .class, .jar and .aar files to dex
|
||||||
-Pjava-convert.mode - Convert mode, values: [dx, d8, both], default: both
|
- java-convert.mode - convert mode, values: [dx, d8, both], default: both
|
||||||
-Pjava-convert.d8-desugar - Use desugar in d8, values: [yes, no], default: no
|
- java-convert.d8-desugar - use desugar in d8, values: [yes, no], default: no
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
jadx -d out classes.dex
|
jadx -d out classes.dex
|
||||||
|
|||||||
-33
@@ -1,33 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<component type="desktop">
|
|
||||||
<id>com.github.skylot.jadx</id>
|
|
||||||
<metadata_license>CC0-1.0</metadata_license>
|
|
||||||
<project_license>Apache-2.0</project_license>
|
|
||||||
<name>JADX</name>
|
|
||||||
<summary>Dex to Java decompiler</summary>
|
|
||||||
<description>
|
|
||||||
<p>Command line and GUI tools for producing Java source code from Android Dex and Apk files</p>
|
|
||||||
<ul>
|
|
||||||
<li>decompile Dalvik bytecode to java classes from APK, dex, aar, aab and zip files</li>
|
|
||||||
<li>decode AndroidManifest.xml and other resources from resources.arsc</li>
|
|
||||||
<li>deobfuscator included</li>
|
|
||||||
<li>view decompiled code with highlighted syntax</li>
|
|
||||||
<li>jump to declaration</li>
|
|
||||||
<li>find usage</li>
|
|
||||||
<li>full text search</li>
|
|
||||||
<li>smali debugger</li>
|
|
||||||
</ul>
|
|
||||||
</description>
|
|
||||||
<screenshots>
|
|
||||||
<screenshot type="default">
|
|
||||||
<image>https://user-images.githubusercontent.com/118523/142730720-839f017e-38db-423e-b53f-39f5f0a0316f.png</image>
|
|
||||||
</screenshot>
|
|
||||||
</screenshots>
|
|
||||||
<content_rating type="oars-1.1" />
|
|
||||||
<launchable type="desktop-id">com.github.skylot.jadx.desktop</launchable>
|
|
||||||
<url type="homepage">https://github.com/skylot/jadx</url>
|
|
||||||
<url type="bugtracker">https://github.com/skylot/jadx/issues</url>
|
|
||||||
<releases>
|
|
||||||
<release version="1.3.4" date="2022-03-20" />
|
|
||||||
</releases>
|
|
||||||
</component>
|
|
||||||
+8
-10
@@ -1,6 +1,6 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'com.github.ben-manes.versions' version '0.42.0'
|
id 'com.github.ben-manes.versions' version '0.42.0'
|
||||||
id 'com.diffplug.spotless' version '6.5.0'
|
id 'com.diffplug.spotless' version '6.6.1'
|
||||||
}
|
}
|
||||||
|
|
||||||
ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev"
|
ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev"
|
||||||
@@ -14,7 +14,6 @@ allprojects {
|
|||||||
version = jadxVersion
|
version = jadxVersion
|
||||||
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
|
||||||
|
|
||||||
compileJava {
|
compileJava {
|
||||||
options.encoding = "UTF-8"
|
options.encoding = "UTF-8"
|
||||||
@@ -32,7 +31,7 @@ allprojects {
|
|||||||
|
|
||||||
testImplementation 'ch.qos.logback:logback-classic:1.2.11'
|
testImplementation 'ch.qos.logback:logback-classic:1.2.11'
|
||||||
testImplementation 'org.hamcrest:hamcrest-library:2.2'
|
testImplementation 'org.hamcrest:hamcrest-library:2.2'
|
||||||
testImplementation 'org.mockito:mockito-core:4.5.1'
|
testImplementation 'org.mockito:mockito-core:4.6.0'
|
||||||
testImplementation 'org.assertj:assertj-core:3.22.0'
|
testImplementation 'org.assertj:assertj-core:3.22.0'
|
||||||
|
|
||||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
|
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
|
||||||
@@ -50,6 +49,11 @@ allprojects {
|
|||||||
mavenLocal()
|
mavenLocal()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
google()
|
google()
|
||||||
|
// Commented out for now since we're using a local mapping-io fork atm.
|
||||||
|
// maven {
|
||||||
|
// name 'FabricMC'
|
||||||
|
// url 'https://maven.fabricmc.net/'
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,13 +68,7 @@ spotless {
|
|||||||
|
|
||||||
importOrderFile 'config/code-formatter/eclipse.importorder'
|
importOrderFile 'config/code-formatter/eclipse.importorder'
|
||||||
eclipse().configFile 'config/code-formatter/eclipse.xml'
|
eclipse().configFile 'config/code-formatter/eclipse.xml'
|
||||||
if (JavaVersion.current() < JavaVersion.VERSION_16) {
|
removeUnusedImports()
|
||||||
removeUnusedImports()
|
|
||||||
} else {
|
|
||||||
// google-format on Java 16+ issue: https://github.com/diffplug/spotless/issues/834
|
|
||||||
println('Warning! Unused imports remove is disabled for Java 16+'
|
|
||||||
+ ' (use workaround from https://github.com/diffplug/spotless/tree/main/plugin-gradle#google-java-format)')
|
|
||||||
}
|
|
||||||
|
|
||||||
lineEndings(com.diffplug.spotless.LineEnding.UNIX)
|
lineEndings(com.diffplug.spotless.LineEnding.UNIX)
|
||||||
encoding("UTF-8")
|
encoding("UTF-8")
|
||||||
|
|||||||
@@ -1,2 +1,11 @@
|
|||||||
org.gradle.warning.mode=all
|
org.gradle.warning.mode=all
|
||||||
org.gradle.parallel=true
|
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'
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import java.util.LinkedHashMap;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
@@ -22,6 +23,7 @@ import jadx.api.plugins.JadxPluginInfo;
|
|||||||
import jadx.api.plugins.JadxPluginManager;
|
import jadx.api.plugins.JadxPluginManager;
|
||||||
import jadx.api.plugins.options.JadxPluginOptions;
|
import jadx.api.plugins.options.JadxPluginOptions;
|
||||||
import jadx.api.plugins.options.OptionDescription;
|
import jadx.api.plugins.options.OptionDescription;
|
||||||
|
import jadx.core.utils.Utils;
|
||||||
|
|
||||||
public class JCommanderWrapper<T> {
|
public class JCommanderWrapper<T> {
|
||||||
private final JCommander jc;
|
private final JCommander jc;
|
||||||
@@ -50,12 +52,24 @@ public class JCommanderWrapper<T> {
|
|||||||
if (parameter.isAssigned()) {
|
if (parameter.isAssigned()) {
|
||||||
// copy assigned field value to obj
|
// copy assigned field value to obj
|
||||||
Parameterized parameterized = parameter.getParameterized();
|
Parameterized parameterized = parameter.getParameterized();
|
||||||
Object val = parameterized.get(parameter.getObject());
|
Object providedValue = parameterized.get(parameter.getObject());
|
||||||
parameterized.set(obj, val);
|
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() {
|
public void printUsage() {
|
||||||
// print usage in not sorted fields order (by default its sorted by description)
|
// print usage in not sorted fields order (by default its sorted by description)
|
||||||
PrintStream out = System.out;
|
PrintStream out = System.out;
|
||||||
@@ -179,11 +193,11 @@ public class JCommanderWrapper<T> {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
JadxPluginInfo pluginInfo = plugin.getPluginInfo();
|
JadxPluginInfo pluginInfo = plugin.getPluginInfo();
|
||||||
out.append("\n ").append(k).append(") ");
|
out.append("\n ").append(k).append(") ");
|
||||||
out.append(pluginInfo.getPluginId()).append(" (").append(pluginInfo.getDescription()).append(") ");
|
out.append(pluginInfo.getPluginId()).append(": ").append(pluginInfo.getDescription());
|
||||||
for (OptionDescription desc : descs) {
|
for (OptionDescription desc : descs) {
|
||||||
StringBuilder opt = new StringBuilder();
|
StringBuilder opt = new StringBuilder();
|
||||||
opt.append(" -P").append(desc.name());
|
opt.append(" - ").append(desc.name());
|
||||||
addSpaces(opt, maxNamesLen - opt.length());
|
addSpaces(opt, maxNamesLen - opt.length());
|
||||||
opt.append("- ").append(desc.description());
|
opt.append("- ").append(desc.description());
|
||||||
if (!desc.values().isEmpty()) {
|
if (!desc.values().isEmpty()) {
|
||||||
|
|||||||
@@ -66,8 +66,14 @@ public class JadxCLI {
|
|||||||
|
|
||||||
private static boolean checkForErrors(JadxDecompiler jadx) {
|
private static boolean checkForErrors(JadxDecompiler jadx) {
|
||||||
if (jadx.getRoot().getClasses().isEmpty()) {
|
if (jadx.getRoot().getClasses().isEmpty()) {
|
||||||
LOG.error("Load failed! No classes for decompile!");
|
if (jadx.getArgs().isSkipResources()) {
|
||||||
return true;
|
LOG.error("Load failed! No classes for decompile!");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!jadx.getArgs().isSkipSources()) {
|
||||||
|
LOG.warn("No classes to decompile; decoding resources only");
|
||||||
|
jadx.getArgs().setSkipSources(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (jadx.getErrorsCount() > 0) {
|
if (jadx.getErrorsCount() > 0) {
|
||||||
LOG.error("Load with errors! Check log for details");
|
LOG.error("Load with errors! Check log for details");
|
||||||
|
|||||||
@@ -123,9 +123,6 @@ public class JadxCLIArgs {
|
|||||||
)
|
)
|
||||||
protected DeobfuscationMapFileMode deobfuscationMapFileMode = DeobfuscationMapFileMode.READ;
|
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")
|
@Parameter(names = { "--deobf-use-sourcename" }, description = "use source file name as class name alias")
|
||||||
protected boolean deobfuscationUseSourceNameAsAlias = false;
|
protected boolean deobfuscationUseSourceNameAsAlias = false;
|
||||||
|
|
||||||
@@ -259,11 +256,7 @@ public class JadxCLIArgs {
|
|||||||
args.setReplaceConsts(replaceConsts);
|
args.setReplaceConsts(replaceConsts);
|
||||||
args.setDeobfuscationOn(deobfuscationOn);
|
args.setDeobfuscationOn(deobfuscationOn);
|
||||||
args.setDeobfuscationMapFile(FileUtils.toFile(deobfuscationMapFile));
|
args.setDeobfuscationMapFile(FileUtils.toFile(deobfuscationMapFile));
|
||||||
if (deobfuscationForceSave) {
|
args.setDeobfuscationMapFileMode(deobfuscationMapFileMode);
|
||||||
args.setDeobfuscationMapFileMode(DeobfuscationMapFileMode.OVERWRITE);
|
|
||||||
} else {
|
|
||||||
args.setDeobfuscationMapFileMode(deobfuscationMapFileMode);
|
|
||||||
}
|
|
||||||
args.setDeobfuscationMinLength(deobfuscationMinLength);
|
args.setDeobfuscationMinLength(deobfuscationMinLength);
|
||||||
args.setDeobfuscationMaxLength(deobfuscationMaxLength);
|
args.setDeobfuscationMaxLength(deobfuscationMaxLength);
|
||||||
args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias);
|
args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias);
|
||||||
@@ -377,10 +370,6 @@ public class JadxCLIArgs {
|
|||||||
return deobfuscationMapFileMode;
|
return deobfuscationMapFileMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isDeobfuscationForceSave() {
|
|
||||||
return deobfuscationForceSave;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isDeobfuscationUseSourceNameAsAlias() {
|
public boolean isDeobfuscationUseSourceNameAsAlias() {
|
||||||
return deobfuscationUseSourceNameAsAlias;
|
return deobfuscationUseSourceNameAsAlias;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
package jadx.cli;
|
package jadx.cli;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.hamcrest.Matchers;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import static jadx.core.utils.Utils.newConstStringMap;
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.hamcrest.Matchers.is;
|
import static org.hamcrest.Matchers.is;
|
||||||
|
|
||||||
@@ -47,6 +52,40 @@ public class JadxCLIArgsTest {
|
|||||||
assertThat(override(args, "").isUseImports(), is(false));
|
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) {
|
private JadxCLIArgs parse(String... args) {
|
||||||
return parse(new JadxCLIArgs(), args);
|
return parse(new JadxCLIArgs(), args);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,33 @@ public class TestInput {
|
|||||||
decompile("multi", "samples/hello.dex", "samples/HelloWorld.smali");
|
decompile("multi", "samples/hello.dex", "samples/HelloWorld.smali");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResourceOnly() throws Exception {
|
||||||
|
decode("resourceOnly", "samples/resources-only.apk");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void decode(String tmpDirName, String apkSample) throws URISyntaxException, IOException {
|
||||||
|
List<String> args = new ArrayList<>();
|
||||||
|
Path tempDir = FileUtils.createTempDir(tmpDirName);
|
||||||
|
args.add("-v");
|
||||||
|
args.add("-d");
|
||||||
|
args.add(tempDir.toAbsolutePath().toString());
|
||||||
|
|
||||||
|
URL resource = getClass().getClassLoader().getResource(apkSample);
|
||||||
|
assertThat(resource).isNotNull();
|
||||||
|
String sampleFile = resource.toURI().getRawPath();
|
||||||
|
args.add(sampleFile);
|
||||||
|
|
||||||
|
int result = JadxCLI.execute(args.toArray(new String[0]));
|
||||||
|
assertThat(result).isEqualTo(0);
|
||||||
|
List<Path> files = Files.find(
|
||||||
|
tempDir,
|
||||||
|
3,
|
||||||
|
(file, attr) -> file.getFileName().toString().equalsIgnoreCase("AndroidManifest.xml"))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
assertThat(files.isEmpty()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
private void decompile(String tmpDirName, String... inputSamples) throws URISyntaxException, IOException {
|
private void decompile(String tmpDirName, String... inputSamples) throws URISyntaxException, IOException {
|
||||||
List<String> args = new ArrayList<>();
|
List<String> args = new ArrayList<>();
|
||||||
Path tempDir = FileUtils.createTempDir(tmpDirName);
|
Path tempDir = FileUtils.createTempDir(tmpDirName);
|
||||||
|
|||||||
Binary file not shown.
@@ -12,6 +12,9 @@ import java.util.Set;
|
|||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import jadx.api.args.DeobfuscationMapFileMode;
|
import jadx.api.args.DeobfuscationMapFileMode;
|
||||||
import jadx.api.data.ICodeData;
|
import jadx.api.data.ICodeData;
|
||||||
import jadx.api.impl.AnnotatedCodeWriter;
|
import jadx.api.impl.AnnotatedCodeWriter;
|
||||||
@@ -19,6 +22,7 @@ import jadx.api.impl.InMemoryCodeCache;
|
|||||||
import jadx.core.utils.files.FileUtils;
|
import jadx.core.utils.files.FileUtils;
|
||||||
|
|
||||||
public class JadxArgs {
|
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);
|
public static final int DEFAULT_THREADS_COUNT = Math.max(1, Runtime.getRuntime().availableProcessors() / 2);
|
||||||
|
|
||||||
@@ -122,6 +126,19 @@ public class JadxArgs {
|
|||||||
setOutDirRes(new File(rootDir, DEFAULT_RES_DIR));
|
setOutDirRes(new File(rootDir, DEFAULT_RES_DIR));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void close() {
|
||||||
|
try {
|
||||||
|
inputFiles = null;
|
||||||
|
if (codeCache != null) {
|
||||||
|
codeCache.close();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.error("Failed to close JadxArgs", e);
|
||||||
|
} finally {
|
||||||
|
codeCache = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public List<File> getInputFiles() {
|
public List<File> getInputFiles() {
|
||||||
return inputFiles;
|
return inputFiles;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
private final Map<MethodNode, JavaMethod> methodsMap = new ConcurrentHashMap<>();
|
private final Map<MethodNode, JavaMethod> methodsMap = new ConcurrentHashMap<>();
|
||||||
private final Map<FieldNode, JavaField> fieldsMap = 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<>();
|
private final List<ILoadResult> customLoads = new ArrayList<>();
|
||||||
|
|
||||||
@@ -161,8 +161,13 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
classesMap.clear();
|
classesMap.clear();
|
||||||
methodsMap.clear();
|
methodsMap.clear();
|
||||||
fieldsMap.clear();
|
fieldsMap.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
reset();
|
||||||
closeInputs();
|
closeInputs();
|
||||||
|
args.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void closeInputs() {
|
private void closeInputs() {
|
||||||
@@ -176,11 +181,6 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
loadedInputs.clear();
|
loadedInputs.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadPlugins(JadxArgs args) {
|
private void loadPlugins(JadxArgs args) {
|
||||||
pluginManager.providesSuggestion("java-input", args.isUseDxInput() ? "java-convert" : "java-input");
|
pluginManager.providesSuggestion("java-input", args.isUseDxInput() ? "java-convert" : "java-input");
|
||||||
pluginManager.load();
|
pluginManager.load();
|
||||||
@@ -202,6 +202,7 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
public void registerPlugin(JadxPlugin plugin) {
|
public void registerPlugin(JadxPlugin plugin) {
|
||||||
pluginManager.register(plugin);
|
pluginManager.register(plugin);
|
||||||
}
|
}
|
||||||
@@ -467,23 +468,10 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
return protoXmlParser;
|
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
|
* Get JavaClass by ClassNode without loading and decompilation
|
||||||
*/
|
*/
|
||||||
|
@ApiStatus.Internal
|
||||||
JavaClass convertClassNode(ClassNode cls) {
|
JavaClass convertClassNode(ClassNode cls) {
|
||||||
return classesMap.compute(cls, (node, prevJavaCls) -> {
|
return classesMap.compute(cls, (node, prevJavaCls) -> {
|
||||||
if (prevJavaCls != null && prevJavaCls.getClassNode() == cls) {
|
if (prevJavaCls != null && prevJavaCls.getClassNode() == cls) {
|
||||||
@@ -497,66 +485,23 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable("For not generated classes")
|
|
||||||
@ApiStatus.Internal
|
@ApiStatus.Internal
|
||||||
public JavaClass getJavaClassByNode(ClassNode cls) {
|
JavaField convertFieldNode(FieldNode field) {
|
||||||
JavaClass javaClass = classesMap.get(cls);
|
return fieldsMap.computeIfAbsent(field, fldNode -> {
|
||||||
if (javaClass != null && javaClass.getClassNode() == cls) {
|
JavaClass parentCls = convertClassNode(fldNode.getParentClass());
|
||||||
return javaClass;
|
return new JavaField(parentCls, fldNode);
|
||||||
}
|
});
|
||||||
// load parent class if inner
|
|
||||||
ClassNode parentClass = cls.getTopParentClass();
|
|
||||||
if (parentClass.contains(AFlag.DONT_GENERATE)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
JavaClass parentJavaClass = classesMap.get(parentClass);
|
|
||||||
if (parentJavaClass == null) {
|
|
||||||
getClasses();
|
|
||||||
parentJavaClass = classesMap.get(parentClass);
|
|
||||||
}
|
|
||||||
if (parentJavaClass != null) {
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiStatus.Internal
|
@ApiStatus.Internal
|
||||||
@Nullable
|
JavaMethod convertMethodNode(MethodNode method) {
|
||||||
public JavaMethod getJavaMethodByNode(MethodNode mth) {
|
return methodsMap.computeIfAbsent(method, mthNode -> {
|
||||||
JavaMethod javaMethod = methodsMap.get(mth);
|
ClassNode codeCls = getCodeParentClass(mthNode.getParentClass());
|
||||||
if (javaMethod != null && javaMethod.getMethodNode() == mth) {
|
return new JavaMethod(convertClassNode(codeCls), mthNode);
|
||||||
return javaMethod;
|
});
|
||||||
}
|
|
||||||
if (mth.contains(AFlag.DONT_GENERATE)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// parent class not loaded yet
|
|
||||||
ClassNode parentClass = mth.getParentClass();
|
|
||||||
ClassNode codeCls = getCodeParentClass(parentClass);
|
|
||||||
JavaClass javaClass = getJavaClassByNode(codeCls);
|
|
||||||
if (javaClass == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
loadJavaClass(javaClass);
|
|
||||||
javaMethod = methodsMap.get(mth);
|
|
||||||
if (javaMethod != null) {
|
|
||||||
return javaMethod;
|
|
||||||
}
|
|
||||||
if (parentClass.hasNotGeneratedParent()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
throw new JadxRuntimeException("JavaMethod not found by MethodNode: " + mth);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ClassNode getCodeParentClass(ClassNode cls) {
|
private static ClassNode getCodeParentClass(ClassNode cls) {
|
||||||
ClassNode codeCls;
|
ClassNode codeCls;
|
||||||
InlinedAttr inlinedAttr = cls.get(AType.INLINED);
|
InlinedAttr inlinedAttr = cls.get(AType.INLINED);
|
||||||
if (inlinedAttr != null) {
|
if (inlinedAttr != null) {
|
||||||
@@ -570,35 +515,12 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
return getCodeParentClass(codeCls);
|
return getCodeParentClass(codeCls);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiStatus.Internal
|
|
||||||
@Nullable
|
|
||||||
public JavaField getJavaFieldByNode(FieldNode fld) {
|
|
||||||
JavaField javaField = fieldsMap.get(fld);
|
|
||||||
if (javaField != null && javaField.getFieldNode() == fld) {
|
|
||||||
return javaField;
|
|
||||||
}
|
|
||||||
// parent class not loaded yet
|
|
||||||
JavaClass javaClass = getJavaClassByNode(fld.getParentClass().getTopParentClass());
|
|
||||||
if (javaClass == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
loadJavaClass(javaClass);
|
|
||||||
javaField = fieldsMap.get(fld);
|
|
||||||
if (javaField != null) {
|
|
||||||
return javaField;
|
|
||||||
}
|
|
||||||
if (fld.getParentClass().hasNotGeneratedParent()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
throw new JadxRuntimeException("JavaField not found by FieldNode: " + fld);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public JavaClass searchJavaClassByOrigFullName(String fullName) {
|
public JavaClass searchJavaClassByOrigFullName(String fullName) {
|
||||||
return getRoot().getClasses().stream()
|
return getRoot().getClasses().stream()
|
||||||
.filter(cls -> cls.getClassInfo().getFullName().equals(fullName))
|
.filter(cls -> cls.getClassInfo().getFullName().equals(fullName))
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.map(this::getJavaClassByNode)
|
.map(this::convertClassNode)
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -619,9 +541,9 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
.orElse(null);
|
.orElse(null);
|
||||||
if (node != null) {
|
if (node != null) {
|
||||||
if (node.contains(AFlag.DONT_GENERATE)) {
|
if (node.contains(AFlag.DONT_GENERATE)) {
|
||||||
return getJavaClassByNode(node.getTopParentClass());
|
return convertClassNode(node.getTopParentClass());
|
||||||
} else {
|
} else {
|
||||||
return getJavaClassByNode(node);
|
return convertClassNode(node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@@ -632,7 +554,7 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
return getRoot().getClasses().stream()
|
return getRoot().getClasses().stream()
|
||||||
.filter(cls -> cls.getClassInfo().getAliasFullName().equals(fullName))
|
.filter(cls -> cls.getClassInfo().getAliasFullName().equals(fullName))
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.map(this::getJavaClassByNode)
|
.map(this::convertClassNode)
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -650,9 +572,9 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
case CLASS:
|
case CLASS:
|
||||||
return convertClassNode((ClassNode) ann);
|
return convertClassNode((ClassNode) ann);
|
||||||
case METHOD:
|
case METHOD:
|
||||||
return getJavaMethodByNode((MethodNode) ann);
|
return convertMethodNode((MethodNode) ann);
|
||||||
case FIELD:
|
case FIELD:
|
||||||
return getJavaFieldByNode((FieldNode) ann);
|
return convertFieldNode((FieldNode) ann);
|
||||||
case DECLARATION:
|
case DECLARATION:
|
||||||
return getJavaNodeByCodeAnnotation(codeInfo, ((NodeDeclareRef) ann).getNode());
|
return getJavaNodeByCodeAnnotation(codeInfo, ((NodeDeclareRef) ann).getNode());
|
||||||
case VAR:
|
case VAR:
|
||||||
@@ -670,7 +592,7 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
@Nullable
|
@Nullable
|
||||||
private JavaVariable resolveVarNode(VarNode varNode) {
|
private JavaVariable resolveVarNode(VarNode varNode) {
|
||||||
MethodNode mthNode = varNode.getMth();
|
MethodNode mthNode = varNode.getMth();
|
||||||
JavaMethod mth = getJavaMethodByNode(mthNode);
|
JavaMethod mth = convertMethodNode(mthNode);
|
||||||
if (mth == null) {
|
if (mth == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -683,10 +605,13 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
throw new JadxRuntimeException("Missing code info for resolve VarRef: " + varRef);
|
throw new JadxRuntimeException("Missing code info for resolve VarRef: " + varRef);
|
||||||
}
|
}
|
||||||
ICodeAnnotation varNodeAnn = codeInfo.getCodeMetadata().getAt(varRef.getRefPos());
|
ICodeAnnotation varNodeAnn = codeInfo.getCodeMetadata().getAt(varRef.getRefPos());
|
||||||
if (varNodeAnn == null) {
|
if (varNodeAnn != null && varNodeAnn.getAnnType() == ICodeAnnotation.AnnType.DECLARATION) {
|
||||||
return null;
|
ICodeNodeRef nodeRef = ((NodeDeclareRef) varNodeAnn).getNode();
|
||||||
|
if (nodeRef.getAnnType() == ICodeAnnotation.AnnType.VAR) {
|
||||||
|
return resolveVarNode((VarNode) nodeRef);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return (JavaVariable) getJavaNodeByCodeAnnotation(codeInfo, varNodeAnn);
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<JavaNode> convertNodes(Collection<? extends ICodeNodeRef> nodesList) {
|
List<JavaNode> convertNodes(Collection<? extends ICodeNodeRef> nodesList) {
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import java.util.Map;
|
|||||||
import org.jetbrains.annotations.ApiStatus;
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import jadx.api.metadata.ICodeAnnotation;
|
import jadx.api.metadata.ICodeAnnotation;
|
||||||
import jadx.api.metadata.ICodeNodeRef;
|
import jadx.api.metadata.ICodeNodeRef;
|
||||||
@@ -20,8 +22,10 @@ import jadx.core.dex.info.AccessInfo;
|
|||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
import jadx.core.dex.nodes.FieldNode;
|
import jadx.core.dex.nodes.FieldNode;
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
|
import jadx.core.utils.ListUtils;
|
||||||
|
|
||||||
public final class JavaClass implements JavaNode {
|
public final class JavaClass implements JavaNode {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(JavaClass.class);
|
||||||
|
|
||||||
private final JadxDecompiler decompiler;
|
private final JadxDecompiler decompiler;
|
||||||
private final ClassNode cls;
|
private final ClassNode cls;
|
||||||
@@ -83,6 +87,14 @@ public final class JavaClass implements JavaNode {
|
|||||||
return cls.getDisassembledCode();
|
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!
|
* Internal API. Not Stable!
|
||||||
*/
|
*/
|
||||||
@@ -94,7 +106,6 @@ public final class JavaClass implements JavaNode {
|
|||||||
/**
|
/**
|
||||||
* Decompile class and loads internal lists of fields, methods, etc.
|
* Decompile class and loads internal lists of fields, methods, etc.
|
||||||
* Do nothing if already loaded.
|
* Do nothing if already loaded.
|
||||||
* Return not null on first call only (for actual loading)
|
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
private synchronized void load() {
|
private synchronized void load() {
|
||||||
@@ -135,10 +146,9 @@ public final class JavaClass implements JavaNode {
|
|||||||
if (fieldsCount != 0) {
|
if (fieldsCount != 0) {
|
||||||
List<JavaField> flds = new ArrayList<>(fieldsCount);
|
List<JavaField> flds = new ArrayList<>(fieldsCount);
|
||||||
for (FieldNode f : cls.getFields()) {
|
for (FieldNode f : cls.getFields()) {
|
||||||
// if (!f.contains(AFlag.DONT_GENERATE)) {
|
if (!f.contains(AFlag.DONT_GENERATE)) {
|
||||||
JavaField javaField = new JavaField(this, f);
|
flds.add(rootDecompiler.convertFieldNode(f));
|
||||||
flds.add(javaField);
|
}
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
this.fields = Collections.unmodifiableList(flds);
|
this.fields = Collections.unmodifiableList(flds);
|
||||||
}
|
}
|
||||||
@@ -148,8 +158,7 @@ public final class JavaClass implements JavaNode {
|
|||||||
List<JavaMethod> mths = new ArrayList<>(methodsCount);
|
List<JavaMethod> mths = new ArrayList<>(methodsCount);
|
||||||
for (MethodNode m : cls.getMethods()) {
|
for (MethodNode m : cls.getMethods()) {
|
||||||
if (!m.contains(AFlag.DONT_GENERATE)) {
|
if (!m.contains(AFlag.DONT_GENERATE)) {
|
||||||
JavaMethod javaMethod = new JavaMethod(this, m);
|
mths.add(rootDecompiler.convertMethodNode(m));
|
||||||
mths.add(javaMethod);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mths.sort(Comparator.comparing(JavaMethod::getName));
|
mths.sort(Comparator.comparing(JavaMethod::getName));
|
||||||
@@ -157,7 +166,7 @@ public final class JavaClass implements JavaNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected JadxDecompiler getRootDecompiler() {
|
JadxDecompiler getRootDecompiler() {
|
||||||
if (parent != null) {
|
if (parent != null) {
|
||||||
return parent.getRootDecompiler();
|
return parent.getRootDecompiler();
|
||||||
}
|
}
|
||||||
@@ -188,23 +197,16 @@ public final class JavaClass implements JavaNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public List<Integer> getUsePlacesFor(ICodeInfo codeInfo, JavaNode javaNode) {
|
public List<Integer> getUsePlacesFor(ICodeInfo codeInfo, JavaNode javaNode) {
|
||||||
Map<Integer, ICodeAnnotation> map = codeInfo.getCodeMetadata().getAsMap();
|
if (!codeInfo.hasMetadata()) {
|
||||||
if (map.isEmpty() || decompiler == null) {
|
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
JadxDecompiler rootDec = getRootDecompiler();
|
|
||||||
List<Integer> result = new ArrayList<>();
|
List<Integer> result = new ArrayList<>();
|
||||||
for (Map.Entry<Integer, ICodeAnnotation> entry : map.entrySet()) {
|
codeInfo.getCodeMetadata().searchDown(0, (pos, ann) -> {
|
||||||
ICodeAnnotation ann = entry.getValue();
|
if (javaNode.isOwnCodeAnnotation(ann)) {
|
||||||
if (ann.getAnnType() == ICodeAnnotation.AnnType.DECLARATION) {
|
result.add(pos);
|
||||||
// ignore declarations
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
JavaNode annNode = rootDec.getJavaNodeByCodeAnnotation(codeInfo, ann);
|
return null;
|
||||||
if (javaNode.equals(annNode)) {
|
});
|
||||||
result.add(entry.getKey());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,7 +287,16 @@ public final class JavaClass implements JavaNode {
|
|||||||
if (methodNode == null) {
|
if (methodNode == null) {
|
||||||
return 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
|
@Override
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import java.util.List;
|
|||||||
|
|
||||||
import org.jetbrains.annotations.ApiStatus;
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
|
||||||
|
import jadx.api.metadata.ICodeAnnotation;
|
||||||
import jadx.core.dex.info.AccessInfo;
|
import jadx.core.dex.info.AccessInfo;
|
||||||
import jadx.core.dex.instructions.args.ArgType;
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
import jadx.core.dex.nodes.FieldNode;
|
import jadx.core.dex.nodes.FieldNode;
|
||||||
@@ -65,6 +66,14 @@ public final class JavaField implements JavaNode {
|
|||||||
this.field.getFieldInfo().removeAlias();
|
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!
|
* Internal API. Not Stable!
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import org.jetbrains.annotations.ApiStatus;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import jadx.api.metadata.ICodeAnnotation;
|
||||||
import jadx.core.dex.attributes.AType;
|
import jadx.core.dex.attributes.AType;
|
||||||
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
|
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
|
||||||
import jadx.core.dex.info.AccessInfo;
|
import jadx.core.dex.info.AccessInfo;
|
||||||
@@ -18,6 +19,7 @@ import jadx.core.utils.Utils;
|
|||||||
|
|
||||||
public final class JavaMethod implements JavaNode {
|
public final class JavaMethod implements JavaNode {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(JavaMethod.class);
|
private static final Logger LOG = LoggerFactory.getLogger(JavaMethod.class);
|
||||||
|
|
||||||
private final MethodNode mth;
|
private final MethodNode mth;
|
||||||
private final JavaClass parent;
|
private final JavaClass parent;
|
||||||
|
|
||||||
@@ -78,7 +80,7 @@ public final class JavaMethod implements JavaNode {
|
|||||||
JadxDecompiler decompiler = getDeclaringClass().getRootDecompiler();
|
JadxDecompiler decompiler = getDeclaringClass().getRootDecompiler();
|
||||||
return ovrdAttr.getRelatedMthNodes().stream()
|
return ovrdAttr.getRelatedMthNodes().stream()
|
||||||
.map(m -> {
|
.map(m -> {
|
||||||
JavaMethod javaMth = decompiler.getJavaMethodByNode(m);
|
JavaMethod javaMth = decompiler.convertMethodNode(m);
|
||||||
if (javaMth == null) {
|
if (javaMth == null) {
|
||||||
LOG.warn("Failed convert to java method: {}", m);
|
LOG.warn("Failed convert to java method: {}", m);
|
||||||
}
|
}
|
||||||
@@ -106,6 +108,14 @@ public final class JavaMethod implements JavaNode {
|
|||||||
this.mth.getMethodInfo().removeAlias();
|
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!
|
* Internal API. Not Stable!
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package jadx.api;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import jadx.api.metadata.ICodeAnnotation;
|
||||||
|
|
||||||
public interface JavaNode {
|
public interface JavaNode {
|
||||||
|
|
||||||
String getName();
|
String getName();
|
||||||
@@ -18,4 +20,6 @@ public interface JavaNode {
|
|||||||
|
|
||||||
default void removeAlias() {
|
default void removeAlias() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean isOwnCodeAnnotation(ICodeAnnotation ann);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import java.util.List;
|
|||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import jadx.api.metadata.ICodeAnnotation;
|
||||||
|
|
||||||
public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
|
public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
|
||||||
private final String name;
|
private final String name;
|
||||||
private final List<JavaClass> classes;
|
private final List<JavaClass> classes;
|
||||||
@@ -49,6 +51,11 @@ public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
|
|||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isOwnCodeAnnotation(ICodeAnnotation ann) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compareTo(@NotNull JavaPackage o) {
|
public int compareTo(@NotNull JavaPackage o) {
|
||||||
return name.compareTo(o.name);
|
return name.compareTo(o.name);
|
||||||
|
|||||||
@@ -5,7 +5,10 @@ import java.util.List;
|
|||||||
|
|
||||||
import org.jetbrains.annotations.ApiStatus;
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
|
||||||
|
import jadx.api.metadata.ICodeAnnotation;
|
||||||
import jadx.api.metadata.annotations.VarNode;
|
import jadx.api.metadata.annotations.VarNode;
|
||||||
|
import jadx.api.metadata.annotations.VarRef;
|
||||||
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
|
|
||||||
public class JavaVariable implements JavaNode {
|
public class JavaVariable implements JavaNode {
|
||||||
private final JavaMethod mth;
|
private final JavaMethod mth;
|
||||||
@@ -43,6 +46,10 @@ public class JavaVariable implements JavaNode {
|
|||||||
return varNode.getType() + " " + varNode.getName() + " (r" + varNode.getReg() + "v" + varNode.getSsa() + ")";
|
return varNode.getType() + " " + varNode.getName() + " (r" + varNode.getReg() + "v" + varNode.getSsa() + ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ArgType getType() {
|
||||||
|
return ArgType.tryToResolveClassAlias(mth.getMethodNode().root(), varNode.getType());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JavaClass getDeclaringClass() {
|
public JavaClass getDeclaringClass() {
|
||||||
return mth.getDeclaringClass();
|
return mth.getDeclaringClass();
|
||||||
@@ -63,6 +70,15 @@ public class JavaVariable implements JavaNode {
|
|||||||
return Collections.singletonList(mth);
|
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
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return varNode.hashCode();
|
return varNode.hashCode();
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ public class Consts {
|
|||||||
public static final boolean DEBUG_TYPE_INFERENCE = false;
|
public static final boolean DEBUG_TYPE_INFERENCE = false;
|
||||||
public static final boolean DEBUG_OVERLOADED_CASTS = false;
|
public static final boolean DEBUG_OVERLOADED_CASTS = false;
|
||||||
public static final boolean DEBUG_EXC_HANDLERS = false;
|
public static final boolean DEBUG_EXC_HANDLERS = false;
|
||||||
|
public static final boolean DEBUG_FINALLY = false;
|
||||||
|
|
||||||
public static final String CLASS_OBJECT = "java.lang.Object";
|
public static final String CLASS_OBJECT = "java.lang.Object";
|
||||||
public static final String CLASS_STRING = "java.lang.String";
|
public static final String CLASS_STRING = "java.lang.String";
|
||||||
|
|||||||
@@ -238,9 +238,17 @@ public class Jadx {
|
|||||||
private static String version;
|
private static String version;
|
||||||
|
|
||||||
public static String getVersion() {
|
public static String getVersion() {
|
||||||
if (version != null) {
|
if (version == null) {
|
||||||
return version;
|
version = searchJadxVersion();
|
||||||
}
|
}
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isDevVersion() {
|
||||||
|
return getVersion().equals(VERSION_DEV);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String searchJadxVersion() {
|
||||||
try {
|
try {
|
||||||
ClassLoader classLoader = Jadx.class.getClassLoader();
|
ClassLoader classLoader = Jadx.class.getClassLoader();
|
||||||
if (classLoader != null) {
|
if (classLoader != null) {
|
||||||
@@ -250,7 +258,6 @@ public class Jadx {
|
|||||||
Manifest manifest = new Manifest(is);
|
Manifest manifest = new Manifest(is);
|
||||||
String ver = manifest.getMainAttributes().getValue("jadx-version");
|
String ver = manifest.getMainAttributes().getValue("jadx-version");
|
||||||
if (ver != null) {
|
if (ver != null) {
|
||||||
version = ver;
|
|
||||||
return ver;
|
return ver;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -203,6 +203,9 @@ public class ClspGraph {
|
|||||||
if (isNew) {
|
if (isNew) {
|
||||||
addSuperTypes(parentCls, result);
|
addSuperTypes(parentCls, result);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// parent type is unknown
|
||||||
|
result.add(parentType.getObject());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -614,21 +614,23 @@ public class ClassGen {
|
|||||||
if (useCls.equals(extClsInfo)) {
|
if (useCls.equals(extClsInfo)) {
|
||||||
return shortName;
|
return shortName;
|
||||||
}
|
}
|
||||||
if (extClsInfo.getPackage().equals("java.lang") && extClsInfo.getParentClass() == null) {
|
|
||||||
return shortName;
|
|
||||||
}
|
|
||||||
if (isClassInnerFor(useCls, extClsInfo)) {
|
if (isClassInnerFor(useCls, extClsInfo)) {
|
||||||
return shortName;
|
return shortName;
|
||||||
}
|
}
|
||||||
if (extClsInfo.isInner()) {
|
if (extClsInfo.isInner()) {
|
||||||
return expandInnerClassName(useCls, extClsInfo);
|
return expandInnerClassName(useCls, extClsInfo);
|
||||||
}
|
}
|
||||||
if (searchCollision(cls.root(), useCls, extClsInfo)) {
|
if (checkInnerCollision(cls.root(), useCls, extClsInfo)
|
||||||
|
|| checkInPackageCollision(cls.root(), useCls, extClsInfo)) {
|
||||||
return fullName;
|
return fullName;
|
||||||
}
|
}
|
||||||
if (isBothClassesInOneTopClass(useCls, extClsInfo)) {
|
if (isBothClassesInOneTopClass(useCls, extClsInfo)) {
|
||||||
return shortName;
|
return shortName;
|
||||||
}
|
}
|
||||||
|
// don't add import for top classes from 'java.lang' package (subpackages excluded)
|
||||||
|
if (extClsInfo.getPackage().equals("java.lang") && extClsInfo.getParentClass() == null) {
|
||||||
|
return shortName;
|
||||||
|
}
|
||||||
// don't add import if this class from same package
|
// don't add import if this class from same package
|
||||||
if (extClsInfo.getPackage().equals(useCls.getPackage()) && !extClsInfo.isInner()) {
|
if (extClsInfo.getPackage().equals(useCls.getPackage()) && !extClsInfo.isInner()) {
|
||||||
return shortName;
|
return shortName;
|
||||||
@@ -709,7 +711,7 @@ public class ClassGen {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean searchCollision(RootNode root, ClassInfo useCls, ClassInfo searchCls) {
|
private static boolean checkInnerCollision(RootNode root, @Nullable ClassInfo useCls, ClassInfo searchCls) {
|
||||||
if (useCls == null) {
|
if (useCls == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -726,7 +728,20 @@ public class ClassGen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return searchCollision(root, useCls.getParentClass(), searchCls);
|
return checkInnerCollision(root, useCls.getParentClass(), searchCls);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if class with same name exists in current package
|
||||||
|
*/
|
||||||
|
private static boolean checkInPackageCollision(RootNode root, ClassInfo useCls, ClassInfo searchCls) {
|
||||||
|
String currentPkg = useCls.getAliasPkg();
|
||||||
|
if (currentPkg.equals(searchCls.getAliasPkg())) {
|
||||||
|
// search class already from current package
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String shortName = searchCls.getAliasShortName();
|
||||||
|
return root.getClsp().isClsKnown(currentPkg + '.' + shortName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void insertRenameInfo(ICodeWriter code, ClassNode cls) {
|
private void insertRenameInfo(ICodeWriter code, ClassNode cls) {
|
||||||
|
|||||||
@@ -168,10 +168,11 @@ public class InsnGen {
|
|||||||
* Variable definition without type, only var name
|
* Variable definition without type, only var name
|
||||||
*/
|
*/
|
||||||
private void defVar(ICodeWriter code, CodeVar codeVar) {
|
private void defVar(ICodeWriter code, CodeVar codeVar) {
|
||||||
|
String varName = mgen.getNameGen().assignArg(codeVar);
|
||||||
if (code.isMetadataSupported()) {
|
if (code.isMetadataSupported()) {
|
||||||
code.attachDefinition(VarNode.get(mth, codeVar));
|
code.attachDefinition(VarNode.get(mth, codeVar));
|
||||||
}
|
}
|
||||||
code.add(mgen.getNameGen().assignArg(codeVar));
|
code.add(varName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String lit(LiteralArg arg) {
|
private String lit(LiteralArg arg) {
|
||||||
@@ -806,14 +807,9 @@ public class InsnGen {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case SUPER:
|
case SUPER:
|
||||||
ClassInfo superCallCls = getClassForSuperCall(code, callMth);
|
callSuper(code, callMth);
|
||||||
if (superCallCls != null) {
|
k++; // use 'super' instead 'this' in 0 arg
|
||||||
useClass(code, superCallCls);
|
code.add('.');
|
||||||
code.add('.');
|
|
||||||
}
|
|
||||||
// use 'super' instead 'this' in 0 arg
|
|
||||||
code.add("super").add('.');
|
|
||||||
k++;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case STATIC:
|
case STATIC:
|
||||||
@@ -964,34 +960,43 @@ public class InsnGen {
|
|||||||
code.startLine('}');
|
code.startLine('}');
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
private void callSuper(ICodeWriter code, MethodInfo callMth) {
|
||||||
private ClassInfo getClassForSuperCall(ICodeWriter code, MethodInfo callMth) {
|
ClassInfo superCallCls = getClassForSuperCall(callMth);
|
||||||
ClassNode useCls = mth.getParentClass();
|
if (superCallCls == null) {
|
||||||
ClassInfo insnCls = useCls.getClassInfo();
|
// unknown class, add comment to keep that info
|
||||||
ClassInfo declClass = callMth.getDeclClass();
|
code.add("super/*").add(callMth.getDeclClass().getFullName()).add("*/");
|
||||||
if (insnCls.equals(declClass)) {
|
return;
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
ClassNode topClass = useCls.getTopParentClass();
|
ClassInfo curClass = mth.getParentClass().getClassInfo();
|
||||||
if (topClass.getClassInfo().equals(declClass)) {
|
if (superCallCls.equals(curClass)) {
|
||||||
return declClass;
|
code.add("super");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
// search call class
|
// use custom class
|
||||||
ClassNode nextParent = useCls;
|
useClass(code, superCallCls);
|
||||||
do {
|
code.add(".super");
|
||||||
ClassInfo nextClsInfo = nextParent.getClassInfo();
|
}
|
||||||
if (nextClsInfo.equals(declClass)
|
|
||||||
|| ArgType.isInstanceOf(mth.root(), nextClsInfo.getType(), declClass.getType())) {
|
|
||||||
if (nextParent == useCls) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return nextClsInfo;
|
|
||||||
}
|
|
||||||
nextParent = nextParent.getParentClass();
|
|
||||||
} while (nextParent != null && nextParent != topClass);
|
|
||||||
|
|
||||||
// search failed, just return parent class
|
/**
|
||||||
return useCls.getParentClass().getClassInfo();
|
* Search call class in super types of this
|
||||||
|
* and all parent classes (needed for inlined synthetic calls)
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
private ClassInfo getClassForSuperCall(MethodInfo callMth) {
|
||||||
|
ArgType declClsType = callMth.getDeclClass().getType();
|
||||||
|
ClassNode parentNode = mth.getParentClass();
|
||||||
|
while (true) {
|
||||||
|
ClassInfo parentCls = parentNode.getClassInfo();
|
||||||
|
if (ArgType.isInstanceOf(root, parentCls.getType(), declClsType)) {
|
||||||
|
return parentCls;
|
||||||
|
}
|
||||||
|
ClassNode nextParent = parentNode.getParentClass();
|
||||||
|
if (nextParent == parentNode) {
|
||||||
|
// no parent, class not found
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
parentNode = nextParent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void generateMethodArguments(ICodeWriter code, BaseInvokeNode insn, int startArgNum,
|
void generateMethodArguments(ICodeWriter code, BaseInvokeNode insn, int startArgNum,
|
||||||
|
|||||||
@@ -180,12 +180,12 @@ public final class ClassInfo implements Comparable<ClassInfo> {
|
|||||||
return makeFullClsName(pkg, name, parentClass, false, true);
|
return makeFullClsName(pkg, name, parentClass, false, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String makeAliasFullName() {
|
public String makeAliasFullName() {
|
||||||
return makeFullClsName(getAliasPkg(), getAliasShortName(), parentClass, true, false);
|
return makeFullClsName(getAliasPkg(), getAliasShortName(), parentClass, true, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String makeAliasRawFullName() {
|
public String makeAliasRawFullName() {
|
||||||
return makeFullClsName(pkg, name, parentClass, true, true);
|
return makeFullClsName(getAliasPkg(), getAliasShortName(), parentClass, true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getAliasFullPath() {
|
public String getAliasFullPath() {
|
||||||
|
|||||||
@@ -170,6 +170,10 @@ public final class BlockNode extends AttrNode implements IBlock, Comparable<Bloc
|
|||||||
return contains(AFlag.RETURN);
|
return contains(AFlag.RETURN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return instructions.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return startOffset;
|
return startOffset;
|
||||||
|
|||||||
@@ -379,7 +379,9 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
ICodeInfo codeInfo = root.getProcessClasses().generateCode(this);
|
ICodeInfo codeInfo = root.getProcessClasses().generateCode(this);
|
||||||
codeCache.add(clsRawName, codeInfo);
|
if (codeInfo != ICodeInfo.EMPTY) {
|
||||||
|
codeCache.add(clsRawName, codeInfo);
|
||||||
|
}
|
||||||
return codeInfo;
|
return codeInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -461,6 +461,10 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
|
|||||||
return regsCount;
|
return regsCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getArgsStartReg() {
|
||||||
|
return argsStartReg;
|
||||||
|
}
|
||||||
|
|
||||||
public SSAVar makeNewSVar(@NotNull RegisterArg assignArg) {
|
public SSAVar makeNewSVar(@NotNull RegisterArg assignArg) {
|
||||||
int regNum = assignArg.getRegNum();
|
int regNum = assignArg.getRegNum();
|
||||||
return makeNewSVar(regNum, getNextSVarVersion(regNum), assignArg);
|
return makeNewSVar(regNum, getNextSVarVersion(regNum), assignArg);
|
||||||
|
|||||||
@@ -115,21 +115,25 @@ public class RootNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void addDummyClass(IClassData classData, Exception exc) {
|
private void addDummyClass(IClassData classData, Exception exc) {
|
||||||
String typeStr = classData.getType();
|
|
||||||
String name = null;
|
|
||||||
try {
|
try {
|
||||||
ClassInfo clsInfo = ClassInfo.fromName(this, typeStr);
|
String typeStr = classData.getType();
|
||||||
if (clsInfo != null) {
|
String name = null;
|
||||||
name = clsInfo.getShortName();
|
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) {
|
if (name == null || name.isEmpty()) {
|
||||||
LOG.error("Failed to get name for class with type {}", typeStr, e);
|
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) {
|
private static void markDuplicatedClasses(List<ClassNode> classes) {
|
||||||
@@ -265,6 +269,7 @@ public class RootNode {
|
|||||||
public void runPreDecompileStage() {
|
public void runPreDecompileStage() {
|
||||||
boolean debugEnabled = LOG.isDebugEnabled();
|
boolean debugEnabled = LOG.isDebugEnabled();
|
||||||
for (IDexTreeVisitor pass : preDecompilePasses) {
|
for (IDexTreeVisitor pass : preDecompilePasses) {
|
||||||
|
Utils.checkThreadInterrupt();
|
||||||
long start = debugEnabled ? System.currentTimeMillis() : 0;
|
long start = debugEnabled ? System.currentTimeMillis() : 0;
|
||||||
try {
|
try {
|
||||||
pass.init(this);
|
pass.init(this);
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ import jadx.core.dex.instructions.args.ArgType;
|
|||||||
import jadx.core.dex.instructions.args.InsnArg;
|
import jadx.core.dex.instructions.args.InsnArg;
|
||||||
import jadx.core.dex.nodes.BlockNode;
|
import jadx.core.dex.nodes.BlockNode;
|
||||||
import jadx.core.dex.nodes.IContainer;
|
import jadx.core.dex.nodes.IContainer;
|
||||||
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
import jadx.core.utils.InsnUtils;
|
import jadx.core.utils.InsnUtils;
|
||||||
import jadx.core.utils.Utils;
|
import jadx.core.utils.Utils;
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
|
||||||
|
|
||||||
public class ExceptionHandler {
|
public class ExceptionHandler {
|
||||||
|
|
||||||
@@ -33,9 +33,14 @@ public class ExceptionHandler {
|
|||||||
|
|
||||||
private boolean removed = false;
|
private boolean removed = false;
|
||||||
|
|
||||||
public ExceptionHandler(int addr, @Nullable ClassInfo type) {
|
public static ExceptionHandler build(MethodNode mth, int addr, @Nullable ClassInfo type) {
|
||||||
|
ExceptionHandler eh = new ExceptionHandler(addr);
|
||||||
|
eh.addCatchType(mth, type);
|
||||||
|
return eh;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ExceptionHandler(int addr) {
|
||||||
this.handlerOffset = addr;
|
this.handlerOffset = addr;
|
||||||
addCatchType(type);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -43,7 +48,7 @@ public class ExceptionHandler {
|
|||||||
*
|
*
|
||||||
* @param type - null for 'all' or 'Throwable' handler
|
* @param type - null for 'all' or 'Throwable' handler
|
||||||
*/
|
*/
|
||||||
public boolean addCatchType(@Nullable ClassInfo type) {
|
public boolean addCatchType(MethodNode mth, @Nullable ClassInfo type) {
|
||||||
if (type != null) {
|
if (type != null) {
|
||||||
if (catchTypes.contains(type)) {
|
if (catchTypes.contains(type)) {
|
||||||
return false;
|
return false;
|
||||||
@@ -51,14 +56,16 @@ public class ExceptionHandler {
|
|||||||
return catchTypes.add(type);
|
return catchTypes.add(type);
|
||||||
}
|
}
|
||||||
if (!this.catchTypes.isEmpty()) {
|
if (!this.catchTypes.isEmpty()) {
|
||||||
throw new JadxRuntimeException("Null type added to not empty exception handler: " + this);
|
mth.addDebugComment("Throwable added to exception handler: '" + catchTypeStr() + "', keep only Throwable");
|
||||||
|
catchTypes.clear();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addCatchTypes(Collection<ClassInfo> types) {
|
public void addCatchTypes(MethodNode mth, Collection<ClassInfo> types) {
|
||||||
for (ClassInfo type : types) {
|
for (ClassInfo type : types) {
|
||||||
addCatchType(type);
|
addCatchType(mth, type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ public class AttachTryCatchVisitor extends AbstractVisitor {
|
|||||||
ExcHandlerAttr excHandlerAttr = insn.get(AType.EXC_HANDLER);
|
ExcHandlerAttr excHandlerAttr = insn.get(AType.EXC_HANDLER);
|
||||||
if (excHandlerAttr != null) {
|
if (excHandlerAttr != null) {
|
||||||
ExceptionHandler handler = excHandlerAttr.getHandler();
|
ExceptionHandler handler = excHandlerAttr.getHandler();
|
||||||
if (handler.addCatchType(type)) {
|
if (handler.addCatchType(mth, type)) {
|
||||||
// exist handler updated (assume from same try block) - don't add again
|
// exist handler updated (assume from same try block) - don't add again
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -143,7 +143,7 @@ public class AttachTryCatchVisitor extends AbstractVisitor {
|
|||||||
} else {
|
} else {
|
||||||
insn = insertNOP(insnByOffset, handlerOffset);
|
insn = insertNOP(insnByOffset, handlerOffset);
|
||||||
}
|
}
|
||||||
ExceptionHandler handler = new ExceptionHandler(handlerOffset, type);
|
ExceptionHandler handler = ExceptionHandler.build(mth, handlerOffset, type);
|
||||||
mth.addExceptionHandler(handler);
|
mth.addExceptionHandler(handler);
|
||||||
insn.addAttr(new ExcHandlerAttr(handler));
|
insn.addAttr(new ExcHandlerAttr(handler));
|
||||||
return handler;
|
return handler;
|
||||||
|
|||||||
@@ -362,8 +362,7 @@ public class ModVisitor extends AbstractVisitor {
|
|||||||
private static void removeCheckCast(MethodNode mth, BlockNode block, int i, IndexInsnNode insn) {
|
private static void removeCheckCast(MethodNode mth, BlockNode block, int i, IndexInsnNode insn) {
|
||||||
InsnArg castArg = insn.getArg(0);
|
InsnArg castArg = insn.getArg(0);
|
||||||
ArgType castType = (ArgType) insn.getIndex();
|
ArgType castType = (ArgType) insn.getIndex();
|
||||||
if (!ArgType.isCastNeeded(mth.root(), castArg.getType(), castType)
|
if (!ArgType.isCastNeeded(mth.root(), castArg.getType(), castType)) {
|
||||||
|| isCastDuplicate(insn)) {
|
|
||||||
RegisterArg result = insn.getResult();
|
RegisterArg result = insn.getResult();
|
||||||
result.setType(castArg.getType());
|
result.setType(castArg.getType());
|
||||||
|
|
||||||
@@ -371,10 +370,19 @@ public class ModVisitor extends AbstractVisitor {
|
|||||||
move.setResult(result);
|
move.setResult(result);
|
||||||
move.addArg(castArg);
|
move.addArg(castArg);
|
||||||
replaceInsn(mth, block, i, move);
|
replaceInsn(mth, block, i, move);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
InsnNode prevCast = isCastDuplicate(insn);
|
||||||
|
if (prevCast != null) {
|
||||||
|
// replace previous cast with move
|
||||||
|
InsnNode move = new InsnNode(InsnType.MOVE, 1);
|
||||||
|
move.setResult(prevCast.getResult());
|
||||||
|
move.addArg(prevCast.getArg(0));
|
||||||
|
replaceInsn(mth, block, prevCast, move);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isCastDuplicate(IndexInsnNode castInsn) {
|
private static @Nullable InsnNode isCastDuplicate(IndexInsnNode castInsn) {
|
||||||
InsnArg arg = castInsn.getArg(0);
|
InsnArg arg = castInsn.getArg(0);
|
||||||
if (arg.isRegister()) {
|
if (arg.isRegister()) {
|
||||||
SSAVar sVar = ((RegisterArg) arg).getSVar();
|
SSAVar sVar = ((RegisterArg) arg).getSVar();
|
||||||
@@ -382,11 +390,13 @@ public class ModVisitor extends AbstractVisitor {
|
|||||||
InsnNode assignInsn = sVar.getAssign().getParentInsn();
|
InsnNode assignInsn = sVar.getAssign().getParentInsn();
|
||||||
if (assignInsn != null && assignInsn.getType() == InsnType.CHECK_CAST) {
|
if (assignInsn != null && assignInsn.getType() == InsnType.CHECK_CAST) {
|
||||||
ArgType assignCastType = (ArgType) ((IndexInsnNode) assignInsn).getIndex();
|
ArgType assignCastType = (ArgType) ((IndexInsnNode) assignInsn).getIndex();
|
||||||
return assignCastType.equals(castInsn.getIndex());
|
if (assignCastType.equals(castInsn.getIndex())) {
|
||||||
|
return assignInsn;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -549,7 +549,7 @@ public class BlockExceptionHandler {
|
|||||||
if (handler == resultHandler) {
|
if (handler == resultHandler) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
resultHandler.addCatchTypes(handler.getCatchTypes());
|
resultHandler.addCatchTypes(mth, handler.getCatchTypes());
|
||||||
handler.markForRemove();
|
handler.markForRemove();
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,9 +6,12 @@ import java.util.List;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import jadx.core.dex.nodes.BlockNode;
|
import jadx.core.dex.nodes.BlockNode;
|
||||||
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
import jadx.core.dex.trycatch.ExceptionHandler;
|
import jadx.core.dex.trycatch.ExceptionHandler;
|
||||||
|
import jadx.core.utils.Utils;
|
||||||
|
|
||||||
public class FinallyExtractInfo {
|
public class FinallyExtractInfo {
|
||||||
|
private final MethodNode mth;
|
||||||
private final ExceptionHandler finallyHandler;
|
private final ExceptionHandler finallyHandler;
|
||||||
private final List<BlockNode> allHandlerBlocks;
|
private final List<BlockNode> allHandlerBlocks;
|
||||||
private final List<InsnsSlice> duplicateSlices = new ArrayList<>();
|
private final List<InsnsSlice> duplicateSlices = new ArrayList<>();
|
||||||
@@ -16,12 +19,17 @@ public class FinallyExtractInfo {
|
|||||||
private final InsnsSlice finallyInsnsSlice = new InsnsSlice();
|
private final InsnsSlice finallyInsnsSlice = new InsnsSlice();
|
||||||
private final BlockNode startBlock;
|
private final BlockNode startBlock;
|
||||||
|
|
||||||
public FinallyExtractInfo(ExceptionHandler finallyHandler, BlockNode startBlock, List<BlockNode> allHandlerBlocks) {
|
public FinallyExtractInfo(MethodNode mth, ExceptionHandler finallyHandler, BlockNode startBlock, List<BlockNode> allHandlerBlocks) {
|
||||||
|
this.mth = mth;
|
||||||
this.finallyHandler = finallyHandler;
|
this.finallyHandler = finallyHandler;
|
||||||
this.startBlock = startBlock;
|
this.startBlock = startBlock;
|
||||||
this.allHandlerBlocks = allHandlerBlocks;
|
this.allHandlerBlocks = allHandlerBlocks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MethodNode getMth() {
|
||||||
|
return mth;
|
||||||
|
}
|
||||||
|
|
||||||
public ExceptionHandler getFinallyHandler() {
|
public ExceptionHandler getFinallyHandler() {
|
||||||
return finallyHandler;
|
return finallyHandler;
|
||||||
}
|
}
|
||||||
@@ -45,4 +53,12 @@ public class FinallyExtractInfo {
|
|||||||
public BlockNode getStartBlock() {
|
public BlockNode getStartBlock() {
|
||||||
return startBlock;
|
return startBlock;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "FinallyExtractInfo{"
|
||||||
|
+ "\n finally:\n " + finallyInsnsSlice
|
||||||
|
+ "\n dups:\n " + Utils.listToString(duplicateSlices, "\n ")
|
||||||
|
+ "\n}";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import jadx.core.Consts;
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
import jadx.core.dex.attributes.AType;
|
import jadx.core.dex.attributes.AType;
|
||||||
import jadx.core.dex.instructions.InsnType;
|
import jadx.core.dex.instructions.InsnType;
|
||||||
@@ -38,6 +39,7 @@ import jadx.core.utils.Utils;
|
|||||||
)
|
)
|
||||||
public class MarkFinallyVisitor extends AbstractVisitor {
|
public class MarkFinallyVisitor extends AbstractVisitor {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(MarkFinallyVisitor.class);
|
private static final Logger LOG = LoggerFactory.getLogger(MarkFinallyVisitor.class);
|
||||||
|
// private static final Logger LOG = LoggerFactory.getLogger(MarkFinallyVisitor.class);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void visit(MethodNode mth) {
|
public void visit(MethodNode mth) {
|
||||||
@@ -60,7 +62,7 @@ public class MarkFinallyVisitor extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.warn("Undo finally extract visitor, mth: {}", mth, e);
|
mth.addWarnComment("Undo finally extract visitor", e);
|
||||||
undoFinallyVisitor(mth);
|
undoFinallyVisitor(mth);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -100,20 +102,23 @@ public class MarkFinallyVisitor extends AbstractVisitor {
|
|||||||
List<BlockNode> handlerBlocks =
|
List<BlockNode> handlerBlocks =
|
||||||
new ArrayList<>(BlockUtils.collectBlocksDominatedByWithExcHandlers(mth, handlerBlock, handlerBlock));
|
new ArrayList<>(BlockUtils.collectBlocksDominatedByWithExcHandlers(mth, handlerBlock, handlerBlock));
|
||||||
handlerBlocks.remove(handlerBlock); // exclude block with 'move-exception'
|
handlerBlocks.remove(handlerBlock); // exclude block with 'move-exception'
|
||||||
handlerBlocks.removeIf(b -> BlockUtils.checkLastInsnType(b, InsnType.THROW));
|
cutPathEnds(mth, handlerBlocks);
|
||||||
if (handlerBlocks.isEmpty() || BlockUtils.isAllBlocksEmpty(handlerBlocks)) {
|
if (handlerBlocks.isEmpty() || BlockUtils.isAllBlocksEmpty(handlerBlocks)) {
|
||||||
// remove empty catch
|
// remove empty catch
|
||||||
allHandler.getTryBlock().removeHandler(allHandler);
|
allHandler.getTryBlock().removeHandler(allHandler);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
BlockNode startBlock = Utils.getOne(handlerBlock.getCleanSuccessors());
|
BlockNode startBlock = Utils.getOne(handlerBlock.getCleanSuccessors());
|
||||||
FinallyExtractInfo extractInfo = new FinallyExtractInfo(allHandler, startBlock, handlerBlocks);
|
FinallyExtractInfo extractInfo = new FinallyExtractInfo(mth, allHandler, startBlock, handlerBlocks);
|
||||||
|
if (Consts.DEBUG_FINALLY) {
|
||||||
|
LOG.debug("Finally info: handler=({}), start={}, blocks={}", allHandler, startBlock, handlerBlocks);
|
||||||
|
}
|
||||||
|
|
||||||
boolean hasInnerBlocks = !tryBlock.getInnerTryBlocks().isEmpty();
|
boolean hasInnerBlocks = !tryBlock.getInnerTryBlocks().isEmpty();
|
||||||
List<ExceptionHandler> handlers;
|
List<ExceptionHandler> handlers;
|
||||||
if (hasInnerBlocks) {
|
if (hasInnerBlocks) {
|
||||||
// collect handlers from this and all inner blocks (intentionally not using recursive collect for
|
// collect handlers from this and all inner blocks
|
||||||
// now)
|
// (intentionally not using recursive collect for now)
|
||||||
handlers = new ArrayList<>(tryBlock.getHandlers());
|
handlers = new ArrayList<>(tryBlock.getHandlers());
|
||||||
for (TryCatchBlockAttr innerTryBlock : tryBlock.getInnerTryBlocks()) {
|
for (TryCatchBlockAttr innerTryBlock : tryBlock.getInnerTryBlocks()) {
|
||||||
handlers.addAll(innerTryBlock.getHandlers());
|
handlers.addAll(innerTryBlock.getHandlers());
|
||||||
@@ -137,10 +142,12 @@ public class MarkFinallyVisitor extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (Consts.DEBUG_FINALLY) {
|
||||||
|
LOG.debug("Handlers slices:\n{}", extractInfo);
|
||||||
|
}
|
||||||
boolean mergeInnerTryBlocks;
|
boolean mergeInnerTryBlocks;
|
||||||
int duplicatesCount = extractInfo.getDuplicateSlices().size();
|
int duplicatesCount = extractInfo.getDuplicateSlices().size();
|
||||||
boolean fullTryBlock = duplicatesCount == (handlers.size() - 1);
|
if (duplicatesCount == (handlers.size() - 1)) {
|
||||||
if (fullTryBlock) {
|
|
||||||
// all collected handlers have duplicate block
|
// all collected handlers have duplicate block
|
||||||
mergeInnerTryBlocks = hasInnerBlocks;
|
mergeInnerTryBlocks = hasInnerBlocks;
|
||||||
} else {
|
} else {
|
||||||
@@ -170,15 +177,24 @@ public class MarkFinallyVisitor extends AbstractVisitor {
|
|||||||
if (upPath.size() < handlerBlocks.size()) {
|
if (upPath.size() < handlerBlocks.size()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (Consts.DEBUG_FINALLY) {
|
||||||
|
LOG.debug("Checking dup path starts: {} from {}", upPath, pred);
|
||||||
|
}
|
||||||
for (BlockNode block : upPath) {
|
for (BlockNode block : upPath) {
|
||||||
if (searchDuplicateInsns(block, extractInfo)) {
|
if (searchDuplicateInsns(block, extractInfo)) {
|
||||||
found = true;
|
found = true;
|
||||||
|
if (Consts.DEBUG_FINALLY) {
|
||||||
|
LOG.debug("Found dup in: {} from {}", block, pred);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
extractInfo.getFinallyInsnsSlice().resetIncomplete();
|
extractInfo.getFinallyInsnsSlice().resetIncomplete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (Consts.DEBUG_FINALLY) {
|
||||||
|
LOG.debug("Result slices:\n{}", extractInfo);
|
||||||
|
}
|
||||||
if (!found) {
|
if (!found) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -204,6 +220,28 @@ public class MarkFinallyVisitor extends AbstractVisitor {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void cutPathEnds(MethodNode mth, List<BlockNode> handlerBlocks) {
|
||||||
|
List<BlockNode> throwBlocks = ListUtils.filter(handlerBlocks,
|
||||||
|
b -> BlockUtils.checkLastInsnType(b, InsnType.THROW));
|
||||||
|
if (throwBlocks.size() != 1) {
|
||||||
|
mth.addDebugComment("Finally have unexpected throw blocks count: " + throwBlocks.size() + ", expect 1");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
BlockNode throwBlock = throwBlocks.get(0);
|
||||||
|
handlerBlocks.remove(throwBlock);
|
||||||
|
removeEmptyUpPath(handlerBlocks, throwBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void removeEmptyUpPath(List<BlockNode> handlerBlocks, BlockNode startBlock) {
|
||||||
|
for (BlockNode pred : startBlock.getPredecessors()) {
|
||||||
|
if (pred.isEmpty()) {
|
||||||
|
if (handlerBlocks.remove(pred) && !BlockUtils.isBackEdge(pred, startBlock)) {
|
||||||
|
removeEmptyUpPath(handlerBlocks, pred);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static List<BlockNode> getPathStarts(MethodNode mth, BlockNode bottom, BlockNode bottomFinallyBlock) {
|
private static List<BlockNode> getPathStarts(MethodNode mth, BlockNode bottom, BlockNode bottomFinallyBlock) {
|
||||||
Stream<BlockNode> preds = bottom.getPredecessors().stream().filter(b -> b != bottomFinallyBlock);
|
Stream<BlockNode> preds = bottom.getPredecessors().stream().filter(b -> b != bottomFinallyBlock);
|
||||||
if (bottom == mth.getExitBlock()) {
|
if (bottom == mth.getExitBlock()) {
|
||||||
@@ -219,9 +257,8 @@ public class MarkFinallyVisitor extends AbstractVisitor {
|
|||||||
for (InsnsSlice dupSlice : extractInfo.getDuplicateSlices()) {
|
for (InsnsSlice dupSlice : extractInfo.getDuplicateSlices()) {
|
||||||
List<InsnNode> dupInsnsList = dupSlice.getInsnsList();
|
List<InsnNode> dupInsnsList = dupSlice.getInsnsList();
|
||||||
if (dupInsnsList.size() != finallyInsnsList.size()) {
|
if (dupInsnsList.size() != finallyInsnsList.size()) {
|
||||||
if (LOG.isDebugEnabled()) {
|
extractInfo.getMth().addDebugComment(
|
||||||
LOG.debug("Incorrect finally slice size: {}, expected: {}", dupSlice, finallySlice);
|
"Incorrect finally slice size: " + dupSlice + ", expected: " + finallySlice);
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -231,9 +268,8 @@ public class MarkFinallyVisitor extends AbstractVisitor {
|
|||||||
List<InsnNode> insnsList = dupSlice.getInsnsList();
|
List<InsnNode> insnsList = dupSlice.getInsnsList();
|
||||||
InsnNode dupInsn = insnsList.get(i);
|
InsnNode dupInsn = insnsList.get(i);
|
||||||
if (finallyInsn.getType() != dupInsn.getType()) {
|
if (finallyInsn.getType() != dupInsn.getType()) {
|
||||||
if (LOG.isDebugEnabled()) {
|
extractInfo.getMth().addDebugComment(
|
||||||
LOG.debug("Incorrect finally slice insn: {}, expected: {}", dupInsn, finallyInsn);
|
"Incorrect finally slice insn: " + dupInsn + ", expected: " + finallyInsn);
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -342,24 +378,29 @@ public class MarkFinallyVisitor extends AbstractVisitor {
|
|||||||
private static InsnsSlice isStartBlock(BlockNode dupBlock, BlockNode finallyBlock, FinallyExtractInfo extractInfo) {
|
private static InsnsSlice isStartBlock(BlockNode dupBlock, BlockNode finallyBlock, FinallyExtractInfo extractInfo) {
|
||||||
List<InsnNode> dupInsns = dupBlock.getInstructions();
|
List<InsnNode> dupInsns = dupBlock.getInstructions();
|
||||||
List<InsnNode> finallyInsns = finallyBlock.getInstructions();
|
List<InsnNode> finallyInsns = finallyBlock.getInstructions();
|
||||||
if (dupInsns.size() < finallyInsns.size()) {
|
int dupSize = dupInsns.size();
|
||||||
|
int finSize = finallyInsns.size();
|
||||||
|
if (dupSize < finSize) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
int startPos = dupInsns.size() - finallyInsns.size();
|
int startPos;
|
||||||
int endPos = 0;
|
int endPos = 0;
|
||||||
// fast check from end of block
|
if (dupSize == finSize) {
|
||||||
if (!checkInsns(dupInsns, finallyInsns, startPos)) {
|
if (!checkInsns(dupInsns, finallyInsns, 0)) {
|
||||||
// check from block start
|
return null;
|
||||||
if (checkInsns(dupInsns, finallyInsns, 0)) {
|
}
|
||||||
startPos = 0;
|
startPos = 0;
|
||||||
endPos = finallyInsns.size();
|
} else {
|
||||||
} else {
|
// dupSize > finSize
|
||||||
|
startPos = dupSize - finSize;
|
||||||
|
// fast check from end of block
|
||||||
|
if (!checkInsns(dupInsns, finallyInsns, startPos)) {
|
||||||
// search start insn
|
// search start insn
|
||||||
boolean found = false;
|
boolean found = false;
|
||||||
for (int i = 1; i < startPos; i++) {
|
for (int i = 1; i < startPos; i++) {
|
||||||
if (checkInsns(dupInsns, finallyInsns, i)) {
|
if (checkInsns(dupInsns, finallyInsns, i)) {
|
||||||
startPos = i;
|
startPos = i;
|
||||||
endPos = finallyInsns.size() + i;
|
endPos = finSize + i;
|
||||||
found = true;
|
found = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -379,7 +420,7 @@ public class MarkFinallyVisitor extends AbstractVisitor {
|
|||||||
// both slices completed
|
// both slices completed
|
||||||
complete = true;
|
complete = true;
|
||||||
} else {
|
} else {
|
||||||
endIndex = dupInsns.size();
|
endIndex = dupSize;
|
||||||
complete = false;
|
complete = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -393,9 +434,8 @@ public class MarkFinallyVisitor extends AbstractVisitor {
|
|||||||
if (finallySlice.isComplete()) {
|
if (finallySlice.isComplete()) {
|
||||||
// compare slices
|
// compare slices
|
||||||
if (finallySlice.getInsnsList().size() != slice.getInsnsList().size()) {
|
if (finallySlice.getInsnsList().size() != slice.getInsnsList().size()) {
|
||||||
if (LOG.isDebugEnabled()) {
|
extractInfo.getMth().addDebugComment(
|
||||||
LOG.debug("Another duplicated slice has different insns count: {}, finally: {}", slice, finallySlice);
|
"Another duplicated slice has different insns count: " + slice + ", finally: " + finallySlice);
|
||||||
}
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
// TODO: add additional slices checks
|
// TODO: add additional slices checks
|
||||||
@@ -522,7 +562,7 @@ public class MarkFinallyVisitor extends AbstractVisitor {
|
|||||||
DepthTraversal.visit(visitor, mth);
|
DepthTraversal.visit(visitor, mth);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error("Undo finally extract failed, mth: {}", mth, e);
|
mth.addError("Undo finally extract failed", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,9 +13,7 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import jadx.api.IDecompileScheduler;
|
import jadx.api.IDecompileScheduler;
|
||||||
import jadx.api.JadxDecompiler;
|
|
||||||
import jadx.api.JavaClass;
|
import jadx.api.JavaClass;
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
public class DecompilerScheduler implements IDecompileScheduler {
|
public class DecompilerScheduler implements IDecompileScheduler {
|
||||||
@@ -24,18 +22,11 @@ public class DecompilerScheduler implements IDecompileScheduler {
|
|||||||
private static final int MERGED_BATCH_SIZE = 16;
|
private static final int MERGED_BATCH_SIZE = 16;
|
||||||
private static final boolean DEBUG_BATCHES = false;
|
private static final boolean DEBUG_BATCHES = false;
|
||||||
|
|
||||||
private final JadxDecompiler decompiler;
|
|
||||||
|
|
||||||
public DecompilerScheduler(JadxDecompiler decompiler) {
|
|
||||||
this.decompiler = decompiler;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<List<JavaClass>> buildBatches(List<JavaClass> classes) {
|
public List<List<JavaClass>> buildBatches(List<JavaClass> classes) {
|
||||||
try {
|
try {
|
||||||
long start = System.currentTimeMillis();
|
long start = System.currentTimeMillis();
|
||||||
List<List<ClassNode>> batches = internalBatches(Utils.collectionMap(classes, JavaClass::getClassNode));
|
List<List<JavaClass>> result = internalBatches(classes);
|
||||||
List<List<JavaClass>> result = Utils.collectionMap(batches, l -> Utils.collectionMapNoNull(l, decompiler::getJavaClassByNode));
|
|
||||||
if (LOG.isDebugEnabled()) {
|
if (LOG.isDebugEnabled()) {
|
||||||
LOG.debug("Build decompilation batches in {}ms", System.currentTimeMillis() - start);
|
LOG.debug("Build decompilation batches in {}ms", System.currentTimeMillis() - start);
|
||||||
}
|
}
|
||||||
@@ -53,14 +44,14 @@ public class DecompilerScheduler implements IDecompileScheduler {
|
|||||||
* Put classes with many dependencies at the end.
|
* Put classes with many dependencies at the end.
|
||||||
* Build batches for dependencies of single class to avoid locking from another thread.
|
* Build batches for dependencies of single class to avoid locking from another thread.
|
||||||
*/
|
*/
|
||||||
public List<List<ClassNode>> internalBatches(List<ClassNode> classes) {
|
public List<List<JavaClass>> internalBatches(List<JavaClass> classes) {
|
||||||
List<DepInfo> deps = sumDependencies(classes);
|
List<DepInfo> deps = sumDependencies(classes);
|
||||||
Set<ClassNode> added = new HashSet<>(classes.size());
|
Set<JavaClass> added = new HashSet<>(classes.size());
|
||||||
Comparator<ClassNode> cmpDepSize = Comparator.comparingInt(ClassNode::getTotalDepsCount);
|
Comparator<JavaClass> cmpDepSize = Comparator.comparingInt(JavaClass::getTotalDepsCount);
|
||||||
List<List<ClassNode>> result = new ArrayList<>();
|
List<List<JavaClass>> result = new ArrayList<>();
|
||||||
List<ClassNode> mergedBatch = new ArrayList<>(MERGED_BATCH_SIZE);
|
List<JavaClass> mergedBatch = new ArrayList<>(MERGED_BATCH_SIZE);
|
||||||
for (DepInfo depInfo : deps) {
|
for (DepInfo depInfo : deps) {
|
||||||
ClassNode cls = depInfo.getCls();
|
JavaClass cls = depInfo.getCls();
|
||||||
if (!added.add(cls)) {
|
if (!added.add(cls)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -73,9 +64,9 @@ public class DecompilerScheduler implements IDecompileScheduler {
|
|||||||
mergedBatch = new ArrayList<>(MERGED_BATCH_SIZE);
|
mergedBatch = new ArrayList<>(MERGED_BATCH_SIZE);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
List<ClassNode> batch = new ArrayList<>(depsSize + 1);
|
List<JavaClass> batch = new ArrayList<>(depsSize + 1);
|
||||||
for (ClassNode dep : cls.getDependencies()) {
|
for (JavaClass dep : cls.getDependencies()) {
|
||||||
ClassNode topDep = dep.getTopParentClass();
|
JavaClass topDep = dep.getTopParentClass();
|
||||||
if (!added.contains(topDep)) {
|
if (!added.contains(topDep)) {
|
||||||
batch.add(topDep);
|
batch.add(topDep);
|
||||||
added.add(topDep);
|
added.add(topDep);
|
||||||
@@ -95,11 +86,11 @@ public class DecompilerScheduler implements IDecompileScheduler {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<DepInfo> sumDependencies(List<ClassNode> classes) {
|
private static List<DepInfo> sumDependencies(List<JavaClass> classes) {
|
||||||
List<DepInfo> deps = new ArrayList<>(classes.size());
|
List<DepInfo> deps = new ArrayList<>(classes.size());
|
||||||
for (ClassNode cls : classes) {
|
for (JavaClass cls : classes) {
|
||||||
int count = 0;
|
int count = 0;
|
||||||
for (ClassNode dep : cls.getDependencies()) {
|
for (JavaClass dep : cls.getDependencies()) {
|
||||||
count += 1 + dep.getTotalDepsCount();
|
count += 1 + dep.getTotalDepsCount();
|
||||||
}
|
}
|
||||||
deps.add(new DepInfo(cls, count));
|
deps.add(new DepInfo(cls, count));
|
||||||
@@ -109,15 +100,15 @@ public class DecompilerScheduler implements IDecompileScheduler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static final class DepInfo implements Comparable<DepInfo> {
|
private static final class DepInfo implements Comparable<DepInfo> {
|
||||||
private final ClassNode cls;
|
private final JavaClass cls;
|
||||||
private final int depsCount;
|
private final int depsCount;
|
||||||
|
|
||||||
private DepInfo(ClassNode cls, int depsCount) {
|
private DepInfo(JavaClass cls, int depsCount) {
|
||||||
this.cls = cls;
|
this.cls = cls;
|
||||||
this.depsCount = depsCount;
|
this.depsCount = depsCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ClassNode getCls() {
|
public JavaClass getCls() {
|
||||||
return cls;
|
return cls;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,7 +120,7 @@ public class DecompilerScheduler implements IDecompileScheduler {
|
|||||||
public int compareTo(@NotNull DecompilerScheduler.DepInfo o) {
|
public int compareTo(@NotNull DecompilerScheduler.DepInfo o) {
|
||||||
int deps = Integer.compare(depsCount, o.depsCount);
|
int deps = Integer.compare(depsCount, o.depsCount);
|
||||||
if (deps == 0) {
|
if (deps == 0) {
|
||||||
return cls.compareTo(o.cls);
|
return cls.getClassNode().compareTo(o.cls.getClassNode());
|
||||||
}
|
}
|
||||||
return deps;
|
return deps;
|
||||||
}
|
}
|
||||||
@@ -147,9 +138,9 @@ public class DecompilerScheduler implements IDecompileScheduler {
|
|||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void dumpBatchesStats(List<ClassNode> classes, List<List<ClassNode>> result, List<DepInfo> deps) {
|
private void dumpBatchesStats(List<JavaClass> classes, List<List<JavaClass>> result, List<DepInfo> deps) {
|
||||||
double avg = result.stream().mapToInt(List::size).average().orElse(-1);
|
double avg = result.stream().mapToInt(List::size).average().orElse(-1);
|
||||||
int maxSingleDeps = classes.stream().mapToInt(ClassNode::getTotalDepsCount).max().orElse(-1);
|
int maxSingleDeps = classes.stream().mapToInt(JavaClass::getTotalDepsCount).max().orElse(-1);
|
||||||
int maxSubDeps = deps.stream().mapToInt(DepInfo::getDepsCount).max().orElse(-1);
|
int maxSubDeps = deps.stream().mapToInt(DepInfo::getDepsCount).max().orElse(-1);
|
||||||
LOG.info("Batches stats:"
|
LOG.info("Batches stats:"
|
||||||
+ "\n input classes: " + classes.size()
|
+ "\n input classes: " + classes.size()
|
||||||
|
|||||||
@@ -428,7 +428,7 @@ public class Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void checkThreadInterrupt() {
|
public static void checkThreadInterrupt() {
|
||||||
if (Thread.interrupted()) {
|
if (Thread.currentThread().isInterrupted()) {
|
||||||
throw new JadxRuntimeException("Thread interrupted");
|
throw new JadxRuntimeException("Thread interrupted");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,6 +103,10 @@ public class FileUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void deleteFileIfExists(Path filePath) throws IOException {
|
||||||
|
Files.deleteIfExists(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean deleteDir(File dir) {
|
public static boolean deleteDir(File dir) {
|
||||||
File[] content = dir.listFiles();
|
File[] content = dir.listFiles();
|
||||||
if (content != null) {
|
if (content != null) {
|
||||||
@@ -115,17 +119,22 @@ public class FileUtils {
|
|||||||
|
|
||||||
public static void deleteDirIfExists(Path dir) {
|
public static void deleteDirIfExists(Path dir) {
|
||||||
if (Files.exists(dir)) {
|
if (Files.exists(dir)) {
|
||||||
deleteDir(dir);
|
try {
|
||||||
|
deleteDir(dir);
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.error("Failed to delete dir: " + dir.toAbsolutePath(), e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void deleteDir(Path dir) {
|
private static void deleteDir(Path dir) {
|
||||||
try (Stream<Path> pathStream = Files.walk(dir)) {
|
try (Stream<Path> pathStream = Files.walk(dir)) {
|
||||||
pathStream.sorted(Comparator.reverseOrder())
|
pathStream.sorted(Comparator.reverseOrder())
|
||||||
.map(Path::toFile)
|
.forEach(path -> {
|
||||||
.forEach(file -> {
|
try {
|
||||||
if (!file.delete()) {
|
Files.delete(path);
|
||||||
LOG.warn("Failed to remove file: {}", file.getAbsolutePath());
|
} catch (Exception e) {
|
||||||
|
throw new JadxRuntimeException("Failed to delete path: " + path.toAbsolutePath(), e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -152,11 +161,11 @@ public class FileUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void deleteTempRootDir() {
|
public static void deleteTempRootDir() {
|
||||||
deleteDir(TEMP_ROOT_DIR);
|
deleteDirIfExists(TEMP_ROOT_DIR);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void clearTempRootDir() {
|
public static void clearTempRootDir() {
|
||||||
deleteDir(TEMP_ROOT_DIR);
|
deleteDirIfExists(TEMP_ROOT_DIR);
|
||||||
makeDirs(TEMP_ROOT_DIR);
|
makeDirs(TEMP_ROOT_DIR);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,6 +226,15 @@ public class FileUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void writeFile(Path file, String data) throws IOException {
|
||||||
|
FileUtils.makeDirsForFile(file);
|
||||||
|
Files.write(file, data.getBytes(StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String readFile(Path textFile) throws IOException {
|
||||||
|
return new String(Files.readAllBytes(textFile), StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public static File prepareFile(File file) {
|
public static File prepareFile(File file) {
|
||||||
File saveFile = cutFileName(file);
|
File saveFile = cutFileName(file);
|
||||||
@@ -254,6 +272,28 @@ public class FileUtils {
|
|||||||
return new String(hexChars, StandardCharsets.UTF_8);
|
return new String(hexChars, StandardCharsets.UTF_8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zero padded hex string for first byte
|
||||||
|
*/
|
||||||
|
public static String byteToHex(int value) {
|
||||||
|
int v = value & 0xFF;
|
||||||
|
byte[] hexChars = new byte[] { HEX_ARRAY[v >>> 4], HEX_ARRAY[v & 0x0F] };
|
||||||
|
return new String(hexChars, StandardCharsets.US_ASCII);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zero padded hex string for int value
|
||||||
|
*/
|
||||||
|
public static String intToHex(int value) {
|
||||||
|
byte[] hexChars = new byte[8];
|
||||||
|
int v = value;
|
||||||
|
for (int i = 7; i >= 0; i--) {
|
||||||
|
hexChars[i] = HEX_ARRAY[v & 0x0F];
|
||||||
|
v >>>= 4;
|
||||||
|
}
|
||||||
|
return new String(hexChars, StandardCharsets.US_ASCII);
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean isZipFile(File file) {
|
public static boolean isZipFile(File file) {
|
||||||
try (InputStream is = new FileInputStream(file)) {
|
try (InputStream is = new FileInputStream(file)) {
|
||||||
byte[] headers = new byte[4];
|
byte[] headers = new byte[4];
|
||||||
|
|||||||
@@ -89,7 +89,8 @@ public class BinaryXMLParser extends CommonBinaryParser {
|
|||||||
is.mark(4);
|
is.mark(4);
|
||||||
int v = is.readInt16(); // version
|
int v = is.readInt16(); // version
|
||||||
int h = is.readInt16(); // header size
|
int h = is.readInt16(); // header size
|
||||||
if (v == 0x0003 && h == 0x0008) {
|
// Some APK Manifest.xml the version is 0
|
||||||
|
if (h == 0x0008) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
is.reset();
|
is.reset();
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
package jadx.api;
|
package jadx.api;
|
||||||
|
|
||||||
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
|
import jadx.core.dex.nodes.FieldNode;
|
||||||
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
import jadx.core.dex.nodes.RootNode;
|
import jadx.core.dex.nodes.RootNode;
|
||||||
|
|
||||||
public class JadxInternalAccess {
|
public class JadxInternalAccess {
|
||||||
@@ -7,4 +10,16 @@ public class JadxInternalAccess {
|
|||||||
public static RootNode getRoot(JadxDecompiler d) {
|
public static RootNode getRoot(JadxDecompiler d) {
|
||||||
return d.getRoot();
|
return d.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static JavaClass convertClassNode(JadxDecompiler d, ClassNode clsNode) {
|
||||||
|
return d.convertClassNode(clsNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JavaMethod convertMethodNode(JadxDecompiler d, MethodNode mthNode) {
|
||||||
|
return d.convertMethodNode(mthNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JavaField convertFieldNode(JadxDecompiler d, FieldNode fldNode) {
|
||||||
|
return d.convertFieldNode(fldNode);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,9 +43,6 @@ import jadx.api.args.DeobfuscationMapFileMode;
|
|||||||
import jadx.api.metadata.ICodeMetadata;
|
import jadx.api.metadata.ICodeMetadata;
|
||||||
import jadx.api.metadata.annotations.InsnCodeOffset;
|
import jadx.api.metadata.annotations.InsnCodeOffset;
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
import jadx.core.dex.attributes.AType;
|
|
||||||
import jadx.core.dex.attributes.IAttributeNode;
|
|
||||||
import jadx.core.dex.attributes.nodes.JadxCommentsAttr;
|
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
import jadx.core.dex.nodes.RootNode;
|
import jadx.core.dex.nodes.RootNode;
|
||||||
@@ -63,13 +60,11 @@ import jadx.tests.api.utils.TestUtils;
|
|||||||
import static org.apache.commons.lang3.StringUtils.leftPad;
|
import static org.apache.commons.lang3.StringUtils.leftPad;
|
||||||
import static org.apache.commons.lang3.StringUtils.rightPad;
|
import static org.apache.commons.lang3.StringUtils.rightPad;
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.hamcrest.Matchers.containsString;
|
|
||||||
import static org.hamcrest.Matchers.empty;
|
import static org.hamcrest.Matchers.empty;
|
||||||
import static org.hamcrest.Matchers.emptyArray;
|
import static org.hamcrest.Matchers.emptyArray;
|
||||||
import static org.hamcrest.Matchers.is;
|
import static org.hamcrest.Matchers.is;
|
||||||
import static org.hamcrest.Matchers.not;
|
import static org.hamcrest.Matchers.not;
|
||||||
import static org.hamcrest.Matchers.notNullValue;
|
import static org.hamcrest.Matchers.notNullValue;
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
import static org.junit.jupiter.api.Assertions.fail;
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
|
||||||
@@ -79,8 +74,6 @@ public abstract class IntegrationTest extends TestUtils {
|
|||||||
private static final String TEST_DIRECTORY = "src/test/java";
|
private static final String TEST_DIRECTORY = "src/test/java";
|
||||||
private static final String TEST_DIRECTORY2 = "jadx-core/" + TEST_DIRECTORY;
|
private static final String TEST_DIRECTORY2 = "jadx-core/" + TEST_DIRECTORY;
|
||||||
|
|
||||||
private static final String OUT_DIR = "test-out-tmp";
|
|
||||||
|
|
||||||
private static final String DEFAULT_INPUT_PLUGIN = "dx";
|
private static final String DEFAULT_INPUT_PLUGIN = "dx";
|
||||||
/**
|
/**
|
||||||
* Set 'TEST_INPUT_PLUGIN' env variable to use 'java' or 'dx' input in tests
|
* Set 'TEST_INPUT_PLUGIN' env variable to use 'java' or 'dx' input in tests
|
||||||
@@ -132,7 +125,7 @@ public abstract class IntegrationTest extends TestUtils {
|
|||||||
this.useJavaInput = null;
|
this.useJavaInput = null;
|
||||||
|
|
||||||
args = new JadxArgs();
|
args = new JadxArgs();
|
||||||
args.setOutDir(new File(OUT_DIR));
|
args.setOutDir(new File("test-out-tmp"));
|
||||||
args.setShowInconsistentCode(true);
|
args.setShowInconsistentCode(true);
|
||||||
args.setThreadsCount(1);
|
args.setThreadsCount(1);
|
||||||
args.setSkipResources(true);
|
args.setSkipResources(true);
|
||||||
@@ -156,6 +149,10 @@ public abstract class IntegrationTest extends TestUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setOutDirSuffix(String suffix) {
|
||||||
|
args.setOutDir(new File("test-out-" + suffix + "-tmp"));
|
||||||
|
}
|
||||||
|
|
||||||
public String getTestName() {
|
public String getTestName() {
|
||||||
return this.getClass().getSimpleName();
|
return this.getClass().getSimpleName();
|
||||||
}
|
}
|
||||||
@@ -287,7 +284,7 @@ public abstract class IntegrationTest extends TestUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void runChecks(List<ClassNode> clsList) {
|
protected void runChecks(List<ClassNode> clsList) {
|
||||||
clsList.forEach(this::checkCode);
|
clsList.forEach(cls -> checkCode(cls, allowWarnInCode));
|
||||||
compileClassNode(clsList);
|
compileClassNode(clsList);
|
||||||
clsList.forEach(this::runAutoCheck);
|
clsList.forEach(this::runAutoCheck);
|
||||||
}
|
}
|
||||||
@@ -345,33 +342,6 @@ public abstract class IntegrationTest extends TestUtils {
|
|||||||
root.processResources(resStorage);
|
root.processResources(resStorage);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void checkCode(ClassNode cls) {
|
|
||||||
assertFalse(hasErrors(cls), "Inconsistent cls: " + cls);
|
|
||||||
for (MethodNode mthNode : cls.getMethods()) {
|
|
||||||
if (hasErrors(mthNode)) {
|
|
||||||
fail("Method with problems: " + mthNode
|
|
||||||
+ "\n " + Utils.listToString(mthNode.getAttributesStringsList(), "\n "));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String code = cls.getCode().getCodeStr();
|
|
||||||
assertThat(code, not(containsString("inconsistent")));
|
|
||||||
assertThat(code, not(containsString("JADX ERROR")));
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean hasErrors(IAttributeNode node) {
|
|
||||||
if (node.contains(AFlag.INCONSISTENT_CODE) || node.contains(AType.JADX_ERROR)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!allowWarnInCode) {
|
|
||||||
JadxCommentsAttr commentsAttr = node.get(AType.JADX_COMMENTS);
|
|
||||||
if (commentsAttr != null) {
|
|
||||||
return commentsAttr.getComments().get(CommentsLevel.WARN) != null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void runAutoCheck(ClassNode cls) {
|
private void runAutoCheck(ClassNode cls) {
|
||||||
String clsName = cls.getClassInfo().getRawName().replace('/', '.');
|
String clsName = cls.getClassInfo().getRawName().replace('/', '.');
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import java.nio.file.Path;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
import javax.tools.DiagnosticListener;
|
import javax.tools.DiagnosticListener;
|
||||||
import javax.tools.JavaCompiler;
|
import javax.tools.JavaCompiler;
|
||||||
@@ -82,7 +81,7 @@ public class TestCompiler implements Closeable {
|
|||||||
arguments.addAll(options.getArguments());
|
arguments.addAll(options.getArguments());
|
||||||
|
|
||||||
DiagnosticListener<? super JavaFileObject> diagnostic =
|
DiagnosticListener<? super JavaFileObject> diagnostic =
|
||||||
diagObj -> System.out.println("Compiler diagnostic: " + diagObj.getMessage(Locale.ROOT));
|
diagObj -> System.out.println("Compiler diagnostic: " + diagObj);
|
||||||
Writer out = new PrintWriter(System.out);
|
Writer out = new PrintWriter(System.out);
|
||||||
CompilationTask compilerTask = compiler.getTask(out, fileManager, diagnostic, arguments, null, jfObjects);
|
CompilationTask compilerTask = compiler.getTask(out, fileManager, diagnostic, arguments, null, jfObjects);
|
||||||
if (Boolean.FALSE.equals(compilerTask.call())) {
|
if (Boolean.FALSE.equals(compilerTask.call())) {
|
||||||
|
|||||||
@@ -31,6 +31,10 @@ public enum TestProfile implements Consumer<IntegrationTest> {
|
|||||||
test.useTargetJavaVersion(11);
|
test.useTargetJavaVersion(11);
|
||||||
test.useJavaInput();
|
test.useJavaInput();
|
||||||
}),
|
}),
|
||||||
|
JAVA17("java-17", test -> {
|
||||||
|
test.useTargetJavaVersion(17);
|
||||||
|
test.useJavaInput();
|
||||||
|
}),
|
||||||
ECJ_DX_J8("ecj-dx-j8", test -> {
|
ECJ_DX_J8("ecj-dx-j8", test -> {
|
||||||
test.useEclipseCompiler();
|
test.useEclipseCompiler();
|
||||||
test.useTargetJavaVersion(8);
|
test.useTargetJavaVersion(8);
|
||||||
@@ -52,8 +56,9 @@ public enum TestProfile implements Consumer<IntegrationTest> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void accept(IntegrationTest integrationTest) {
|
public void accept(IntegrationTest test) {
|
||||||
this.setup.accept(integrationTest);
|
this.setup.accept(test);
|
||||||
|
test.setOutDirSuffix(description);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDescription() {
|
public String getDescription() {
|
||||||
|
|||||||
@@ -3,7 +3,21 @@ package jadx.tests.api.utils;
|
|||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
|
||||||
import jadx.NotYetImplementedExtension;
|
import jadx.NotYetImplementedExtension;
|
||||||
|
import jadx.api.CommentsLevel;
|
||||||
import jadx.api.ICodeWriter;
|
import jadx.api.ICodeWriter;
|
||||||
|
import jadx.core.dex.attributes.AFlag;
|
||||||
|
import jadx.core.dex.attributes.AType;
|
||||||
|
import jadx.core.dex.attributes.IAttributeNode;
|
||||||
|
import jadx.core.dex.attributes.nodes.JadxCommentsAttr;
|
||||||
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
|
import jadx.core.utils.Utils;
|
||||||
|
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.hamcrest.Matchers.containsString;
|
||||||
|
import static org.hamcrest.Matchers.not;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
|
||||||
@ExtendWith(NotYetImplementedExtension.class)
|
@ExtendWith(NotYetImplementedExtension.class)
|
||||||
public class TestUtils {
|
public class TestUtils {
|
||||||
@@ -35,4 +49,31 @@ public class TestUtils {
|
|||||||
}
|
}
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected static void checkCode(ClassNode cls, boolean allowWarnInCode) {
|
||||||
|
assertFalse(hasErrors(cls, allowWarnInCode), "Inconsistent cls: " + cls);
|
||||||
|
for (MethodNode mthNode : cls.getMethods()) {
|
||||||
|
if (hasErrors(mthNode, allowWarnInCode)) {
|
||||||
|
fail("Method with problems: " + mthNode
|
||||||
|
+ "\n " + Utils.listToString(mthNode.getAttributesStringsList(), "\n "));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String code = cls.getCode().getCodeStr();
|
||||||
|
assertThat(code, not(containsString("inconsistent")));
|
||||||
|
assertThat(code, not(containsString("JADX ERROR")));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static boolean hasErrors(IAttributeNode node, boolean allowWarnInCode) {
|
||||||
|
if (node.contains(AFlag.INCONSISTENT_CODE) || node.contains(AType.JADX_ERROR)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!allowWarnInCode) {
|
||||||
|
JadxCommentsAttr commentsAttr = node.get(AType.JADX_COMMENTS);
|
||||||
|
if (commentsAttr != null) {
|
||||||
|
return commentsAttr.getComments().get(CommentsLevel.WARN) != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,21 +13,24 @@ import jadx.api.ICodeWriter;
|
|||||||
import jadx.api.JadxArgs;
|
import jadx.api.JadxArgs;
|
||||||
import jadx.api.JadxDecompiler;
|
import jadx.api.JadxDecompiler;
|
||||||
import jadx.api.JadxInternalAccess;
|
import jadx.api.JadxInternalAccess;
|
||||||
|
import jadx.api.metadata.ICodeAnnotation;
|
||||||
import jadx.api.metadata.ICodeNodeRef;
|
import jadx.api.metadata.ICodeNodeRef;
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
import jadx.core.dex.nodes.RootNode;
|
import jadx.core.dex.nodes.RootNode;
|
||||||
import jadx.core.utils.DebugChecks;
|
import jadx.core.utils.DebugChecks;
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
import jadx.tests.api.IntegrationTest;
|
import jadx.tests.api.utils.TestUtils;
|
||||||
|
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.hamcrest.Matchers.greaterThan;
|
import static org.hamcrest.Matchers.greaterThan;
|
||||||
import static org.hamcrest.Matchers.is;
|
import static org.hamcrest.Matchers.is;
|
||||||
|
|
||||||
public abstract class BaseExternalTest extends IntegrationTest {
|
public abstract class BaseExternalTest extends TestUtils {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(BaseExternalTest.class);
|
private static final Logger LOG = LoggerFactory.getLogger(BaseExternalTest.class);
|
||||||
|
|
||||||
|
protected JadxDecompiler decompiler;
|
||||||
|
|
||||||
protected abstract String getSamplesDir();
|
protected abstract String getSamplesDir();
|
||||||
|
|
||||||
protected JadxArgs prepare(String inputFile) {
|
protected JadxArgs prepare(String inputFile) {
|
||||||
@@ -55,16 +58,16 @@ public abstract class BaseExternalTest extends IntegrationTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected JadxDecompiler decompile(JadxArgs jadxArgs, @Nullable String clsPatternStr, @Nullable String mthPatternStr) {
|
protected JadxDecompiler decompile(JadxArgs jadxArgs, @Nullable String clsPatternStr, @Nullable String mthPatternStr) {
|
||||||
JadxDecompiler jadx = new JadxDecompiler(jadxArgs);
|
decompiler = new JadxDecompiler(jadxArgs);
|
||||||
jadx.load();
|
decompiler.load();
|
||||||
|
|
||||||
if (clsPatternStr == null) {
|
if (clsPatternStr == null) {
|
||||||
jadx.save();
|
decompiler.save();
|
||||||
} else {
|
} else {
|
||||||
processByPatterns(jadx, clsPatternStr, mthPatternStr);
|
processByPatterns(decompiler, clsPatternStr, mthPatternStr);
|
||||||
}
|
}
|
||||||
printErrorReport(jadx);
|
printErrorReport(decompiler);
|
||||||
return jadx;
|
return decompiler;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processByPatterns(JadxDecompiler jadx, String clsPattern, @Nullable String mthPattern) {
|
private void processByPatterns(JadxDecompiler jadx, String clsPattern, @Nullable String mthPattern) {
|
||||||
@@ -109,7 +112,7 @@ public abstract class BaseExternalTest extends IntegrationTest {
|
|||||||
} else {
|
} else {
|
||||||
LOG.info("Code: \n{}", classNode.getCode());
|
LOG.info("Code: \n{}", classNode.getCode());
|
||||||
}
|
}
|
||||||
checkCode(classNode);
|
checkCode(classNode, false);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,7 +137,7 @@ public abstract class BaseExternalTest extends IntegrationTest {
|
|||||||
String dashLine = "======================================================================================";
|
String dashLine = "======================================================================================";
|
||||||
for (MethodNode mth : classNode.getMethods()) {
|
for (MethodNode mth : classNode.getMethods()) {
|
||||||
if (isMthMatch(mth, mthPattern)) {
|
if (isMthMatch(mth, mthPattern)) {
|
||||||
String mthCode = cutMethodCode(codeInfo, code, mth);
|
String mthCode = cutMethodCode(codeInfo, mth);
|
||||||
LOG.info("Print method: {}\n{}\n{}\n{}", mth.getMethodInfo().getShortId(),
|
LOG.info("Print method: {}\n{}\n{}\n{}", mth.getMethodInfo().getShortId(),
|
||||||
dashLine,
|
dashLine,
|
||||||
mthCode,
|
mthCode,
|
||||||
@@ -143,36 +146,33 @@ public abstract class BaseExternalTest extends IntegrationTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
private String cutMethodCode(ICodeInfo codeInfo, MethodNode mth) {
|
||||||
private String cutMethodCode(ICodeInfo codeInfo, String code, MethodNode mth) {
|
int startPos = getCommentStartPos(codeInfo, mth.getDefPosition());
|
||||||
int defPos = mth.getDefPosition();
|
int stopPos = getNextNodePos(mth, codeInfo);
|
||||||
int startPos = getCommentStartPos(code, defPos);
|
return codeInfo.getCodeStr().substring(startPos, stopPos);
|
||||||
ICodeNodeRef nodeBelow = codeInfo.getCodeMetadata().getNodeBelow(defPos);
|
|
||||||
int stopPos = nodeBelow == null ? code.length() : nodeBelow.getDefPosition();
|
|
||||||
int brackets = 0;
|
|
||||||
StringBuilder mthCode = new StringBuilder();
|
|
||||||
for (int i = startPos; i > 0 && i < stopPos;) {
|
|
||||||
int codePoint = code.codePointAt(i);
|
|
||||||
mthCode.appendCodePoint(codePoint);
|
|
||||||
if (i >= defPos) {
|
|
||||||
// also count brackets for detect method end
|
|
||||||
if (codePoint == '{') {
|
|
||||||
brackets++;
|
|
||||||
} else if (codePoint == '}') {
|
|
||||||
brackets--;
|
|
||||||
if (brackets <= 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
i += Character.charCount(codePoint);
|
|
||||||
}
|
|
||||||
return mthCode.toString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected int getCommentStartPos(String code, int pos) {
|
private int getNextNodePos(MethodNode mth, ICodeInfo codeInfo) {
|
||||||
|
int pos = mth.getDefPosition() + 1;
|
||||||
|
while (true) {
|
||||||
|
ICodeNodeRef nodeBelow = codeInfo.getCodeMetadata().getNodeBelow(pos);
|
||||||
|
if (nodeBelow == null) {
|
||||||
|
return codeInfo.getCodeStr().length();
|
||||||
|
}
|
||||||
|
if (nodeBelow.getAnnType() != ICodeAnnotation.AnnType.METHOD) {
|
||||||
|
return nodeBelow.getDefPosition();
|
||||||
|
}
|
||||||
|
MethodNode nodeMth = (MethodNode) nodeBelow;
|
||||||
|
if (nodeMth.getParentClass().equals(mth.getParentClass())) { // skip methods from anonymous classes
|
||||||
|
return getCommentStartPos(codeInfo, nodeMth.getDefPosition());
|
||||||
|
}
|
||||||
|
pos = nodeMth.getDefPosition() + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int getCommentStartPos(ICodeInfo codeInfo, int pos) {
|
||||||
String emptyLine = ICodeWriter.NL + ICodeWriter.NL;
|
String emptyLine = ICodeWriter.NL + ICodeWriter.NL;
|
||||||
int emptyLinePos = code.lastIndexOf(emptyLine, pos);
|
int emptyLinePos = codeInfo.getCodeStr().lastIndexOf(emptyLine, pos);
|
||||||
return emptyLinePos == -1 ? pos : emptyLinePos + emptyLine.length();
|
return emptyLinePos == -1 ? pos : emptyLinePos + emptyLine.length();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package jadx.tests.integration.invoke;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import jadx.tests.api.IntegrationTest;
|
||||||
|
|
||||||
|
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||||
|
|
||||||
|
public class TestSuperInvokeUnknown extends IntegrationTest {
|
||||||
|
|
||||||
|
public static class TestCls {
|
||||||
|
public static class BaseClass {
|
||||||
|
public int doSomething() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class NestedClass extends BaseClass {
|
||||||
|
@Override
|
||||||
|
public int doSomething() {
|
||||||
|
return super.doSomething();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
disableCompilation();
|
||||||
|
noDebugInfo();
|
||||||
|
assertThat(getClassNode(TestCls.NestedClass.class)) // BaseClass unknown
|
||||||
|
.code()
|
||||||
|
.containsOne("return super.doSomething();");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTopCls() {
|
||||||
|
noDebugInfo();
|
||||||
|
assertThat(getClassNode(TestCls.class))
|
||||||
|
.code()
|
||||||
|
.containsOne("return super.doSomething();");
|
||||||
|
}
|
||||||
|
}
|
||||||
+59
@@ -0,0 +1,59 @@
|
|||||||
|
package jadx.tests.integration.names;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
|
import jadx.tests.api.IntegrationTest;
|
||||||
|
|
||||||
|
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||||
|
|
||||||
|
public class TestCollisionWithJavaLangClasses extends IntegrationTest {
|
||||||
|
|
||||||
|
public static class TestCls1 {
|
||||||
|
public static class System {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
java.lang.System.out.println("Hello world");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test1() {
|
||||||
|
assertThat(getClassNode(TestCls1.class))
|
||||||
|
.code()
|
||||||
|
.containsOne("java.lang.System.out.println");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class TestCls2 {
|
||||||
|
public void doSomething() {
|
||||||
|
System.doSomething();
|
||||||
|
java.lang.System.out.println("Hello World");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class System {
|
||||||
|
public static void doSomething() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test2() {
|
||||||
|
assertThat(getClassNode(TestCls2.class))
|
||||||
|
.code()
|
||||||
|
.containsLine(2, "System.doSomething();")
|
||||||
|
.containsOne("java.lang.System.out.println");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test3() {
|
||||||
|
List<ClassNode> classes = getClassNodes(
|
||||||
|
jadx.tests.integration.names.pkg2.System.class,
|
||||||
|
jadx.tests.integration.names.pkg2.TestCls.class);
|
||||||
|
assertThat(searchCls(classes, "TestCls"))
|
||||||
|
.code()
|
||||||
|
.containsLine(2, "System.doSomething();")
|
||||||
|
.containsOne("java.lang.System.out.println");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package jadx.tests.integration.names.pkg2;
|
||||||
|
|
||||||
|
public class System {
|
||||||
|
public static void doSomething() {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package jadx.tests.integration.names.pkg2;
|
||||||
|
|
||||||
|
public class TestCls {
|
||||||
|
public void doSomething() {
|
||||||
|
System.doSomething();
|
||||||
|
java.lang.System.out.println("Hello World");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
package jadx.tests.integration.others;
|
package jadx.tests.integration.others;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import jadx.api.JadxInternalAccess;
|
||||||
import jadx.api.JavaClass;
|
import jadx.api.JavaClass;
|
||||||
import jadx.api.JavaMethod;
|
import jadx.api.JavaMethod;
|
||||||
import jadx.api.metadata.ICodeAnnotation;
|
import jadx.api.metadata.ICodeAnnotation;
|
||||||
@@ -46,8 +46,8 @@ public class TestCodeMetadata extends IntegrationTest {
|
|||||||
int callDefPos = callMth.getDefPosition();
|
int callDefPos = callMth.getDefPosition();
|
||||||
assertThat(callDefPos).isNotZero();
|
assertThat(callDefPos).isNotZero();
|
||||||
|
|
||||||
JavaClass javaClass = Objects.requireNonNull(jadxDecompiler.getJavaClassByNode(cls));
|
JavaClass javaClass = JadxInternalAccess.convertClassNode(jadxDecompiler, cls);
|
||||||
JavaMethod callJavaMethod = Objects.requireNonNull(jadxDecompiler.getJavaMethodByNode(callMth));
|
JavaMethod callJavaMethod = JadxInternalAccess.convertMethodNode(jadxDecompiler, callMth);
|
||||||
List<Integer> callUsePlaces = javaClass.getUsePlacesFor(javaClass.getCodeInfo(), callJavaMethod);
|
List<Integer> callUsePlaces = javaClass.getUsePlacesFor(javaClass.getCodeInfo(), callJavaMethod);
|
||||||
assertThat(callUsePlaces).hasSize(1);
|
assertThat(callUsePlaces).hasSize(1);
|
||||||
int callUse = callUsePlaces.get(0);
|
int callUse = callUsePlaces.get(0);
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package jadx.tests.integration.trycatch;
|
||||||
|
|
||||||
|
import jadx.tests.api.IntegrationTest;
|
||||||
|
import jadx.tests.api.extensions.profiles.TestProfile;
|
||||||
|
import jadx.tests.api.extensions.profiles.TestWithProfiles;
|
||||||
|
|
||||||
|
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||||
|
|
||||||
|
public class TestTryCatchFinally14 extends IntegrationTest {
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public static class TestCls {
|
||||||
|
private TCls t;
|
||||||
|
|
||||||
|
public void test() {
|
||||||
|
try {
|
||||||
|
if (t != null) {
|
||||||
|
t.doSomething();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (t != null) {
|
||||||
|
t.doFinally();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class TCls {
|
||||||
|
public void doSomething() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void doFinally() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestWithProfiles({ TestProfile.DX_J8, TestProfile.D8_J11, TestProfile.JAVA8 })
|
||||||
|
public void test() {
|
||||||
|
assertThat(getClassNode(TestCls.class))
|
||||||
|
.code()
|
||||||
|
.containsOne(".doSomething();")
|
||||||
|
.containsOne("} finally {")
|
||||||
|
.containsOne(".doFinally();")
|
||||||
|
.countString(2, "!= null) {");
|
||||||
|
}
|
||||||
|
}
|
||||||
-2
@@ -25,8 +25,6 @@ public class TestTryCatchMultiException extends IntegrationTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test() {
|
public void test() {
|
||||||
// printDisassemble();
|
|
||||||
// setFallback();
|
|
||||||
noDebugInfo();
|
noDebugInfo();
|
||||||
ClassNode cls = getClassNode(TestCls.class);
|
ClassNode cls = getClassNode(TestCls.class);
|
||||||
String code = cls.getCode().toString();
|
String code = cls.getCode().toString();
|
||||||
|
|||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
package jadx.tests.integration.trycatch;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import jadx.tests.api.SmaliTest;
|
||||||
|
|
||||||
|
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||||
|
|
||||||
|
@SuppressWarnings("CommentedOutCode")
|
||||||
|
public class TestTryCatchMultiException2 extends SmaliTest {
|
||||||
|
|
||||||
|
// @formatter:off
|
||||||
|
/*
|
||||||
|
public static boolean test() {
|
||||||
|
try {
|
||||||
|
Class<?> cls = Class.forName("c");
|
||||||
|
return ((Boolean) cls.getMethod("b", new Class[0]).invoke(cls, new Object[0])).booleanValue();
|
||||||
|
} catch (ClassNotFoundException | NoSuchMethodException | Exception | Throwable unused) {
|
||||||
|
// java compiler don't allow shadow subclasses in multi-catch
|
||||||
|
// in this case leave only Throwable
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
// @formatter:on
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
assertThat(getClassNodeFromSmali())
|
||||||
|
.code()
|
||||||
|
.containsOne("} catch (Throwable unused) {");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package jadx.tests.integration.types;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import jadx.tests.api.SmaliTest;
|
||||||
|
|
||||||
|
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Issue 1527
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("CommentedOutCode")
|
||||||
|
public class TestTypeResolver21 extends SmaliTest {
|
||||||
|
// @formatter:off
|
||||||
|
/*
|
||||||
|
public Number test(Object objectArray) {
|
||||||
|
Object[] arr = (Object[]) objectArray;
|
||||||
|
return (Number) arr[0];
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
// @formatter:on
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
assertThat(getClassNodeFromSmali())
|
||||||
|
.code()
|
||||||
|
.containsOne("Object[] arr = (Object[]) objectArray;");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package jadx.tests.integration.types;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import jadx.tests.api.IntegrationTest;
|
||||||
|
import jadx.tests.api.extensions.profiles.TestProfile;
|
||||||
|
import jadx.tests.api.extensions.profiles.TestWithProfiles;
|
||||||
|
|
||||||
|
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||||
|
|
||||||
|
public class TestTypeResolver22 extends IntegrationTest {
|
||||||
|
|
||||||
|
public static class TestCls {
|
||||||
|
public void test(InputStream input, long count) throws IOException {
|
||||||
|
long pos = input.skip(count);
|
||||||
|
while (pos < count) {
|
||||||
|
pos += input.skip(count - pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestWithProfiles({ TestProfile.JAVA8, TestProfile.DX_J8 })
|
||||||
|
public void test() {
|
||||||
|
assertThat(getClassNode(TestCls.class))
|
||||||
|
.code()
|
||||||
|
.containsOne("long pos = ");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package jadx.tests.integration.types;
|
||||||
|
|
||||||
|
import jadx.tests.api.IntegrationTest;
|
||||||
|
import jadx.tests.api.extensions.profiles.TestProfile;
|
||||||
|
import jadx.tests.api.extensions.profiles.TestWithProfiles;
|
||||||
|
|
||||||
|
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||||
|
|
||||||
|
public class TestTypeResolver23 extends IntegrationTest {
|
||||||
|
|
||||||
|
public static class TestCls {
|
||||||
|
public long test(int a) {
|
||||||
|
long v = 1L;
|
||||||
|
if (a == 2) {
|
||||||
|
v = 2L;
|
||||||
|
} else if (a == 3) {
|
||||||
|
v = 3L;
|
||||||
|
}
|
||||||
|
System.out.println(v);
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestWithProfiles({ TestProfile.JAVA8, TestProfile.DX_J8 })
|
||||||
|
public void test() {
|
||||||
|
assertThat(getClassNode(TestCls.class))
|
||||||
|
.code()
|
||||||
|
.containsOne("long v");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
.class public Ltrycatch/TestTryCatchMultiException2;
|
||||||
|
.super Ljava/lang/Object;
|
||||||
|
|
||||||
|
|
||||||
|
.method public static test()Z
|
||||||
|
.registers 5
|
||||||
|
|
||||||
|
:try_start_b
|
||||||
|
const-string v0, "c"
|
||||||
|
invoke-static {v0}, Ljava/lang/Class;->forName(Ljava/lang/String;)Ljava/lang/Class;
|
||||||
|
move-result-object v1
|
||||||
|
|
||||||
|
const/4 v0, 0x0
|
||||||
|
const-string v2, "b"
|
||||||
|
new-array v3, v0, [Ljava/lang/Class;
|
||||||
|
invoke-virtual {v1, v2, v3}, Ljava/lang/Class;->getMethod(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;
|
||||||
|
move-result-object v2
|
||||||
|
|
||||||
|
new-array v3, v0, [Ljava/lang/Object;
|
||||||
|
invoke-virtual {v2, v1, v3}, Ljava/lang/reflect/Method;->invoke(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;
|
||||||
|
move-result-object v1
|
||||||
|
|
||||||
|
check-cast v1, Ljava/lang/Boolean;
|
||||||
|
invoke-virtual {v1}, Ljava/lang/Boolean;->booleanValue()Z
|
||||||
|
|
||||||
|
move-result v1
|
||||||
|
:try_end_2f
|
||||||
|
.catch Ljava/lang/ClassNotFoundException; {:try_start_b .. :try_end_2f} :catch_30
|
||||||
|
.catch Ljava/lang/NoSuchMethodException; {:try_start_b .. :try_end_2f} :catch_30
|
||||||
|
.catch Ljava/lang/Exception; {:try_start_b .. :try_end_2f} :catch_30
|
||||||
|
.catchall {:try_start_b .. :try_end_2f} :catchall_30
|
||||||
|
|
||||||
|
return v1
|
||||||
|
|
||||||
|
:catch_30
|
||||||
|
:catchall_30
|
||||||
|
return v0
|
||||||
|
.end method
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
.class public Ltypes/TestTypeResolver21;
|
||||||
|
.super Ljava/lang/Object;
|
||||||
|
.source "TestTypeResolver21.java"
|
||||||
|
|
||||||
|
|
||||||
|
.method public test(Ljava/lang/Object;)Ljava/lang/Number;
|
||||||
|
.registers 4
|
||||||
|
.param p1, "objectArray" # Ljava/lang/Object;
|
||||||
|
|
||||||
|
.prologue
|
||||||
|
.line 16
|
||||||
|
check-cast p1, [Ljava/lang/Object;
|
||||||
|
.end local p1 # "objectArray":Ljava/lang/Object;
|
||||||
|
move-object v0, p1
|
||||||
|
check-cast v0, [Ljava/lang/Object;
|
||||||
|
|
||||||
|
.line 17
|
||||||
|
.local v0, "arr":[Ljava/lang/Object;
|
||||||
|
const/4 v1, 0x0
|
||||||
|
aget-object v1, v0, v1
|
||||||
|
check-cast v1, Ljava/lang/Number;
|
||||||
|
return-object v1
|
||||||
|
.end method
|
||||||
+20
-8
@@ -7,7 +7,6 @@ plugins {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(project(':jadx-core'))
|
implementation(project(':jadx-core'))
|
||||||
|
|
||||||
implementation(project(":jadx-cli"))
|
implementation(project(":jadx-cli"))
|
||||||
implementation 'com.beust:jcommander:1.82'
|
implementation 'com.beust:jcommander:1.82'
|
||||||
implementation 'ch.qos.logback:logback-classic:1.2.11'
|
implementation 'ch.qos.logback:logback-classic:1.2.11'
|
||||||
@@ -16,9 +15,9 @@ dependencies {
|
|||||||
implementation files('libs/jfontchooser-1.0.5.jar')
|
implementation files('libs/jfontchooser-1.0.5.jar')
|
||||||
implementation 'hu.kazocsaba:image-viewer:1.2.3'
|
implementation 'hu.kazocsaba:image-viewer:1.2.3'
|
||||||
|
|
||||||
implementation 'com.formdev:flatlaf:2.2'
|
implementation 'com.formdev:flatlaf:2.3'
|
||||||
implementation 'com.formdev:flatlaf-intellij-themes:2.2'
|
implementation 'com.formdev:flatlaf-intellij-themes:2.3'
|
||||||
implementation 'com.formdev:flatlaf-extras:2.2'
|
implementation 'com.formdev:flatlaf-extras:2.3'
|
||||||
implementation 'com.formdev:svgSalamander:1.1.3'
|
implementation 'com.formdev:svgSalamander:1.1.3'
|
||||||
|
|
||||||
implementation 'com.google.code.gson:gson:2.9.0'
|
implementation 'com.google.code.gson:gson:2.9.0'
|
||||||
@@ -30,6 +29,11 @@ dependencies {
|
|||||||
implementation 'com.android.tools.build:apksig:4.2.1'
|
implementation 'com.android.tools.build:apksig:4.2.1'
|
||||||
implementation 'io.github.hqktech:jdwp:1.0'
|
implementation 'io.github.hqktech:jdwp:1.0'
|
||||||
|
|
||||||
|
// TODO: Switch back to upstream once this PR gets merged:
|
||||||
|
// https://github.com/FabricMC/mapping-io/pull/19
|
||||||
|
// implementation 'net.fabricmc:mapping-io:0.3.0'
|
||||||
|
implementation files('libs/mapping-io-0.4.0-SNAPSHOT.jar')
|
||||||
|
|
||||||
testImplementation project(":jadx-core").sourceSets.test.output
|
testImplementation project(":jadx-core").sourceSets.test.output
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,7 +104,6 @@ runtime {
|
|||||||
addModules(
|
addModules(
|
||||||
'java.desktop',
|
'java.desktop',
|
||||||
'java.naming',
|
'java.naming',
|
||||||
//'java.sql', // TODO: GSON register adapter for java.sql.Time
|
|
||||||
'java.xml',
|
'java.xml',
|
||||||
)
|
)
|
||||||
jpackage {
|
jpackage {
|
||||||
@@ -113,10 +116,9 @@ runtime {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
task distWinWithJre(type: Zip, dependsOn: ['runtime', 'createExe']) {
|
task copyDistWinWithJre(type: Copy, dependsOn: ['runtime', 'createExe']) {
|
||||||
group 'jadx'
|
group 'jadx'
|
||||||
destinationDirectory = buildDir
|
destinationDir = new File(buildDir, "jadx-gui-${jadxVersion}-with-jre-win")
|
||||||
archiveFileName = "jadx-gui-${jadxVersion}-with-jre-win.zip"
|
|
||||||
from(runtime.jreDir) {
|
from(runtime.jreDir) {
|
||||||
include '**/*'
|
include '**/*'
|
||||||
into 'jre'
|
into 'jre'
|
||||||
@@ -126,3 +128,13 @@ task distWinWithJre(type: Zip, dependsOn: ['runtime', 'createExe']) {
|
|||||||
}
|
}
|
||||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
task distWinWithJre(type: Zip, dependsOn: ['copyDistWinWithJre']) {
|
||||||
|
group 'jadx'
|
||||||
|
destinationDirectory = buildDir
|
||||||
|
archiveFileName = "jadx-gui-${jadxVersion}-with-jre-win.zip"
|
||||||
|
from(copyDistWinWithJre.outputs) {
|
||||||
|
include '**/*'
|
||||||
|
}
|
||||||
|
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||||
|
}
|
||||||
|
|||||||
Binary file not shown.
@@ -1,29 +1,36 @@
|
|||||||
package jadx.gui;
|
package jadx.gui;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import jadx.api.ICodeCache;
|
import jadx.api.ICodeInfo;
|
||||||
import jadx.api.JadxArgs;
|
import jadx.api.JadxArgs;
|
||||||
import jadx.api.JadxDecompiler;
|
import jadx.api.JadxDecompiler;
|
||||||
import jadx.api.JavaClass;
|
import jadx.api.JavaClass;
|
||||||
|
import jadx.api.JavaNode;
|
||||||
import jadx.api.JavaPackage;
|
import jadx.api.JavaPackage;
|
||||||
import jadx.api.ResourceFile;
|
import jadx.api.ResourceFile;
|
||||||
import jadx.api.impl.InMemoryCodeCache;
|
import jadx.api.impl.InMemoryCodeCache;
|
||||||
|
import jadx.api.metadata.ICodeNodeRef;
|
||||||
|
import jadx.api.plugins.JadxPlugin;
|
||||||
|
import jadx.api.plugins.JadxPluginManager;
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
import jadx.core.dex.nodes.ProcessState;
|
import jadx.core.dex.nodes.ProcessState;
|
||||||
|
import jadx.core.dex.nodes.RootNode;
|
||||||
|
import jadx.core.dex.visitors.rename.RenameVisitor;
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
import jadx.core.utils.files.FileUtils;
|
import jadx.core.utils.files.FileUtils;
|
||||||
import jadx.gui.settings.JadxProject;
|
import jadx.gui.settings.JadxProject;
|
||||||
import jadx.gui.settings.JadxSettings;
|
import jadx.gui.settings.JadxSettings;
|
||||||
|
import jadx.gui.ui.MainWindow;
|
||||||
import jadx.gui.utils.codecache.CodeStringCache;
|
import jadx.gui.utils.codecache.CodeStringCache;
|
||||||
import jadx.gui.utils.codecache.disk.BufferCodeCache;
|
import jadx.gui.utils.codecache.disk.BufferCodeCache;
|
||||||
import jadx.gui.utils.codecache.disk.DiskCodeCache;
|
import jadx.gui.utils.codecache.disk.DiskCodeCache;
|
||||||
@@ -32,41 +39,41 @@ import static jadx.core.dex.nodes.ProcessState.GENERATED_AND_UNLOADED;
|
|||||||
import static jadx.core.dex.nodes.ProcessState.NOT_LOADED;
|
import static jadx.core.dex.nodes.ProcessState.NOT_LOADED;
|
||||||
import static jadx.core.dex.nodes.ProcessState.PROCESS_COMPLETE;
|
import static jadx.core.dex.nodes.ProcessState.PROCESS_COMPLETE;
|
||||||
|
|
||||||
|
@SuppressWarnings("ConstantConditions")
|
||||||
public class JadxWrapper {
|
public class JadxWrapper {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(JadxWrapper.class);
|
private static final Logger LOG = LoggerFactory.getLogger(JadxWrapper.class);
|
||||||
|
|
||||||
private final JadxSettings settings;
|
private static final Object DECOMPILER_UPDATE_SYNC = new Object();
|
||||||
private JadxDecompiler decompiler;
|
|
||||||
private @Nullable JadxProject project;
|
|
||||||
private List<Path> openPaths = Collections.emptyList();
|
|
||||||
|
|
||||||
public JadxWrapper(JadxSettings settings) {
|
private final MainWindow mainWindow;
|
||||||
this.settings = settings;
|
private volatile @Nullable JadxDecompiler decompiler;
|
||||||
this.decompiler = new JadxDecompiler(settings.toJadxArgs());
|
|
||||||
|
public JadxWrapper(MainWindow mainWindow) {
|
||||||
|
this.mainWindow = mainWindow;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void openFile(List<Path> paths) {
|
public void open() {
|
||||||
close();
|
close();
|
||||||
this.openPaths = paths;
|
|
||||||
try {
|
try {
|
||||||
JadxArgs jadxArgs = settings.toJadxArgs();
|
synchronized (DECOMPILER_UPDATE_SYNC) {
|
||||||
jadxArgs.setInputFiles(FileUtils.toFiles(paths));
|
JadxProject project = getProject();
|
||||||
if (project != null) {
|
JadxArgs jadxArgs = getSettings().toJadxArgs();
|
||||||
|
jadxArgs.setInputFiles(FileUtils.toFiles(project.getFilePaths()));
|
||||||
jadxArgs.setCodeData(project.getCodeData());
|
jadxArgs.setCodeData(project.getCodeData());
|
||||||
|
|
||||||
|
this.decompiler = new JadxDecompiler(jadxArgs);
|
||||||
|
this.decompiler.load();
|
||||||
|
initCodeCache();
|
||||||
}
|
}
|
||||||
closeCodeCache();
|
|
||||||
this.decompiler = new JadxDecompiler(jadxArgs);
|
|
||||||
this.decompiler.load();
|
|
||||||
initCodeCache(jadxArgs);
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error("Jadx init error", e);
|
LOG.error("Jadx decompiler wrapper init error", e);
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: check and move into core package
|
// TODO: check and move into core package
|
||||||
public void unloadClasses() {
|
public void unloadClasses() {
|
||||||
for (ClassNode cls : decompiler.getRoot().getClasses()) {
|
for (ClassNode cls : getDecompiler().getRoot().getClasses()) {
|
||||||
ProcessState clsState = cls.getState();
|
ProcessState clsState = cls.getState();
|
||||||
cls.unload();
|
cls.unload();
|
||||||
cls.setState(clsState == PROCESS_COMPLETE ? GENERATED_AND_UNLOADED : NOT_LOADED);
|
cls.setState(clsState == PROCESS_COMPLETE ? GENERATED_AND_UNLOADED : NOT_LOADED);
|
||||||
@@ -75,68 +82,50 @@ public class JadxWrapper {
|
|||||||
|
|
||||||
public void close() {
|
public void close() {
|
||||||
try {
|
try {
|
||||||
decompiler.close();
|
synchronized (DECOMPILER_UPDATE_SYNC) {
|
||||||
closeCodeCache();
|
if (decompiler != null) {
|
||||||
|
decompiler.close();
|
||||||
|
decompiler = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error("jadx decompiler close error", e);
|
LOG.error("Jadx decompiler close error", e);
|
||||||
|
} finally {
|
||||||
|
mainWindow.getCacheObject().reset();
|
||||||
}
|
}
|
||||||
this.openPaths = Collections.emptyList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initCodeCache(JadxArgs jadxArgs) {
|
private void initCodeCache() {
|
||||||
switch (settings.getCodeCacheMode()) {
|
switch (getSettings().getCodeCacheMode()) {
|
||||||
case MEMORY:
|
case MEMORY:
|
||||||
jadxArgs.setCodeCache(new InMemoryCodeCache());
|
getArgs().setCodeCache(new InMemoryCodeCache());
|
||||||
break;
|
break;
|
||||||
case DISK_WITH_CACHE:
|
case DISK_WITH_CACHE:
|
||||||
jadxArgs.setCodeCache(new CodeStringCache(buildBufferedDiskCache()));
|
getArgs().setCodeCache(new CodeStringCache(buildBufferedDiskCache()));
|
||||||
break;
|
break;
|
||||||
case DISK:
|
case DISK:
|
||||||
jadxArgs.setCodeCache(buildBufferedDiskCache());
|
getArgs().setCodeCache(buildBufferedDiskCache());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private BufferCodeCache buildBufferedDiskCache() {
|
private BufferCodeCache buildBufferedDiskCache() {
|
||||||
DiskCodeCache diskCache = new DiskCodeCache(decompiler.getRoot(), getCacheDir());
|
DiskCodeCache diskCache = new DiskCodeCache(getDecompiler().getRoot(), getProject().getCacheDir());
|
||||||
return new BufferCodeCache(diskCache);
|
return new BufferCodeCache(diskCache);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Path getCacheDir() {
|
|
||||||
if (project != null && project.getProjectPath() != null) {
|
|
||||||
Path projectPath = project.getProjectPath();
|
|
||||||
return projectPath.resolveSibling(projectPath.getFileName() + ".cache");
|
|
||||||
}
|
|
||||||
if (!openPaths.isEmpty()) {
|
|
||||||
Path path = openPaths.get(0);
|
|
||||||
return path.resolveSibling(path.getFileName() + ".cache");
|
|
||||||
}
|
|
||||||
throw new JadxRuntimeException("Can't get working dir");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void closeCodeCache() {
|
|
||||||
ICodeCache codeCache = getArgs().getCodeCache();
|
|
||||||
if (codeCache != null) {
|
|
||||||
try {
|
|
||||||
codeCache.close();
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new JadxRuntimeException("Error on cache close", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the complete list of classes
|
* Get the complete list of classes
|
||||||
*/
|
*/
|
||||||
public List<JavaClass> getClasses() {
|
public List<JavaClass> getClasses() {
|
||||||
return decompiler.getClasses();
|
return getDecompiler().getClasses();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all classes that are not excluded by the excluded packages settings
|
* Get all classes that are not excluded by the excluded packages settings
|
||||||
*/
|
*/
|
||||||
public List<JavaClass> getIncludedClasses() {
|
public List<JavaClass> getIncludedClasses() {
|
||||||
List<JavaClass> classList = decompiler.getClasses();
|
List<JavaClass> classList = getDecompiler().getClasses();
|
||||||
List<String> excludedPackages = getExcludedPackages();
|
List<String> excludedPackages = getExcludedPackages();
|
||||||
if (excludedPackages.isEmpty()) {
|
if (excludedPackages.isEmpty()) {
|
||||||
return classList;
|
return classList;
|
||||||
@@ -150,7 +139,7 @@ public class JadxWrapper {
|
|||||||
* Get all classes that are not excluded by the excluded packages settings including inner classes
|
* Get all classes that are not excluded by the excluded packages settings including inner classes
|
||||||
*/
|
*/
|
||||||
public List<JavaClass> getIncludedClassesWithInners() {
|
public List<JavaClass> getIncludedClassesWithInners() {
|
||||||
List<JavaClass> classes = decompiler.getClassesWithInners();
|
List<JavaClass> classes = getDecompiler().getClassesWithInners();
|
||||||
List<String> excludedPackages = getExcludedPackages();
|
List<String> excludedPackages = getExcludedPackages();
|
||||||
if (excludedPackages.isEmpty()) {
|
if (excludedPackages.isEmpty()) {
|
||||||
return classes;
|
return classes;
|
||||||
@@ -172,58 +161,99 @@ public class JadxWrapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public List<List<JavaClass>> buildDecompileBatches(List<JavaClass> classes) {
|
public List<List<JavaClass>> buildDecompileBatches(List<JavaClass> classes) {
|
||||||
return decompiler.getDecompileScheduler().buildBatches(classes);
|
return getDecompiler().getDecompileScheduler().buildBatches(classes);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: move to CLI and filter classes in JadxDecompiler
|
// TODO: move to CLI and filter classes in JadxDecompiler
|
||||||
public List<String> getExcludedPackages() {
|
public List<String> getExcludedPackages() {
|
||||||
String excludedPackages = settings.getExcludedPackages().trim();
|
String excludedPackages = getSettings().getExcludedPackages().trim();
|
||||||
if (excludedPackages.isEmpty()) {
|
if (excludedPackages.isEmpty()) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
return Arrays.asList(excludedPackages.split("[ ]+"));
|
return Arrays.asList(excludedPackages.split(" +"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setExcludedPackages(List<String> packagesToExclude) {
|
public void setExcludedPackages(List<String> packagesToExclude) {
|
||||||
settings.setExcludedPackages(String.join(" ", packagesToExclude).trim());
|
getSettings().setExcludedPackages(String.join(" ", packagesToExclude).trim());
|
||||||
settings.sync();
|
getSettings().sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addExcludedPackage(String packageToExclude) {
|
public void addExcludedPackage(String packageToExclude) {
|
||||||
String newExclusion = settings.getExcludedPackages() + ' ' + packageToExclude;
|
String newExclusion = getSettings().getExcludedPackages() + ' ' + packageToExclude;
|
||||||
settings.setExcludedPackages(newExclusion.trim());
|
getSettings().setExcludedPackages(newExclusion.trim());
|
||||||
settings.sync();
|
getSettings().sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeExcludedPackage(String packageToRemoveFromExclusion) {
|
public void removeExcludedPackage(String packageToRemoveFromExclusion) {
|
||||||
List<String> list = new ArrayList<>(getExcludedPackages());
|
List<String> list = new ArrayList<>(getExcludedPackages());
|
||||||
list.remove(packageToRemoveFromExclusion);
|
list.remove(packageToRemoveFromExclusion);
|
||||||
settings.setExcludedPackages(String.join(" ", list));
|
getSettings().setExcludedPackages(String.join(" ", list));
|
||||||
settings.sync();
|
getSettings().sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<JavaPackage> getPackages() {
|
public List<JadxPlugin> getAllPlugins() {
|
||||||
return decompiler.getPackages();
|
if (decompiler != null) {
|
||||||
|
return decompiler.getPluginManager().getAllPlugins();
|
||||||
|
}
|
||||||
|
JadxPluginManager pluginManager = new JadxPluginManager();
|
||||||
|
pluginManager.load();
|
||||||
|
return pluginManager.getAllPlugins();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<ResourceFile> getResources() {
|
/**
|
||||||
return decompiler.getResources();
|
* TODO: make method private
|
||||||
}
|
* Do not store JadxDecompiler in fields to not leak old instances
|
||||||
|
*/
|
||||||
public List<Path> getOpenPaths() {
|
public @NotNull JadxDecompiler getDecompiler() {
|
||||||
return openPaths;
|
if (decompiler == null || decompiler.getRoot() == null) {
|
||||||
}
|
throw new JadxRuntimeException("Decompiler not yet loaded");
|
||||||
|
}
|
||||||
public JadxDecompiler getDecompiler() {
|
|
||||||
return decompiler;
|
return decompiler;
|
||||||
}
|
}
|
||||||
|
|
||||||
public JadxArgs getArgs() {
|
// TODO: forbid usage of this method
|
||||||
return decompiler.getArgs();
|
public RootNode getRootNode() {
|
||||||
|
return getDecompiler().getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setProject(JadxProject project) {
|
public void reInitRenameVisitor() {
|
||||||
this.project = project;
|
new RenameVisitor().init(getRootNode());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reloadCodeData() {
|
||||||
|
getDecompiler().reloadCodeData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public JavaNode getJavaNodeByRef(ICodeNodeRef nodeRef) {
|
||||||
|
return getDecompiler().getJavaNodeByRef(nodeRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
public JavaNode getEnclosingNode(ICodeInfo codeInfo, int pos) {
|
||||||
|
return getDecompiler().getEnclosingNode(codeInfo, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Runnable> getSaveTasks() {
|
||||||
|
return getDecompiler().getSaveTasks();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<JavaPackage> getPackages() {
|
||||||
|
return getDecompiler().getPackages();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ResourceFile> getResources() {
|
||||||
|
return getDecompiler().getResources();
|
||||||
|
}
|
||||||
|
|
||||||
|
public JadxArgs getArgs() {
|
||||||
|
return getDecompiler().getArgs();
|
||||||
|
}
|
||||||
|
|
||||||
|
public JadxProject getProject() {
|
||||||
|
return mainWindow.getProject();
|
||||||
|
}
|
||||||
|
|
||||||
|
public JadxSettings getSettings() {
|
||||||
|
return mainWindow.getSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -231,14 +261,14 @@ public class JadxWrapper {
|
|||||||
* Full name of an outer class. Inner classes are not supported.
|
* Full name of an outer class. Inner classes are not supported.
|
||||||
*/
|
*/
|
||||||
public @Nullable JavaClass searchJavaClassByFullAlias(String fullName) {
|
public @Nullable JavaClass searchJavaClassByFullAlias(String fullName) {
|
||||||
return decompiler.getClasses().stream()
|
return getDecompiler().getClasses().stream()
|
||||||
.filter(cls -> cls.getFullName().equals(fullName))
|
.filter(cls -> cls.getFullName().equals(fullName))
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public @Nullable JavaClass searchJavaClassByOrigClassName(String fullName) {
|
public @Nullable JavaClass searchJavaClassByOrigClassName(String fullName) {
|
||||||
return decompiler.searchJavaClassByOrigFullName(fullName);
|
return getDecompiler().searchJavaClassByOrigFullName(fullName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -246,7 +276,7 @@ public class JadxWrapper {
|
|||||||
* Full raw name of an outer class. Inner classes are not supported.
|
* Full raw name of an outer class. Inner classes are not supported.
|
||||||
*/
|
*/
|
||||||
public @Nullable JavaClass searchJavaClassByRawName(String rawName) {
|
public @Nullable JavaClass searchJavaClassByRawName(String rawName) {
|
||||||
return decompiler.getClasses().stream()
|
return getDecompiler().getClasses().stream()
|
||||||
.filter(cls -> cls.getRawName().equals(rawName))
|
.filter(cls -> cls.getRawName().equals(rawName))
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ public class DbgUtils {
|
|||||||
// TODO: parse AndroidManifest.xml instead of looking for keywords
|
// TODO: parse AndroidManifest.xml instead of looking for keywords
|
||||||
private static String getManifestContent(MainWindow mainWindow) {
|
private static String getManifestContent(MainWindow mainWindow) {
|
||||||
try {
|
try {
|
||||||
ResourceFile androidManifest = mainWindow.getWrapper().getDecompiler().getResources()
|
ResourceFile androidManifest = mainWindow.getWrapper().getResources()
|
||||||
.stream()
|
.stream()
|
||||||
.filter(res -> res.getType() == ResourceType.MANIFEST)
|
.filter(res -> res.getType() == ResourceType.MANIFEST)
|
||||||
.findFirst()
|
.findFirst()
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package jadx.gui.jobs;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
@@ -11,6 +12,7 @@ import java.util.concurrent.TimeUnit;
|
|||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import javax.swing.SwingUtilities;
|
import javax.swing.SwingUtilities;
|
||||||
import javax.swing.SwingWorker;
|
import javax.swing.SwingWorker;
|
||||||
@@ -19,6 +21,7 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
import jadx.gui.settings.JadxSettings;
|
||||||
import jadx.gui.ui.MainWindow;
|
import jadx.gui.ui.MainWindow;
|
||||||
import jadx.gui.ui.panel.ProgressPanel;
|
import jadx.gui.ui.panel.ProgressPanel;
|
||||||
import jadx.gui.utils.NLS;
|
import jadx.gui.utils.NLS;
|
||||||
@@ -33,16 +36,16 @@ import static jadx.gui.utils.UiUtils.calcProgress;
|
|||||||
public class BackgroundExecutor {
|
public class BackgroundExecutor {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(BackgroundExecutor.class);
|
private static final Logger LOG = LoggerFactory.getLogger(BackgroundExecutor.class);
|
||||||
|
|
||||||
private final MainWindow mainWindow;
|
private final JadxSettings settings;
|
||||||
private final ProgressPanel progressPane;
|
private final ProgressPanel progressPane;
|
||||||
|
|
||||||
private ThreadPoolExecutor taskQueueExecutor;
|
private ThreadPoolExecutor taskQueueExecutor;
|
||||||
private final Map<Long, IBackgroundTask> taskRunning = new ConcurrentHashMap<>();
|
private final Map<Long, IBackgroundTask> taskRunning = new ConcurrentHashMap<>();
|
||||||
private final AtomicLong idSupplier = new AtomicLong(0);
|
private final AtomicLong idSupplier = new AtomicLong(0);
|
||||||
|
|
||||||
public BackgroundExecutor(MainWindow mainWindow) {
|
public BackgroundExecutor(JadxSettings settings, ProgressPanel progressPane) {
|
||||||
this.mainWindow = mainWindow;
|
this.settings = Objects.requireNonNull(settings);
|
||||||
this.progressPane = mainWindow.getProgressPane();
|
this.progressPane = Objects.requireNonNull(progressPane);
|
||||||
reset();
|
reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,9 +71,16 @@ public class BackgroundExecutor {
|
|||||||
public synchronized void cancelAll() {
|
public synchronized void cancelAll() {
|
||||||
try {
|
try {
|
||||||
taskRunning.values().forEach(Cancelable::cancel);
|
taskRunning.values().forEach(Cancelable::cancel);
|
||||||
taskQueueExecutor.shutdown();
|
taskQueueExecutor.shutdownNow();
|
||||||
boolean complete = taskQueueExecutor.awaitTermination(30, TimeUnit.SECONDS);
|
boolean complete = taskQueueExecutor.awaitTermination(3, TimeUnit.SECONDS);
|
||||||
LOG.debug("Background task executor terminated with status: {}", complete ? "complete" : "interrupted");
|
if (complete) {
|
||||||
|
LOG.debug("Background task executor canceled successfully");
|
||||||
|
} else {
|
||||||
|
String taskNames = taskRunning.values().stream()
|
||||||
|
.map(IBackgroundTask::getTitle)
|
||||||
|
.collect(Collectors.joining(", "));
|
||||||
|
LOG.debug("Background task executor cancel failed. Running tasks: {}", taskNames);
|
||||||
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error("Error terminating task executor", e);
|
LOG.error("Error terminating task executor", e);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -131,8 +141,17 @@ public class BackgroundExecutor {
|
|||||||
try {
|
try {
|
||||||
runJobs();
|
runJobs();
|
||||||
} finally {
|
} finally {
|
||||||
taskComplete(id);
|
try {
|
||||||
task.onDone(this);
|
task.onDone(this);
|
||||||
|
// treat UI task operations as part of the task to not mix with others
|
||||||
|
UiUtils.uiRunAndWait(() -> {
|
||||||
|
task.onFinish(this);
|
||||||
|
progressPane.setVisible(false);
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
taskComplete(id);
|
||||||
|
progressPane.changeVisibility(this, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
@@ -146,7 +165,7 @@ public class BackgroundExecutor {
|
|||||||
progressPane.changeVisibility(this, true);
|
progressPane.changeVisibility(this, true);
|
||||||
}
|
}
|
||||||
status = TaskStatus.STARTED;
|
status = TaskStatus.STARTED;
|
||||||
int threadsCount = mainWindow.getSettings().getThreadsCount();
|
int threadsCount = settings.getThreadsCount();
|
||||||
executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(threadsCount);
|
executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(threadsCount);
|
||||||
for (Runnable job : jobs) {
|
for (Runnable job : jobs) {
|
||||||
executor.execute(job);
|
executor.execute(job);
|
||||||
@@ -212,8 +231,15 @@ public class BackgroundExecutor {
|
|||||||
// force termination
|
// force termination
|
||||||
task.cancel();
|
task.cancel();
|
||||||
executor.shutdown();
|
executor.shutdown();
|
||||||
|
if (executor.awaitTermination(2, TimeUnit.SECONDS)) {
|
||||||
|
LOG.debug("Task cancel complete");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LOG.debug("Forcing tasks cancel");
|
||||||
|
executor.shutdownNow();
|
||||||
boolean complete = executor.awaitTermination(5, TimeUnit.SECONDS);
|
boolean complete = executor.awaitTermination(5, TimeUnit.SECONDS);
|
||||||
LOG.debug("Task cancel complete: {}", complete ? "success" : "aborted");
|
LOG.debug("Forced task cancel status: {}",
|
||||||
|
complete ? "success" : "fail, still active: " + executor.getActiveCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Supplier<TaskStatus> buildCancelCheck(long startTime) {
|
private Supplier<TaskStatus> buildCancelCheck(long startTime) {
|
||||||
@@ -251,12 +277,6 @@ public class BackgroundExecutor {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void done() {
|
|
||||||
progressPane.setVisible(false);
|
|
||||||
task.onFinish(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TaskStatus getStatus() {
|
public TaskStatus getStatus() {
|
||||||
return status;
|
return status;
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import java.util.List;
|
|||||||
import javax.swing.JOptionPane;
|
import javax.swing.JOptionPane;
|
||||||
|
|
||||||
import jadx.api.ICodeCache;
|
import jadx.api.ICodeCache;
|
||||||
import jadx.api.JadxDecompiler;
|
|
||||||
import jadx.gui.JadxWrapper;
|
import jadx.gui.JadxWrapper;
|
||||||
import jadx.gui.ui.MainWindow;
|
import jadx.gui.ui.MainWindow;
|
||||||
import jadx.gui.utils.NLS;
|
import jadx.gui.utils.NLS;
|
||||||
@@ -35,9 +34,8 @@ public class ExportTask extends CancelableBackgroundTask {
|
|||||||
@Override
|
@Override
|
||||||
public List<Runnable> scheduleJobs() {
|
public List<Runnable> scheduleJobs() {
|
||||||
wrapCodeCache();
|
wrapCodeCache();
|
||||||
JadxDecompiler decompiler = wrapper.getDecompiler();
|
wrapper.getArgs().setRootDir(saveDir);
|
||||||
decompiler.getArgs().setRootDir(saveDir);
|
List<Runnable> saveTasks = wrapper.getSaveTasks();
|
||||||
List<Runnable> saveTasks = decompiler.getSaveTasks();
|
|
||||||
this.timeLimit = DecompileTask.calcDecompileTimeLimit(saveTasks.size());
|
this.timeLimit = DecompileTask.calcDecompileTimeLimit(saveTasks.size());
|
||||||
return saveTasks;
|
return saveTasks;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,285 @@
|
|||||||
|
package jadx.gui.plugins.mappings;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.AbstractMap.SimpleEntry;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import net.fabricmc.mappingio.MappedElementKind;
|
||||||
|
import net.fabricmc.mappingio.MappingWriter;
|
||||||
|
import net.fabricmc.mappingio.format.MappingFormat;
|
||||||
|
import net.fabricmc.mappingio.tree.MemoryMappingTree;
|
||||||
|
|
||||||
|
import jadx.api.ICodeInfo;
|
||||||
|
import jadx.api.data.ICodeComment;
|
||||||
|
import jadx.api.data.ICodeRename;
|
||||||
|
import jadx.api.data.IJavaNodeRef.RefType;
|
||||||
|
import jadx.api.data.impl.JadxCodeData;
|
||||||
|
import jadx.api.data.impl.JadxCodeRef;
|
||||||
|
import jadx.api.metadata.ICodeNodeRef;
|
||||||
|
import jadx.api.metadata.annotations.InsnCodeOffset;
|
||||||
|
import jadx.api.metadata.annotations.NodeDeclareRef;
|
||||||
|
import jadx.api.metadata.annotations.VarNode;
|
||||||
|
import jadx.api.utils.CodeUtils;
|
||||||
|
import jadx.core.Consts;
|
||||||
|
import jadx.core.codegen.TypeGen;
|
||||||
|
import jadx.core.dex.info.ClassInfo;
|
||||||
|
import jadx.core.dex.info.FieldInfo;
|
||||||
|
import jadx.core.dex.info.MethodInfo;
|
||||||
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
|
import jadx.core.dex.nodes.FieldNode;
|
||||||
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
|
import jadx.core.dex.nodes.RootNode;
|
||||||
|
import jadx.core.utils.files.FileUtils;
|
||||||
|
|
||||||
|
public class MappingExporter {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(MappingExporter.class);
|
||||||
|
private final RootNode root;
|
||||||
|
|
||||||
|
public MappingExporter(RootNode rootNode) {
|
||||||
|
this.root = rootNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<VarNode> collectMethodArgs(MethodNode methodNode) {
|
||||||
|
ICodeInfo codeInfo = methodNode.getTopParentClass().getCode();
|
||||||
|
int mthDefPos = methodNode.getDefPosition();
|
||||||
|
int lineEndPos = CodeUtils.getLineEndForPos(codeInfo.getCodeStr(), mthDefPos);
|
||||||
|
List<VarNode> args = new ArrayList<>();
|
||||||
|
codeInfo.getCodeMetadata().searchDown(mthDefPos, (pos, ann) -> {
|
||||||
|
if (pos > lineEndPos) {
|
||||||
|
// Stop at line end
|
||||||
|
return Boolean.TRUE;
|
||||||
|
}
|
||||||
|
if (ann instanceof NodeDeclareRef) {
|
||||||
|
ICodeNodeRef declRef = ((NodeDeclareRef) ann).getNode();
|
||||||
|
if (declRef instanceof VarNode) {
|
||||||
|
VarNode varNode = (VarNode) declRef;
|
||||||
|
if (!varNode.getMth().equals(methodNode)) {
|
||||||
|
// Stop if we've gone too far and have entered a different method
|
||||||
|
return Boolean.TRUE;
|
||||||
|
}
|
||||||
|
args.add(varNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<SimpleEntry<VarNode, Integer>> collectMethodVars(MethodNode methodNode) {
|
||||||
|
ICodeInfo codeInfo = methodNode.getTopParentClass().getCode();
|
||||||
|
int mthDefPos = methodNode.getDefPosition();
|
||||||
|
int mthLineEndPos = CodeUtils.getLineEndForPos(codeInfo.getCodeStr(), mthDefPos);
|
||||||
|
|
||||||
|
List<SimpleEntry<VarNode, Integer>> vars = new ArrayList<>();
|
||||||
|
AtomicInteger lastOffset = new AtomicInteger(-1);
|
||||||
|
codeInfo.getCodeMetadata().searchDown(mthLineEndPos, (pos, ann) -> {
|
||||||
|
if (ann instanceof InsnCodeOffset) {
|
||||||
|
lastOffset.set(((InsnCodeOffset) ann).getOffset());
|
||||||
|
}
|
||||||
|
if (ann instanceof NodeDeclareRef) {
|
||||||
|
ICodeNodeRef declRef = ((NodeDeclareRef) ann).getNode();
|
||||||
|
if (declRef instanceof VarNode) {
|
||||||
|
VarNode varNode = (VarNode) declRef;
|
||||||
|
if (!varNode.getMth().equals(methodNode)) {
|
||||||
|
// Stop if we've gone too far and have entered a different method
|
||||||
|
return Boolean.TRUE;
|
||||||
|
}
|
||||||
|
if (lastOffset.get() != -1) {
|
||||||
|
vars.add(new SimpleEntry<VarNode, Integer>(varNode, lastOffset.get()));
|
||||||
|
} else {
|
||||||
|
LOG.warn("Local variable not present in bytecode, skipping: "
|
||||||
|
+ methodNode.getMethodInfo().getRawFullId() + "#" + varNode.getName());
|
||||||
|
}
|
||||||
|
lastOffset.set(-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
return vars;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void exportMappings(Path path, JadxCodeData codeData, MappingFormat mappingFormat) {
|
||||||
|
MemoryMappingTree mappingTree = new MemoryMappingTree();
|
||||||
|
// Map < SrcName >
|
||||||
|
Set<String> mappedClasses = new HashSet<>();
|
||||||
|
// Map < DeclClass + ShortId >
|
||||||
|
Set<String> mappedFields = new HashSet<>();
|
||||||
|
Set<String> mappedMethods = new HashSet<>();
|
||||||
|
Set<String> methodsWithMappedElements = new HashSet<>();
|
||||||
|
// Map < DeclClass + MethodShortId + CodeRef, NewName >
|
||||||
|
Map<String, String> mappedMethodArgsAndVars = new HashMap<>();
|
||||||
|
// Map < DeclClass + *ShortId + *CodeRef, Comment >
|
||||||
|
Map<String, String> comments = new HashMap<>();
|
||||||
|
|
||||||
|
// We have to do this so we know for sure which elements are *manually* renamed
|
||||||
|
for (ICodeRename codeRename : codeData.getRenames()) {
|
||||||
|
if (codeRename.getNodeRef().getType().equals(RefType.CLASS)) {
|
||||||
|
mappedClasses.add(codeRename.getNodeRef().getDeclaringClass());
|
||||||
|
} else if (codeRename.getNodeRef().getType().equals(RefType.FIELD)) {
|
||||||
|
mappedFields.add(codeRename.getNodeRef().getDeclaringClass() + codeRename.getNodeRef().getShortId());
|
||||||
|
} else if (codeRename.getNodeRef().getType().equals(RefType.METHOD)) {
|
||||||
|
if (codeRename.getCodeRef() == null) {
|
||||||
|
mappedMethods.add(codeRename.getNodeRef().getDeclaringClass() + codeRename.getNodeRef().getShortId());
|
||||||
|
} else {
|
||||||
|
methodsWithMappedElements.add(codeRename.getNodeRef().getDeclaringClass() + codeRename.getNodeRef().getShortId());
|
||||||
|
mappedMethodArgsAndVars.put(codeRename.getNodeRef().getDeclaringClass()
|
||||||
|
+ codeRename.getNodeRef().getShortId()
|
||||||
|
+ codeRename.getCodeRef(),
|
||||||
|
codeRename.getNewName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (ICodeComment codeComment : codeData.getComments()) {
|
||||||
|
comments.put(codeComment.getNodeRef().getDeclaringClass()
|
||||||
|
+ (codeComment.getNodeRef().getShortId() == null ? "" : codeComment.getNodeRef().getShortId())
|
||||||
|
+ (codeComment.getCodeRef() == null ? "" : codeComment.getCodeRef()),
|
||||||
|
codeComment.getComment());
|
||||||
|
if (codeComment.getCodeRef() != null) {
|
||||||
|
methodsWithMappedElements.add(codeComment.getNodeRef().getDeclaringClass() + codeComment.getNodeRef().getShortId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (mappingFormat.hasSingleFile()) {
|
||||||
|
if (path.toFile().exists()) {
|
||||||
|
path.toFile().delete();
|
||||||
|
}
|
||||||
|
path.toFile().createNewFile();
|
||||||
|
} else {
|
||||||
|
FileUtils.makeDirs(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
mappingTree.visitHeader();
|
||||||
|
mappingTree.visitNamespaces("official", Arrays.asList("named"));
|
||||||
|
mappingTree.visitContent();
|
||||||
|
|
||||||
|
for (ClassNode cls : root.getClasses()) {
|
||||||
|
ClassInfo classInfo = cls.getClassInfo();
|
||||||
|
String classPath = classInfo.makeRawFullName().replace('.', '/');
|
||||||
|
String rawClassName = classInfo.getRawName();
|
||||||
|
|
||||||
|
if (classInfo.hasAlias()
|
||||||
|
&& !classInfo.getAliasShortName().equals(classInfo.getShortName())
|
||||||
|
&& mappedClasses.contains(rawClassName)) {
|
||||||
|
mappingTree.visitClass(classPath);
|
||||||
|
String alias = classInfo.makeAliasRawFullName().replace('.', '/');
|
||||||
|
|
||||||
|
if (alias.startsWith(Consts.DEFAULT_PACKAGE_NAME)) {
|
||||||
|
alias = alias.substring(Consts.DEFAULT_PACKAGE_NAME.length() + 1);
|
||||||
|
}
|
||||||
|
mappingTree.visitDstName(MappedElementKind.CLASS, 0, alias);
|
||||||
|
}
|
||||||
|
if (comments.containsKey(rawClassName)) {
|
||||||
|
mappingTree.visitClass(classPath);
|
||||||
|
mappingTree.visitComment(MappedElementKind.CLASS, comments.get(rawClassName));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (FieldNode fld : cls.getFields()) {
|
||||||
|
FieldInfo fieldInfo = fld.getFieldInfo();
|
||||||
|
if (fieldInfo.hasAlias() && mappedFields.contains(rawClassName + fieldInfo.getShortId())) {
|
||||||
|
visitField(mappingTree, classPath, fieldInfo.getName(), TypeGen.signature(fieldInfo.getType()));
|
||||||
|
mappingTree.visitDstName(MappedElementKind.FIELD, 0, fieldInfo.getAlias());
|
||||||
|
}
|
||||||
|
if (comments.containsKey(rawClassName + fieldInfo.getShortId())) {
|
||||||
|
visitField(mappingTree, classPath, fieldInfo.getName(), TypeGen.signature(fieldInfo.getType()));
|
||||||
|
mappingTree.visitComment(MappedElementKind.FIELD, comments.get(rawClassName + fieldInfo.getShortId()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (MethodNode mth : cls.getMethods()) {
|
||||||
|
MethodInfo methodInfo = mth.getMethodInfo();
|
||||||
|
String methodName = methodInfo.getName();
|
||||||
|
String methodDesc = methodInfo.getShortId().substring(methodName.length());
|
||||||
|
if (methodInfo.hasAlias() && mappedMethods.contains(rawClassName + methodInfo.getShortId())) {
|
||||||
|
visitMethod(mappingTree, classPath, methodName, methodDesc);
|
||||||
|
mappingTree.visitDstName(MappedElementKind.METHOD, 0, methodInfo.getAlias());
|
||||||
|
}
|
||||||
|
if (comments.containsKey(rawClassName + methodInfo.getShortId())) {
|
||||||
|
visitMethod(mappingTree, classPath, methodName, methodDesc);
|
||||||
|
mappingTree.visitComment(MappedElementKind.METHOD, comments.get(rawClassName + methodInfo.getShortId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!methodsWithMappedElements.contains(rawClassName + methodInfo.getShortId())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Method args
|
||||||
|
int lvtIndex = mth.getAccessFlags().isStatic() ? 0 : 1;
|
||||||
|
int lastArgLvIndex = lvtIndex - 1;
|
||||||
|
List<VarNode> args = collectMethodArgs(mth);
|
||||||
|
for (VarNode arg : args) {
|
||||||
|
int lvIndex = arg.getReg() - args.get(0).getReg() + (mth.getAccessFlags().isStatic() ? 0 : 1);
|
||||||
|
String key = rawClassName + methodInfo.getShortId()
|
||||||
|
+ JadxCodeRef.forVar(arg.getReg(), arg.getSsa());
|
||||||
|
if (mappedMethodArgsAndVars.containsKey(key)) {
|
||||||
|
visitMethodArg(mappingTree, classPath, methodName, methodDesc, args.indexOf(arg), lvIndex);
|
||||||
|
mappingTree.visitDstName(MappedElementKind.METHOD_ARG, 0, mappedMethodArgsAndVars.get(key));
|
||||||
|
mappedMethodArgsAndVars.remove(key);
|
||||||
|
}
|
||||||
|
lastArgLvIndex = lvIndex;
|
||||||
|
lvtIndex++;
|
||||||
|
// Not checking for comments since method args can't have any
|
||||||
|
}
|
||||||
|
// Method vars
|
||||||
|
List<SimpleEntry<VarNode, Integer>> vars = collectMethodVars(mth);
|
||||||
|
for (SimpleEntry<VarNode, Integer> entry : vars) {
|
||||||
|
VarNode var = entry.getKey();
|
||||||
|
int offset = entry.getValue();
|
||||||
|
int lvIndex = lastArgLvIndex + var.getReg() + (mth.getAccessFlags().isStatic() ? 0 : 1);
|
||||||
|
String key = rawClassName + methodInfo.getShortId()
|
||||||
|
+ JadxCodeRef.forVar(var.getReg(), var.getSsa());
|
||||||
|
if (mappedMethodArgsAndVars.containsKey(key)) {
|
||||||
|
visitMethodVar(mappingTree, classPath, methodName, methodDesc, lvtIndex, lvIndex, offset);
|
||||||
|
mappingTree.visitDstName(MappedElementKind.METHOD_VAR, 0, mappedMethodArgsAndVars.get(key));
|
||||||
|
}
|
||||||
|
key = rawClassName + methodInfo.getShortId() + JadxCodeRef.forInsn(offset);
|
||||||
|
if (comments.containsKey(key)) {
|
||||||
|
visitMethodVar(mappingTree, classPath, methodName, methodDesc, lvtIndex, lvIndex, offset);
|
||||||
|
mappingTree.visitComment(MappedElementKind.METHOD_VAR, comments.get(key));
|
||||||
|
}
|
||||||
|
lvtIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MappingWriter writer = MappingWriter.create(path, mappingFormat);
|
||||||
|
mappingTree.accept(writer);
|
||||||
|
mappingTree.visitEnd();
|
||||||
|
writer.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.error("Failed to save deobfuscation map file '{}'", path.toAbsolutePath(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void visitField(MemoryMappingTree tree, String classPath, String srcName, String srcDesc) {
|
||||||
|
tree.visitClass(classPath);
|
||||||
|
tree.visitField(srcName, srcDesc);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void visitMethod(MemoryMappingTree tree, String classPath, String srcName, String srcDesc) {
|
||||||
|
tree.visitClass(classPath);
|
||||||
|
tree.visitMethod(srcName, srcDesc);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void visitMethodArg(MemoryMappingTree tree, String classPath, String methodSrcName, String methodSrcDesc, int argPosition,
|
||||||
|
int lvIndex) {
|
||||||
|
visitMethod(tree, classPath, methodSrcName, methodSrcDesc);
|
||||||
|
tree.visitMethodArg(argPosition, lvIndex, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void visitMethodVar(MemoryMappingTree tree, String classPath, String methodSrcName, String methodSrcDesc, int lvtIndex,
|
||||||
|
int lvIndex, int startOpIdx) {
|
||||||
|
visitMethod(tree, classPath, methodSrcName, methodSrcDesc);
|
||||||
|
tree.visitMethodVar(lvtIndex, lvIndex, startOpIdx, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -39,7 +39,7 @@ public class QuarkDialog extends JDialog {
|
|||||||
this.files = filterOpenFiles(mainWindow);
|
this.files = filterOpenFiles(mainWindow);
|
||||||
if (files.isEmpty()) {
|
if (files.isEmpty()) {
|
||||||
UiUtils.errorMessage(mainWindow, "Quark is unable to analyze loaded files");
|
UiUtils.errorMessage(mainWindow, "Quark is unable to analyze loaded files");
|
||||||
LOG.error("Quark: The files cannot be analyzed: {}", mainWindow.getWrapper().getOpenPaths());
|
LOG.error("Quark: The files cannot be analyzed: {}", mainWindow.getProject().getFilePaths());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
initUI();
|
initUI();
|
||||||
@@ -47,7 +47,7 @@ public class QuarkDialog extends JDialog {
|
|||||||
|
|
||||||
private List<Path> filterOpenFiles(MainWindow mainWindow) {
|
private List<Path> filterOpenFiles(MainWindow mainWindow) {
|
||||||
PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:**.{apk,dex}");
|
PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:**.{apk,dex}");
|
||||||
return mainWindow.getWrapper().getOpenPaths()
|
return mainWindow.getProject().getFilePaths()
|
||||||
.stream()
|
.stream()
|
||||||
.filter(matcher::matches)
|
.filter(matcher::matches)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ import org.slf4j.LoggerFactory;
|
|||||||
|
|
||||||
import jadx.api.ICodeCache;
|
import jadx.api.ICodeCache;
|
||||||
import jadx.api.ICodeWriter;
|
import jadx.api.ICodeWriter;
|
||||||
import jadx.api.JadxDecompiler;
|
|
||||||
import jadx.api.JavaClass;
|
import jadx.api.JavaClass;
|
||||||
import jadx.api.JavaNode;
|
import jadx.api.JavaNode;
|
||||||
import jadx.api.metadata.ICodeMetadata;
|
import jadx.api.metadata.ICodeMetadata;
|
||||||
import jadx.api.metadata.ICodeNodeRef;
|
import jadx.api.metadata.ICodeNodeRef;
|
||||||
|
import jadx.gui.JadxWrapper;
|
||||||
import jadx.gui.jobs.Cancelable;
|
import jadx.gui.jobs.Cancelable;
|
||||||
import jadx.gui.search.SearchSettings;
|
import jadx.gui.search.SearchSettings;
|
||||||
import jadx.gui.treemodel.CodeNode;
|
import jadx.gui.treemodel.CodeNode;
|
||||||
@@ -23,7 +23,7 @@ public final class CodeSearchProvider extends BaseSearchProvider {
|
|||||||
private static final Logger LOG = LoggerFactory.getLogger(CodeSearchProvider.class);
|
private static final Logger LOG = LoggerFactory.getLogger(CodeSearchProvider.class);
|
||||||
|
|
||||||
private final ICodeCache codeCache;
|
private final ICodeCache codeCache;
|
||||||
private final JadxDecompiler decompiler;
|
private final JadxWrapper wrapper;
|
||||||
|
|
||||||
private @Nullable String code;
|
private @Nullable String code;
|
||||||
private int clsNum = 0;
|
private int clsNum = 0;
|
||||||
@@ -32,7 +32,7 @@ public final class CodeSearchProvider extends BaseSearchProvider {
|
|||||||
public CodeSearchProvider(MainWindow mw, SearchSettings searchSettings, List<JavaClass> classes) {
|
public CodeSearchProvider(MainWindow mw, SearchSettings searchSettings, List<JavaClass> classes) {
|
||||||
super(mw, searchSettings, classes);
|
super(mw, searchSettings, classes);
|
||||||
this.codeCache = mw.getWrapper().getArgs().getCodeCache();
|
this.codeCache = mw.getWrapper().getArgs().getCodeCache();
|
||||||
this.decompiler = mw.getWrapper().getDecompiler();
|
this.wrapper = mw.getWrapper();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -75,7 +75,7 @@ public final class CodeSearchProvider extends BaseSearchProvider {
|
|||||||
try {
|
try {
|
||||||
ICodeMetadata metadata = javaCls.getCodeInfo().getCodeMetadata();
|
ICodeMetadata metadata = javaCls.getCodeInfo().getCodeMetadata();
|
||||||
ICodeNodeRef nodeRef = metadata.getNodeAt(pos);
|
ICodeNodeRef nodeRef = metadata.getNodeAt(pos);
|
||||||
JavaNode encNode = decompiler.getJavaNodeByRef(nodeRef);
|
JavaNode encNode = wrapper.getJavaNodeByRef(nodeRef);
|
||||||
if (encNode != null) {
|
if (encNode != null) {
|
||||||
return convert(encNode);
|
return convert(encNode);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import java.util.Objects;
|
|||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -41,30 +43,38 @@ public class JadxProject {
|
|||||||
private static final int CURRENT_PROJECT_VERSION = 1;
|
private static final int CURRENT_PROJECT_VERSION = 1;
|
||||||
public static final String PROJECT_EXTENSION = "jadx";
|
public static final String PROJECT_EXTENSION = "jadx";
|
||||||
|
|
||||||
private transient MainWindow mainWindow;
|
private final transient MainWindow mainWindow;
|
||||||
private transient JadxSettings settings;
|
|
||||||
|
|
||||||
private transient String name = "New Project";
|
private transient String name = "New Project";
|
||||||
private transient Path projectPath;
|
private transient @Nullable Path projectPath;
|
||||||
|
|
||||||
private transient boolean initial = true;
|
private transient boolean initial = true;
|
||||||
private transient boolean saved;
|
private transient boolean saved;
|
||||||
|
|
||||||
private ProjectData data = new ProjectData();
|
private ProjectData data = new ProjectData();
|
||||||
|
|
||||||
public void setSettings(JadxSettings settings) {
|
public JadxProject(MainWindow mainWindow) {
|
||||||
this.settings = settings;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMainWindow(MainWindow mainWindow) {
|
|
||||||
this.mainWindow = mainWindow;
|
this.mainWindow = mainWindow;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public @Nullable Path getWorkingDir() {
|
||||||
|
if (projectPath != null) {
|
||||||
|
return projectPath.toAbsolutePath().getParent();
|
||||||
|
}
|
||||||
|
List<Path> files = data.getFiles();
|
||||||
|
if (!files.isEmpty()) {
|
||||||
|
Path path = files.get(0);
|
||||||
|
return path.toAbsolutePath().getParent();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
public Path getProjectPath() {
|
public Path getProjectPath() {
|
||||||
return projectPath;
|
return projectPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setProjectPath(Path projectPath) {
|
private void setProjectPath(@NotNull Path projectPath) {
|
||||||
this.projectPath = projectPath;
|
this.projectPath = projectPath;
|
||||||
this.name = CommonFileUtils.removeFileExtension(projectPath.getFileName().toString());
|
this.name = CommonFileUtils.removeFileExtension(projectPath.getFileName().toString());
|
||||||
changed();
|
changed();
|
||||||
@@ -74,7 +84,7 @@ public class JadxProject {
|
|||||||
return data.getFiles();
|
return data.getFiles();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFilePath(List<Path> files) {
|
public void setFilePaths(List<Path> files) {
|
||||||
if (!files.equals(getFilePaths())) {
|
if (!files.equals(getFilePaths())) {
|
||||||
data.setFiles(files);
|
data.setFiles(files);
|
||||||
String joinedName = files.stream().map(p -> CommonFileUtils.removeFileExtension(p.getFileName().toString()))
|
String joinedName = files.stream().map(p -> CommonFileUtils.removeFileExtension(p.getFileName().toString()))
|
||||||
@@ -144,22 +154,63 @@ public class JadxProject {
|
|||||||
return data.getActiveTab();
|
return data.getActiveTab();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public @NotNull Path getCacheDir() {
|
||||||
|
Path cacheDir = data.getCacheDir();
|
||||||
|
if (cacheDir != null) {
|
||||||
|
return cacheDir;
|
||||||
|
}
|
||||||
|
Path newCacheDir = buildCacheDir();
|
||||||
|
setCacheDir(newCacheDir);
|
||||||
|
return newCacheDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCacheDir(Path cacheDir) {
|
||||||
|
data.setCacheDir(cacheDir);
|
||||||
|
changed();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Path buildCacheDir() {
|
||||||
|
if (projectPath != null) {
|
||||||
|
return projectPath.resolveSibling(projectPath.getFileName() + ".cache");
|
||||||
|
}
|
||||||
|
List<Path> files = data.getFiles();
|
||||||
|
if (!files.isEmpty()) {
|
||||||
|
Path path = files.get(0);
|
||||||
|
return path.resolveSibling(path.getFileName() + ".cache");
|
||||||
|
}
|
||||||
|
throw new JadxRuntimeException("Failed to build cache dir");
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnableLiveReload() {
|
||||||
|
return data.isEnableLiveReload();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEnableLiveReload(boolean newValue) {
|
||||||
|
if (newValue != data.isEnableLiveReload()) {
|
||||||
|
data.setEnableLiveReload(newValue);
|
||||||
|
changed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void changed() {
|
private void changed() {
|
||||||
|
JadxSettings settings = mainWindow.getSettings();
|
||||||
if (settings != null && settings.isAutoSaveProject()) {
|
if (settings != null && settings.isAutoSaveProject()) {
|
||||||
save();
|
save();
|
||||||
} else {
|
} else {
|
||||||
saved = false;
|
saved = false;
|
||||||
}
|
}
|
||||||
initial = false;
|
initial = false;
|
||||||
if (mainWindow != null) {
|
mainWindow.updateProject(this);
|
||||||
mainWindow.updateProject(this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isSaveFileSelected() {
|
||||||
|
return projectPath != null;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isSaved() {
|
public boolean isSaved() {
|
||||||
return saved;
|
return saved;
|
||||||
}
|
}
|
||||||
@@ -186,10 +237,10 @@ public class JadxProject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static JadxProject from(Path path) {
|
public static JadxProject load(MainWindow mainWindow, Path path) {
|
||||||
Path basePath = path.toAbsolutePath().getParent();
|
Path basePath = path.toAbsolutePath().getParent();
|
||||||
try (Reader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
|
try (Reader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
|
||||||
JadxProject project = new JadxProject();
|
JadxProject project = new JadxProject(mainWindow);
|
||||||
project.data = buildGson(basePath).fromJson(reader, ProjectData.class);
|
project.data = buildGson(basePath).fromJson(reader, ProjectData.class);
|
||||||
project.saved = true;
|
project.saved = true;
|
||||||
project.setProjectPath(path);
|
project.setProjectPath(path);
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ public class JadxSettings extends JadxCLIArgs {
|
|||||||
|
|
||||||
private static final Path USER_HOME = Paths.get(System.getProperty("user.home"));
|
private static final Path USER_HOME = Paths.get(System.getProperty("user.home"));
|
||||||
private static final int RECENT_PROJECTS_COUNT = 15;
|
private static final int RECENT_PROJECTS_COUNT = 15;
|
||||||
private static final int CURRENT_SETTINGS_VERSION = 17;
|
private static final int CURRENT_SETTINGS_VERSION = 18;
|
||||||
|
|
||||||
private static final Font DEFAULT_FONT = new RSyntaxTextArea().getFont();
|
private static final Font DEFAULT_FONT = new RSyntaxTextArea().getFont();
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ public class JadxSettings extends JadxCLIArgs {
|
|||||||
private Path lastOpenFilePath = USER_HOME;
|
private Path lastOpenFilePath = USER_HOME;
|
||||||
private Path lastSaveFilePath = USER_HOME;
|
private Path lastSaveFilePath = USER_HOME;
|
||||||
private boolean flattenPackage = false;
|
private boolean flattenPackage = false;
|
||||||
private boolean checkForUpdates = false;
|
private boolean checkForUpdates = true;
|
||||||
private List<Path> recentProjects = new ArrayList<>();
|
private List<Path> recentProjects = new ArrayList<>();
|
||||||
private String fontStr = "";
|
private String fontStr = "";
|
||||||
private String smaliFontStr = "";
|
private String smaliFontStr = "";
|
||||||
@@ -91,6 +91,7 @@ public class JadxSettings extends JadxCLIArgs {
|
|||||||
private String adbDialogPort = "5037";
|
private String adbDialogPort = "5037";
|
||||||
|
|
||||||
private CodeCacheMode codeCacheMode = CodeCacheMode.DISK_WITH_CACHE;
|
private CodeCacheMode codeCacheMode = CodeCacheMode.DISK_WITH_CACHE;
|
||||||
|
private boolean jumpOnDoubleClick = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* UI setting: the width of the tree showing the classes, resources, ...
|
* UI setting: the width of the tree showing the classes, resources, ...
|
||||||
@@ -219,29 +220,22 @@ public class JadxSettings extends JadxCLIArgs {
|
|||||||
if (pos == null || pos.getBounds() == null) {
|
if (pos == null || pos.getBounds() == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (window instanceof MainWindow) {
|
if (!isAccessibleInAnyScreen(pos)) {
|
||||||
int extendedState = getMainWindowExtendedState();
|
|
||||||
if (extendedState != JFrame.NORMAL) {
|
|
||||||
((JFrame) window).setExtendedState(extendedState);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isContainedInAnyScreen(pos)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
window.setBounds(pos.getBounds());
|
window.setBounds(pos.getBounds());
|
||||||
|
if (window instanceof MainWindow) {
|
||||||
|
((JFrame) window).setExtendedState(getMainWindowExtendedState());
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isContainedInAnyScreen(WindowLocation pos) {
|
private static boolean isAccessibleInAnyScreen(WindowLocation pos) {
|
||||||
Rectangle bounds = pos.getBounds();
|
Rectangle windowBounds = pos.getBounds();
|
||||||
if (bounds.getX() > 0 && bounds.getY() > 0) {
|
for (GraphicsDevice gd : GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices()) {
|
||||||
for (GraphicsDevice gd : GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices()) {
|
Rectangle screenBounds = gd.getDefaultConfiguration().getBounds();
|
||||||
if (gd.getDefaultConfiguration().getBounds().contains(bounds)) {
|
if (screenBounds.intersects(windowBounds)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LOG.debug("Window saved position was ignored: {}", pos);
|
LOG.debug("Window saved position was ignored: {}", pos);
|
||||||
@@ -623,6 +617,14 @@ public class JadxSettings extends JadxCLIArgs {
|
|||||||
this.codeCacheMode = codeCacheMode;
|
this.codeCacheMode = codeCacheMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isJumpOnDoubleClick() {
|
||||||
|
return jumpOnDoubleClick;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setJumpOnDoubleClick(boolean jumpOnDoubleClick) {
|
||||||
|
this.jumpOnDoubleClick = jumpOnDoubleClick;
|
||||||
|
}
|
||||||
|
|
||||||
private void upgradeSettings(int fromVersion) {
|
private void upgradeSettings(int fromVersion) {
|
||||||
LOG.debug("upgrade settings from version: {} to {}", fromVersion, CURRENT_SETTINGS_VERSION);
|
LOG.debug("upgrade settings from version: {} to {}", fromVersion, CURRENT_SETTINGS_VERSION);
|
||||||
if (fromVersion == 0) {
|
if (fromVersion == 0) {
|
||||||
@@ -702,11 +704,7 @@ public class JadxSettings extends JadxCLIArgs {
|
|||||||
fromVersion++;
|
fromVersion++;
|
||||||
}
|
}
|
||||||
if (fromVersion == 15) {
|
if (fromVersion == 15) {
|
||||||
if (deobfuscationForceSave) {
|
deobfuscationMapFileMode = DeobfuscationMapFileMode.READ;
|
||||||
deobfuscationMapFileMode = DeobfuscationMapFileMode.OVERWRITE;
|
|
||||||
} else {
|
|
||||||
deobfuscationMapFileMode = DeobfuscationMapFileMode.READ;
|
|
||||||
}
|
|
||||||
fromVersion++;
|
fromVersion++;
|
||||||
}
|
}
|
||||||
if (fromVersion == 16) {
|
if (fromVersion == 16) {
|
||||||
@@ -717,6 +715,10 @@ public class JadxSettings extends JadxCLIArgs {
|
|||||||
}
|
}
|
||||||
fromVersion++;
|
fromVersion++;
|
||||||
}
|
}
|
||||||
|
if (fromVersion == 17) {
|
||||||
|
checkForUpdates = true;
|
||||||
|
fromVersion++;
|
||||||
|
}
|
||||||
if (fromVersion != CURRENT_SETTINGS_VERSION) {
|
if (fromVersion != CURRENT_SETTINGS_VERSION) {
|
||||||
LOG.warn("Incorrect settings upgrade. Expected version: {}, got: {}", CURRENT_SETTINGS_VERSION, fromVersion);
|
LOG.warn("Incorrect settings upgrade. Expected version: {}, got: {}", CURRENT_SETTINGS_VERSION, fromVersion);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,7 +65,6 @@ import jadx.api.JadxArgs.UseKotlinMethodsForVarNames;
|
|||||||
import jadx.api.args.DeobfuscationMapFileMode;
|
import jadx.api.args.DeobfuscationMapFileMode;
|
||||||
import jadx.api.plugins.JadxPlugin;
|
import jadx.api.plugins.JadxPlugin;
|
||||||
import jadx.api.plugins.JadxPluginInfo;
|
import jadx.api.plugins.JadxPluginInfo;
|
||||||
import jadx.api.plugins.JadxPluginManager;
|
|
||||||
import jadx.api.plugins.options.JadxPluginOptions;
|
import jadx.api.plugins.options.JadxPluginOptions;
|
||||||
import jadx.api.plugins.options.OptionDescription;
|
import jadx.api.plugins.options.OptionDescription;
|
||||||
import jadx.gui.ui.MainWindow;
|
import jadx.gui.ui.MainWindow;
|
||||||
@@ -138,7 +137,7 @@ public class JadxSettingsWindow extends JDialog {
|
|||||||
|
|
||||||
SwingUtilities.invokeLater(() -> {
|
SwingUtilities.invokeLater(() -> {
|
||||||
if (needReload) {
|
if (needReload) {
|
||||||
mainWindow.reOpenFile();
|
mainWindow.reopen();
|
||||||
}
|
}
|
||||||
if (!settings.getLangLocale().equals(prevLang)) {
|
if (!settings.getLangLocale().equals(prevLang)) {
|
||||||
JOptionPane.showMessageDialog(
|
JOptionPane.showMessageDialog(
|
||||||
@@ -580,8 +579,7 @@ public class JadxSettingsWindow extends JDialog {
|
|||||||
|
|
||||||
private SettingsGroup makePluginOptionsGroup() {
|
private SettingsGroup makePluginOptionsGroup() {
|
||||||
SettingsGroup pluginsGroup = new SettingsGroup(NLS.str("preferences.plugins"));
|
SettingsGroup pluginsGroup = new SettingsGroup(NLS.str("preferences.plugins"));
|
||||||
JadxPluginManager pluginManager = mainWindow.getWrapper().getDecompiler().getPluginManager();
|
for (JadxPlugin plugin : mainWindow.getWrapper().getAllPlugins()) {
|
||||||
for (JadxPlugin plugin : pluginManager.getAllPlugins()) {
|
|
||||||
if (!(plugin instanceof JadxPluginOptions)) {
|
if (!(plugin instanceof JadxPluginOptions)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -628,6 +626,10 @@ public class JadxSettingsWindow extends JDialog {
|
|||||||
mainWindow.loadSettings();
|
mainWindow.loadSettings();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
JCheckBox jumpOnDoubleClick = new JCheckBox();
|
||||||
|
jumpOnDoubleClick.setSelected(settings.isJumpOnDoubleClick());
|
||||||
|
jumpOnDoubleClick.addItemListener(e -> settings.setJumpOnDoubleClick(e.getStateChange() == ItemEvent.SELECTED));
|
||||||
|
|
||||||
JCheckBox update = new JCheckBox();
|
JCheckBox update = new JCheckBox();
|
||||||
update.setSelected(settings.isCheckForUpdates());
|
update.setSelected(settings.isCheckForUpdates());
|
||||||
update.addItemListener(e -> settings.setCheckForUpdates(e.getStateChange() == ItemEvent.SELECTED));
|
update.addItemListener(e -> settings.setCheckForUpdates(e.getStateChange() == ItemEvent.SELECTED));
|
||||||
@@ -649,6 +651,7 @@ public class JadxSettingsWindow extends JDialog {
|
|||||||
SettingsGroup group = new SettingsGroup(NLS.str("preferences.other"));
|
SettingsGroup group = new SettingsGroup(NLS.str("preferences.other"));
|
||||||
group.addRow(NLS.str("preferences.language"), languageCbx);
|
group.addRow(NLS.str("preferences.language"), languageCbx);
|
||||||
group.addRow(NLS.str("preferences.lineNumbersMode"), lineNumbersMode);
|
group.addRow(NLS.str("preferences.lineNumbersMode"), lineNumbersMode);
|
||||||
|
group.addRow(NLS.str("preferences.jumpOnDoubleClick"), jumpOnDoubleClick);
|
||||||
group.addRow(NLS.str("preferences.check_for_updates"), update);
|
group.addRow(NLS.str("preferences.check_for_updates"), update);
|
||||||
group.addRow(NLS.str("preferences.cfg"), cfg);
|
group.addRow(NLS.str("preferences.cfg"), cfg);
|
||||||
group.addRow(NLS.str("preferences.raw_cfg"), rawCfg);
|
group.addRow(NLS.str("preferences.raw_cfg"), rawCfg);
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import java.util.Collections;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import jadx.api.data.impl.JadxCodeData;
|
import jadx.api.data.impl.JadxCodeData;
|
||||||
|
|
||||||
public class ProjectData {
|
public class ProjectData {
|
||||||
@@ -16,6 +18,8 @@ public class ProjectData {
|
|||||||
private JadxCodeData codeData = new JadxCodeData();
|
private JadxCodeData codeData = new JadxCodeData();
|
||||||
private List<TabViewState> openTabs = Collections.emptyList();
|
private List<TabViewState> openTabs = Collections.emptyList();
|
||||||
private int activeTab = -1;
|
private int activeTab = -1;
|
||||||
|
private @Nullable Path cacheDir;
|
||||||
|
private boolean enableLiveReload = false;
|
||||||
|
|
||||||
public List<Path> getFiles() {
|
public List<Path> getFiles() {
|
||||||
return files;
|
return files;
|
||||||
@@ -82,4 +86,21 @@ public class ProjectData {
|
|||||||
this.activeTab = activeTab;
|
this.activeTab = activeTab;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Path getCacheDir() {
|
||||||
|
return cacheDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCacheDir(Path cacheDir) {
|
||||||
|
this.cacheDir = cacheDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnableLiveReload() {
|
||||||
|
return enableLiveReload;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEnableLiveReload(boolean enableLiveReload) {
|
||||||
|
this.enableLiveReload = enableLiveReload;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,6 +86,12 @@ public class JField extends JNode {
|
|||||||
return UiUtils.typeFormatHtml(field.getFullName(), field.getType());
|
return UiUtils.typeFormatHtml(field.getFullName(), field.getType());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTooltip() {
|
||||||
|
String fullType = UiUtils.escapeHtml(field.getType().toString());
|
||||||
|
return UiUtils.wrapHtml(fullType + ' ' + UiUtils.escapeHtml(field.getName()));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String makeDescString() {
|
public String makeDescString() {
|
||||||
return UiUtils.typeStr(field.getType()) + " " + field.getName();
|
return UiUtils.typeStr(field.getType()) + " " + field.getName();
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ public class JRoot extends JNode {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String makeString() {
|
public String makeString() {
|
||||||
List<Path> paths = wrapper.getOpenPaths();
|
List<Path> paths = wrapper.getProject().getFilePaths();
|
||||||
int count = paths.size();
|
int count = paths.size();
|
||||||
if (count == 0) {
|
if (count == 0) {
|
||||||
return "File not open";
|
return "File not open";
|
||||||
@@ -146,7 +146,7 @@ public class JRoot extends JNode {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getTooltip() {
|
public String getTooltip() {
|
||||||
List<Path> paths = wrapper.getOpenPaths();
|
List<Path> paths = wrapper.getProject().getFilePaths();
|
||||||
int count = paths.size();
|
int count = paths.size();
|
||||||
if (count < 2) {
|
if (count < 2) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import javax.swing.Icon;
|
|||||||
|
|
||||||
import jadx.api.JavaNode;
|
import jadx.api.JavaNode;
|
||||||
import jadx.api.JavaVariable;
|
import jadx.api.JavaVariable;
|
||||||
|
import jadx.gui.utils.UiUtils;
|
||||||
|
|
||||||
public class JVariable extends JNode {
|
public class JVariable extends JNode {
|
||||||
private static final long serialVersionUID = -3002100457834453783L;
|
private static final long serialVersionUID = -3002100457834453783L;
|
||||||
@@ -55,6 +56,18 @@ public class JVariable extends JNode {
|
|||||||
return var.getFullName();
|
return var.getFullName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String makeLongStringHtml() {
|
||||||
|
return UiUtils.typeFormatHtml(var.getName(), var.getType());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTooltip() {
|
||||||
|
String name = var.getName() + " (r" + var.getReg() + "v" + var.getSsa() + ")";
|
||||||
|
String fullType = UiUtils.escapeHtml(var.getType().toString());
|
||||||
|
return UiUtils.wrapHtml(fullType + ' ' + UiUtils.escapeHtml(name));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean canRename() {
|
public boolean canRename() {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import java.awt.event.ActionEvent;
|
|||||||
import java.awt.event.ActionListener;
|
import java.awt.event.ActionListener;
|
||||||
import java.awt.event.FocusAdapter;
|
import java.awt.event.FocusAdapter;
|
||||||
import java.awt.event.FocusEvent;
|
import java.awt.event.FocusEvent;
|
||||||
|
import java.awt.event.InputEvent;
|
||||||
import java.awt.event.KeyAdapter;
|
import java.awt.event.KeyAdapter;
|
||||||
import java.awt.event.KeyEvent;
|
import java.awt.event.KeyEvent;
|
||||||
import java.awt.event.MouseAdapter;
|
import java.awt.event.MouseAdapter;
|
||||||
@@ -43,6 +44,7 @@ import javax.swing.Action;
|
|||||||
import javax.swing.Box;
|
import javax.swing.Box;
|
||||||
import javax.swing.ImageIcon;
|
import javax.swing.ImageIcon;
|
||||||
import javax.swing.JCheckBoxMenuItem;
|
import javax.swing.JCheckBoxMenuItem;
|
||||||
|
import javax.swing.JFileChooser;
|
||||||
import javax.swing.JFrame;
|
import javax.swing.JFrame;
|
||||||
import javax.swing.JMenu;
|
import javax.swing.JMenu;
|
||||||
import javax.swing.JMenuBar;
|
import javax.swing.JMenuBar;
|
||||||
@@ -77,6 +79,7 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import ch.qos.logback.classic.Level;
|
import ch.qos.logback.classic.Level;
|
||||||
|
import net.fabricmc.mappingio.format.MappingFormat;
|
||||||
|
|
||||||
import jadx.api.JadxArgs;
|
import jadx.api.JadxArgs;
|
||||||
import jadx.api.JavaNode;
|
import jadx.api.JavaNode;
|
||||||
@@ -85,7 +88,6 @@ import jadx.api.plugins.utils.CommonFileUtils;
|
|||||||
import jadx.core.Jadx;
|
import jadx.core.Jadx;
|
||||||
import jadx.core.utils.ListUtils;
|
import jadx.core.utils.ListUtils;
|
||||||
import jadx.core.utils.StringUtils;
|
import jadx.core.utils.StringUtils;
|
||||||
import jadx.core.utils.Utils;
|
|
||||||
import jadx.core.utils.files.FileUtils;
|
import jadx.core.utils.files.FileUtils;
|
||||||
import jadx.gui.JadxWrapper;
|
import jadx.gui.JadxWrapper;
|
||||||
import jadx.gui.device.debugger.BreakpointManager;
|
import jadx.gui.device.debugger.BreakpointManager;
|
||||||
@@ -94,6 +96,7 @@ import jadx.gui.jobs.DecompileTask;
|
|||||||
import jadx.gui.jobs.ExportTask;
|
import jadx.gui.jobs.ExportTask;
|
||||||
import jadx.gui.jobs.ProcessResult;
|
import jadx.gui.jobs.ProcessResult;
|
||||||
import jadx.gui.jobs.TaskStatus;
|
import jadx.gui.jobs.TaskStatus;
|
||||||
|
import jadx.gui.plugins.mappings.MappingExporter;
|
||||||
import jadx.gui.plugins.quark.QuarkDialog;
|
import jadx.gui.plugins.quark.QuarkDialog;
|
||||||
import jadx.gui.settings.JadxProject;
|
import jadx.gui.settings.JadxProject;
|
||||||
import jadx.gui.settings.JadxSettings;
|
import jadx.gui.settings.JadxSettings;
|
||||||
@@ -133,7 +136,9 @@ import jadx.gui.utils.Link;
|
|||||||
import jadx.gui.utils.NLS;
|
import jadx.gui.utils.NLS;
|
||||||
import jadx.gui.utils.SystemInfo;
|
import jadx.gui.utils.SystemInfo;
|
||||||
import jadx.gui.utils.UiUtils;
|
import jadx.gui.utils.UiUtils;
|
||||||
|
import jadx.gui.utils.fileswatcher.LiveReloadWorker;
|
||||||
import jadx.gui.utils.logs.LogCollector;
|
import jadx.gui.utils.logs.LogCollector;
|
||||||
|
import jadx.gui.utils.ui.ActionHandler;
|
||||||
|
|
||||||
import static io.reactivex.internal.functions.Functions.EMPTY_RUNNABLE;
|
import static io.reactivex.internal.functions.Functions.EMPTY_RUNNABLE;
|
||||||
import static javax.swing.KeyStroke.getKeyStroke;
|
import static javax.swing.KeyStroke.getKeyStroke;
|
||||||
@@ -150,6 +155,7 @@ public class MainWindow extends JFrame {
|
|||||||
private static final ImageIcon ICON_OPEN = UiUtils.openSvgIcon("ui/openDisk");
|
private static final ImageIcon ICON_OPEN = UiUtils.openSvgIcon("ui/openDisk");
|
||||||
private static final ImageIcon ICON_ADD_FILES = UiUtils.openSvgIcon("ui/addFile");
|
private static final ImageIcon ICON_ADD_FILES = UiUtils.openSvgIcon("ui/addFile");
|
||||||
private static final ImageIcon ICON_SAVE_ALL = UiUtils.openSvgIcon("ui/menu-saveall");
|
private static final ImageIcon ICON_SAVE_ALL = UiUtils.openSvgIcon("ui/menu-saveall");
|
||||||
|
private static final ImageIcon ICON_RELOAD = UiUtils.openSvgIcon("ui/refresh");
|
||||||
private static final ImageIcon ICON_EXPORT = UiUtils.openSvgIcon("ui/export");
|
private static final ImageIcon ICON_EXPORT = UiUtils.openSvgIcon("ui/export");
|
||||||
private static final ImageIcon ICON_EXIT = UiUtils.openSvgIcon("ui/exit");
|
private static final ImageIcon ICON_EXIT = UiUtils.openSvgIcon("ui/exit");
|
||||||
private static final ImageIcon ICON_SYNC = UiUtils.openSvgIcon("ui/pagination");
|
private static final ImageIcon ICON_SYNC = UiUtils.openSvgIcon("ui/pagination");
|
||||||
@@ -170,10 +176,12 @@ public class MainWindow extends JFrame {
|
|||||||
private final transient JadxSettings settings;
|
private final transient JadxSettings settings;
|
||||||
private final transient CacheObject cacheObject;
|
private final transient CacheObject cacheObject;
|
||||||
private final transient BackgroundExecutor backgroundExecutor;
|
private final transient BackgroundExecutor backgroundExecutor;
|
||||||
@NotNull
|
|
||||||
private transient JadxProject project = new JadxProject();
|
private transient @NotNull JadxProject project;
|
||||||
|
|
||||||
private transient Action newProjectAction;
|
private transient Action newProjectAction;
|
||||||
private transient Action saveProjectAction;
|
private transient Action saveProjectAction;
|
||||||
|
private transient JMenu exportMappingsMenu;
|
||||||
|
|
||||||
private JPanel mainPanel;
|
private JPanel mainPanel;
|
||||||
private JSplitPane splitPane;
|
private JSplitPane splitPane;
|
||||||
@@ -192,6 +200,9 @@ public class MainWindow extends JFrame {
|
|||||||
private JToggleButton deobfToggleBtn;
|
private JToggleButton deobfToggleBtn;
|
||||||
private JCheckBoxMenuItem deobfMenuItem;
|
private JCheckBoxMenuItem deobfMenuItem;
|
||||||
|
|
||||||
|
private JCheckBoxMenuItem liveReloadMenuItem;
|
||||||
|
private final LiveReloadWorker liveReloadWorker;
|
||||||
|
|
||||||
private transient Link updateLink;
|
private transient Link updateLink;
|
||||||
private transient ProgressPanel progressPane;
|
private transient ProgressPanel progressPane;
|
||||||
private transient Theme editorTheme;
|
private transient Theme editorTheme;
|
||||||
@@ -202,20 +213,21 @@ public class MainWindow extends JFrame {
|
|||||||
public MainWindow(JadxSettings settings) {
|
public MainWindow(JadxSettings settings) {
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
this.cacheObject = new CacheObject();
|
this.cacheObject = new CacheObject();
|
||||||
this.wrapper = new JadxWrapper(settings);
|
this.project = new JadxProject(this);
|
||||||
|
this.wrapper = new JadxWrapper(this);
|
||||||
|
this.liveReloadWorker = new LiveReloadWorker(this);
|
||||||
|
|
||||||
resetCache();
|
resetCache();
|
||||||
FontUtils.registerBundledFonts();
|
FontUtils.registerBundledFonts();
|
||||||
initUI();
|
initUI();
|
||||||
|
this.backgroundExecutor = new BackgroundExecutor(settings, progressPane);
|
||||||
initMenuAndToolbar();
|
initMenuAndToolbar();
|
||||||
registerMouseNavigationButtons();
|
registerMouseNavigationButtons();
|
||||||
UiUtils.setWindowIcons(this);
|
UiUtils.setWindowIcons(this);
|
||||||
loadSettings();
|
loadSettings();
|
||||||
|
|
||||||
this.backgroundExecutor = new BackgroundExecutor(this);
|
update();
|
||||||
|
|
||||||
checkForUpdate();
|
checkForUpdate();
|
||||||
newProject();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void init() {
|
public void init() {
|
||||||
@@ -275,6 +287,10 @@ public class MainWindow extends JFrame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void openFileOrProject() {
|
public void openFileOrProject() {
|
||||||
|
saveAll();
|
||||||
|
if (!ensureProjectIsSaved()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
FileDialog fileDialog = new FileDialog(this, FileDialog.OpenMode.OPEN);
|
FileDialog fileDialog = new FileDialog(this, FileDialog.OpenMode.OPEN);
|
||||||
List<Path> openPaths = fileDialog.show();
|
List<Path> openPaths = fileDialog.show();
|
||||||
if (!openPaths.isEmpty()) {
|
if (!openPaths.isEmpty()) {
|
||||||
@@ -287,20 +303,26 @@ public class MainWindow extends JFrame {
|
|||||||
FileDialog fileDialog = new FileDialog(this, FileDialog.OpenMode.ADD);
|
FileDialog fileDialog = new FileDialog(this, FileDialog.OpenMode.ADD);
|
||||||
List<Path> addPaths = fileDialog.show();
|
List<Path> addPaths = fileDialog.show();
|
||||||
if (!addPaths.isEmpty()) {
|
if (!addPaths.isEmpty()) {
|
||||||
open(ListUtils.distinctMergeSortedLists(addPaths, wrapper.getOpenPaths()));
|
addFiles(addPaths);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addFiles(List<Path> addPaths) {
|
||||||
|
project.setFilePaths(ListUtils.distinctMergeSortedLists(addPaths, project.getFilePaths()));
|
||||||
|
reopen();
|
||||||
|
}
|
||||||
|
|
||||||
private void newProject() {
|
private void newProject() {
|
||||||
if (!ensureProjectIsSaved()) {
|
if (!ensureProjectIsSaved()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
closeAll();
|
closeAll();
|
||||||
updateProject(new JadxProject());
|
exportMappingsMenu.setEnabled(false);
|
||||||
|
updateProject(new JadxProject(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveProject() {
|
private void saveProject() {
|
||||||
if (project.getProjectPath() == null) {
|
if (!project.isSaveFileSelected()) {
|
||||||
saveProjectAs();
|
saveProjectAs();
|
||||||
} else {
|
} else {
|
||||||
project.save();
|
project.save();
|
||||||
@@ -312,9 +334,8 @@ public class MainWindow extends JFrame {
|
|||||||
FileDialog fileDialog = new FileDialog(this, FileDialog.OpenMode.SAVE_PROJECT);
|
FileDialog fileDialog = new FileDialog(this, FileDialog.OpenMode.SAVE_PROJECT);
|
||||||
if (project.getFilePaths().size() == 1) {
|
if (project.getFilePaths().size() == 1) {
|
||||||
// If there is only one file loaded we suggest saving the jadx project file next to the loaded file
|
// If there is only one file loaded we suggest saving the jadx project file next to the loaded file
|
||||||
Path loadedFile = this.project.getFilePaths().get(0);
|
Path projectPath = getProjectPathForFile(this.project.getFilePaths().get(0));
|
||||||
String fileName = loadedFile.getFileName() + "." + JadxProject.PROJECT_EXTENSION;
|
fileDialog.setSelectedFile(projectPath);
|
||||||
fileDialog.setSelectedFile(loadedFile.resolveSibling(fileName));
|
|
||||||
}
|
}
|
||||||
List<Path> saveFiles = fileDialog.show();
|
List<Path> saveFiles = fileDialog.show();
|
||||||
if (saveFiles.isEmpty()) {
|
if (saveFiles.isEmpty()) {
|
||||||
@@ -340,53 +361,126 @@ public class MainWindow extends JFrame {
|
|||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void exportMappings(MappingFormat mappingFormat) {
|
||||||
|
FileDialog fileDialog = new FileDialog(this, FileDialog.OpenMode.CUSTOM_SAVE);
|
||||||
|
fileDialog.setTitle(NLS.str("file.export_mappings_as"));
|
||||||
|
Path workingDir = project.getWorkingDir();
|
||||||
|
Path baseDir = workingDir != null ? workingDir : settings.getLastSaveFilePath();
|
||||||
|
if (mappingFormat.hasSingleFile()) {
|
||||||
|
fileDialog.setSelectedFile(baseDir.resolve("mappings." + mappingFormat.fileExt));
|
||||||
|
fileDialog.setFileExtList(Collections.singletonList(mappingFormat.fileExt));
|
||||||
|
fileDialog.setSelectionMode(JFileChooser.FILES_ONLY);
|
||||||
|
} else {
|
||||||
|
fileDialog.setCurrentDir(baseDir);
|
||||||
|
fileDialog.setSelectionMode(JFileChooser.DIRECTORIES_ONLY);
|
||||||
|
}
|
||||||
|
List<Path> paths = fileDialog.show();
|
||||||
|
if (paths.size() != 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Path savePath = paths.get(0);
|
||||||
|
LOG.info("Export mappings to: {}", savePath.toAbsolutePath());
|
||||||
|
backgroundExecutor.execute(NLS.str("progress.export_mappings"),
|
||||||
|
() -> new MappingExporter(wrapper.getDecompiler().getRoot())
|
||||||
|
.exportMappings(savePath, project.getCodeData(), mappingFormat),
|
||||||
|
s -> update());
|
||||||
|
}
|
||||||
|
|
||||||
void open(List<Path> paths) {
|
void open(List<Path> paths) {
|
||||||
open(paths, EMPTY_RUNNABLE);
|
open(paths, EMPTY_RUNNABLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
void open(List<Path> paths, Runnable onFinish) {
|
private void open(List<Path> paths, Runnable onFinish) {
|
||||||
|
saveAll();
|
||||||
closeAll();
|
closeAll();
|
||||||
if (paths.size() == 1) {
|
if (paths.size() == 1 && openSingleFile(paths.get(0), onFinish)) {
|
||||||
Path singleFile = paths.get(0);
|
return;
|
||||||
String fileExtension = CommonFileUtils.getFileExtension(singleFile.getFileName().toString());
|
|
||||||
if (fileExtension != null && fileExtension.equalsIgnoreCase(JadxProject.PROJECT_EXTENSION)) {
|
|
||||||
List<Path> projectFiles = openProject(singleFile);
|
|
||||||
if (!Utils.isEmpty(projectFiles)) {
|
|
||||||
openFiles(projectFiles, onFinish);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
openFiles(paths, onFinish);
|
// start new project
|
||||||
|
project = new JadxProject(this);
|
||||||
|
project.setFilePaths(paths);
|
||||||
|
loadFiles(onFinish);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void openFiles(List<Path> paths, Runnable onFinish) {
|
private boolean openSingleFile(Path singleFile, Runnable onFinish) {
|
||||||
project.setFilePath(paths);
|
String fileExtension = CommonFileUtils.getFileExtension(singleFile.getFileName().toString());
|
||||||
if (paths.isEmpty()) {
|
if (fileExtension != null && fileExtension.equalsIgnoreCase(JadxProject.PROJECT_EXTENSION)) {
|
||||||
|
openProject(singleFile, onFinish);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// check if project file already saved with default name
|
||||||
|
Path projectPath = getProjectPathForFile(singleFile);
|
||||||
|
if (Files.exists(projectPath)) {
|
||||||
|
LOG.info("Loading project for this file");
|
||||||
|
openProject(projectPath, onFinish);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Path getProjectPathForFile(Path loadedFile) {
|
||||||
|
String fileName = loadedFile.getFileName() + "." + JadxProject.PROJECT_EXTENSION;
|
||||||
|
return loadedFile.resolveSibling(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void reopen() {
|
||||||
|
saveAll();
|
||||||
|
closeAll();
|
||||||
|
loadFiles(EMPTY_RUNNABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void openProject(Path path, Runnable onFinish) {
|
||||||
|
JadxProject jadxProject = JadxProject.load(this, path);
|
||||||
|
if (jadxProject == null) {
|
||||||
|
JOptionPane.showMessageDialog(
|
||||||
|
this,
|
||||||
|
NLS.str("msg.project_error"),
|
||||||
|
NLS.str("msg.project_error_title"),
|
||||||
|
JOptionPane.INFORMATION_MESSAGE);
|
||||||
|
jadxProject = new JadxProject(this);
|
||||||
|
}
|
||||||
|
settings.addRecentProject(path);
|
||||||
|
project = jadxProject;
|
||||||
|
loadFiles(onFinish);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadFiles(Runnable onFinish) {
|
||||||
|
exportMappingsMenu.setEnabled(false);
|
||||||
|
if (project.getFilePaths().isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
backgroundExecutor.execute(NLS.str("progress.load"),
|
backgroundExecutor.execute(NLS.str("progress.load"),
|
||||||
() -> wrapper.openFile(paths),
|
wrapper::open,
|
||||||
status -> {
|
status -> {
|
||||||
if (status == TaskStatus.CANCEL_BY_MEMORY) {
|
if (status == TaskStatus.CANCEL_BY_MEMORY) {
|
||||||
showHeapUsageBar();
|
showHeapUsageBar();
|
||||||
UiUtils.errorMessage(this, NLS.str("message.memoryLow"));
|
UiUtils.errorMessage(this, NLS.str("message.memoryLow"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (status != TaskStatus.COMPLETE) {
|
||||||
|
LOG.warn("Loading task incomplete, status: {}", status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
checkLoadedStatus();
|
checkLoadedStatus();
|
||||||
onOpen(paths);
|
onOpen();
|
||||||
|
exportMappingsMenu.setEnabled(true);
|
||||||
onFinish.run();
|
onFinish.run();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void saveAll() {
|
||||||
|
saveOpenTabs();
|
||||||
|
BreakpointManager.saveAndExit();
|
||||||
|
}
|
||||||
|
|
||||||
private void closeAll() {
|
private void closeAll() {
|
||||||
cancelBackgroundJobs();
|
cancelBackgroundJobs();
|
||||||
saveOpenTabs();
|
|
||||||
clearTree();
|
clearTree();
|
||||||
BreakpointManager.saveAndExit();
|
resetCache();
|
||||||
LogCollector.getInstance().reset();
|
LogCollector.getInstance().reset();
|
||||||
wrapper.close();
|
wrapper.close();
|
||||||
tabbedPane.closeAllTabs();
|
tabbedPane.closeAllTabs();
|
||||||
|
UiUtils.resetClipboardOwner();
|
||||||
System.gc();
|
System.gc();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -409,24 +503,40 @@ public class MainWindow extends JFrame {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onOpen(List<Path> paths) {
|
private void onOpen() {
|
||||||
deobfToggleBtn.setSelected(settings.isDeobfuscationOn());
|
deobfToggleBtn.setSelected(settings.isDeobfuscationOn());
|
||||||
initTree();
|
initTree();
|
||||||
update();
|
update();
|
||||||
BreakpointManager.init(paths.get(0).toAbsolutePath().getParent());
|
updateLiveReload(project.isEnableLiveReload());
|
||||||
|
BreakpointManager.init(project.getFilePaths().get(0).toAbsolutePath().getParent());
|
||||||
|
|
||||||
backgroundExecutor.execute(NLS.str("progress.load"),
|
backgroundExecutor.execute(NLS.str("progress.load"),
|
||||||
this::restoreOpenTabs,
|
this::restoreOpenTabs,
|
||||||
status -> runInitialBackgroundJobs());
|
status -> runInitialBackgroundJobs());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void updateLiveReload(boolean state) {
|
||||||
|
if (liveReloadWorker.isStarted() == state) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
project.setEnableLiveReload(state);
|
||||||
|
liveReloadMenuItem.setEnabled(false);
|
||||||
|
backgroundExecutor.execute(
|
||||||
|
(state ? "Starting" : "Stopping") + " live reload",
|
||||||
|
() -> liveReloadWorker.updateState(state),
|
||||||
|
s -> {
|
||||||
|
liveReloadMenuItem.setState(state);
|
||||||
|
liveReloadMenuItem.setEnabled(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void addTreeCustomNodes() {
|
private void addTreeCustomNodes() {
|
||||||
treeRoot.replaceCustomNode(ApkSignature.getApkSignature(wrapper));
|
treeRoot.replaceCustomNode(ApkSignature.getApkSignature(wrapper));
|
||||||
treeRoot.replaceCustomNode(new SummaryNode(this));
|
treeRoot.replaceCustomNode(new SummaryNode(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean ensureProjectIsSaved() {
|
private boolean ensureProjectIsSaved() {
|
||||||
if (project != null && !project.isSaved() && !project.isInitial()) {
|
if (!project.isSaved() && !project.isInitial()) {
|
||||||
int res = JOptionPane.showConfirmDialog(
|
int res = JOptionPane.showConfirmDialog(
|
||||||
this,
|
this,
|
||||||
NLS.str("confirm.not_saved_message"),
|
NLS.str("confirm.not_saved_message"),
|
||||||
@@ -442,29 +552,8 @@ public class MainWindow extends JFrame {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Path> openProject(Path path) {
|
|
||||||
if (!ensureProjectIsSaved()) {
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
JadxProject jadxProject = JadxProject.from(path);
|
|
||||||
if (jadxProject == null) {
|
|
||||||
JOptionPane.showMessageDialog(
|
|
||||||
this,
|
|
||||||
NLS.str("msg.project_error"),
|
|
||||||
NLS.str("msg.project_error_title"),
|
|
||||||
JOptionPane.INFORMATION_MESSAGE);
|
|
||||||
jadxProject = new JadxProject();
|
|
||||||
}
|
|
||||||
updateProject(jadxProject);
|
|
||||||
settings.addRecentProject(path);
|
|
||||||
return jadxProject.getFilePaths();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void updateProject(@NotNull JadxProject jadxProject) {
|
public void updateProject(@NotNull JadxProject jadxProject) {
|
||||||
jadxProject.setSettings(settings);
|
|
||||||
jadxProject.setMainWindow(this);
|
|
||||||
this.project = jadxProject;
|
this.project = jadxProject;
|
||||||
this.wrapper.setProject(jadxProject);
|
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -548,14 +637,6 @@ public class MainWindow extends JFrame {
|
|||||||
backgroundExecutor.cancelAll();
|
backgroundExecutor.cancelAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void reOpenFile() {
|
|
||||||
List<Path> openedFile = wrapper.getOpenPaths();
|
|
||||||
if (openedFile != null) {
|
|
||||||
saveOpenTabs();
|
|
||||||
open(openedFile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void saveAll(boolean export) {
|
private void saveAll(boolean export) {
|
||||||
FileDialog fileDialog = new FileDialog(this, FileDialog.OpenMode.EXPORT);
|
FileDialog fileDialog = new FileDialog(this, FileDialog.OpenMode.EXPORT);
|
||||||
List<Path> saveDirs = fileDialog.show();
|
List<Path> saveDirs = fileDialog.show();
|
||||||
@@ -588,7 +669,6 @@ public class MainWindow extends JFrame {
|
|||||||
|
|
||||||
private void clearTree() {
|
private void clearTree() {
|
||||||
tabbedPane.reset();
|
tabbedPane.reset();
|
||||||
resetCache();
|
|
||||||
treeRoot = null;
|
treeRoot = null;
|
||||||
treeModel.setRoot(null);
|
treeModel.setRoot(null);
|
||||||
treeModel.reload();
|
treeModel.reload();
|
||||||
@@ -651,7 +731,7 @@ public class MainWindow extends JFrame {
|
|||||||
|
|
||||||
deobfToggleBtn.setSelected(deobfOn);
|
deobfToggleBtn.setSelected(deobfOn);
|
||||||
deobfMenuItem.setState(deobfOn);
|
deobfMenuItem.setState(deobfOn);
|
||||||
reOpenFile();
|
reopen();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean nodeClickAction(@Nullable Object obj) {
|
private boolean nodeClickAction(@Nullable Object obj) {
|
||||||
@@ -745,7 +825,6 @@ public class MainWindow extends JFrame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void initMenuAndToolbar() {
|
private void initMenuAndToolbar() {
|
||||||
final boolean devVersion = (Jadx.VERSION_DEV.equals(Jadx.getVersion()));
|
|
||||||
Action openAction = new AbstractAction(NLS.str("file.open_action"), ICON_OPEN) {
|
Action openAction = new AbstractAction(NLS.str("file.open_action"), ICON_OPEN) {
|
||||||
@Override
|
@Override
|
||||||
public void actionPerformed(ActionEvent e) {
|
public void actionPerformed(ActionEvent e) {
|
||||||
@@ -787,6 +866,49 @@ public class MainWindow extends JFrame {
|
|||||||
};
|
};
|
||||||
saveProjectAsAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("file.save_project_as"));
|
saveProjectAsAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("file.save_project_as"));
|
||||||
|
|
||||||
|
ActionHandler reload = new ActionHandler(ev -> UiUtils.uiRun(this::reopen));
|
||||||
|
reload.setNameAndDesc(NLS.str("file.reload"));
|
||||||
|
reload.setIcon(ICON_RELOAD);
|
||||||
|
reload.setKeyBinding(getKeyStroke(KeyEvent.VK_F5, 0));
|
||||||
|
|
||||||
|
ActionHandler liveReload = new ActionHandler(ev -> updateLiveReload(!project.isEnableLiveReload()));
|
||||||
|
liveReload.setName(NLS.str("file.live_reload"));
|
||||||
|
liveReload.setShortDescription(NLS.str("file.live_reload_desc"));
|
||||||
|
liveReload.setKeyBinding(getKeyStroke(KeyEvent.VK_F5, InputEvent.SHIFT_DOWN_MASK));
|
||||||
|
|
||||||
|
liveReloadMenuItem = new JCheckBoxMenuItem(liveReload);
|
||||||
|
liveReloadMenuItem.setState(project.isEnableLiveReload());
|
||||||
|
|
||||||
|
Action exportMappingsAsTiny2 = new AbstractAction("Tiny v2 file") {
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
exportMappings(MappingFormat.TINY_2);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
exportMappingsAsTiny2.putValue(Action.SHORT_DESCRIPTION, "Tiny v2 file");
|
||||||
|
|
||||||
|
Action exportMappingsAsEnigma = new AbstractAction("Enigma file") {
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
exportMappings(MappingFormat.ENIGMA);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
exportMappingsAsEnigma.putValue(Action.SHORT_DESCRIPTION, "Enigma file");
|
||||||
|
|
||||||
|
Action exportMappingsAsEnigmaDir = new AbstractAction("Enigma directory") {
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
exportMappings(MappingFormat.ENIGMA_DIR);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
exportMappingsAsEnigmaDir.putValue(Action.SHORT_DESCRIPTION, "Enigma directory");
|
||||||
|
|
||||||
|
exportMappingsMenu = new JMenu(NLS.str("file.export_mappings_as"));
|
||||||
|
exportMappingsMenu.add(exportMappingsAsTiny2);
|
||||||
|
exportMappingsMenu.add(exportMappingsAsEnigma);
|
||||||
|
exportMappingsMenu.add(exportMappingsAsEnigmaDir);
|
||||||
|
exportMappingsMenu.setEnabled(false);
|
||||||
|
|
||||||
Action saveAllAction = new AbstractAction(NLS.str("file.save_all"), ICON_SAVE_ALL) {
|
Action saveAllAction = new AbstractAction(NLS.str("file.save_all"), ICON_SAVE_ALL) {
|
||||||
@Override
|
@Override
|
||||||
public void actionPerformed(ActionEvent e) {
|
public void actionPerformed(ActionEvent e) {
|
||||||
@@ -973,6 +1095,11 @@ public class MainWindow extends JFrame {
|
|||||||
file.add(saveProjectAction);
|
file.add(saveProjectAction);
|
||||||
file.add(saveProjectAsAction);
|
file.add(saveProjectAsAction);
|
||||||
file.addSeparator();
|
file.addSeparator();
|
||||||
|
file.add(reload);
|
||||||
|
file.add(liveReloadMenuItem);
|
||||||
|
file.addSeparator();
|
||||||
|
file.add(exportMappingsMenu);
|
||||||
|
file.addSeparator();
|
||||||
file.add(saveAllAction);
|
file.add(saveAllAction);
|
||||||
file.add(exportAction);
|
file.add(exportAction);
|
||||||
file.addSeparator();
|
file.addSeparator();
|
||||||
@@ -1007,7 +1134,7 @@ public class MainWindow extends JFrame {
|
|||||||
JMenu help = new JMenu(NLS.str("menu.help"));
|
JMenu help = new JMenu(NLS.str("menu.help"));
|
||||||
help.setMnemonic(KeyEvent.VK_H);
|
help.setMnemonic(KeyEvent.VK_H);
|
||||||
help.add(logAction);
|
help.add(logAction);
|
||||||
if (devVersion) {
|
if (Jadx.isDevVersion()) {
|
||||||
help.add(new AbstractAction("Show sample error report") {
|
help.add(new AbstractAction("Show sample error report") {
|
||||||
@Override
|
@Override
|
||||||
public void actionPerformed(ActionEvent e) {
|
public void actionPerformed(ActionEvent e) {
|
||||||
@@ -1040,6 +1167,8 @@ public class MainWindow extends JFrame {
|
|||||||
toolbar.add(openAction);
|
toolbar.add(openAction);
|
||||||
toolbar.add(addFilesAction);
|
toolbar.add(addFilesAction);
|
||||||
toolbar.addSeparator();
|
toolbar.addSeparator();
|
||||||
|
toolbar.add(reload);
|
||||||
|
toolbar.addSeparator();
|
||||||
toolbar.add(saveAllAction);
|
toolbar.add(saveAllAction);
|
||||||
toolbar.add(exportAction);
|
toolbar.add(exportAction);
|
||||||
toolbar.addSeparator();
|
toolbar.addSeparator();
|
||||||
@@ -1311,7 +1440,7 @@ public class MainWindow extends JFrame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void closeWindow() {
|
private void closeWindow() {
|
||||||
saveOpenTabs();
|
saveAll();
|
||||||
if (!ensureProjectIsSaved()) {
|
if (!ensureProjectIsSaved()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1321,20 +1450,16 @@ public class MainWindow extends JFrame {
|
|||||||
if (debuggerPanel != null) {
|
if (debuggerPanel != null) {
|
||||||
saveSplittersInfo();
|
saveSplittersInfo();
|
||||||
}
|
}
|
||||||
cancelBackgroundJobs();
|
|
||||||
wrapper.close();
|
|
||||||
heapUsageBar.reset();
|
heapUsageBar.reset();
|
||||||
dispose();
|
closeAll();
|
||||||
|
|
||||||
BreakpointManager.saveAndExit();
|
|
||||||
FileUtils.deleteTempRootDir();
|
FileUtils.deleteTempRootDir();
|
||||||
|
dispose();
|
||||||
System.exit(0);
|
System.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveOpenTabs() {
|
private void saveOpenTabs() {
|
||||||
if (project != null) {
|
project.saveOpenTabs(tabbedPane.getEditorViewStates(), tabbedPane.getSelectedIndex());
|
||||||
project.saveOpenTabs(tabbedPane.getEditorViewStates(), tabbedPane.getSelectedIndex());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void restoreOpenTabs() {
|
private void restoreOpenTabs() {
|
||||||
@@ -1382,10 +1507,6 @@ public class MainWindow extends JFrame {
|
|||||||
return backgroundExecutor;
|
return backgroundExecutor;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ProgressPanel getProgressPane() {
|
|
||||||
return progressPane;
|
|
||||||
}
|
|
||||||
|
|
||||||
public JRoot getTreeRoot() {
|
public JRoot getTreeRoot() {
|
||||||
return treeRoot;
|
return treeRoot;
|
||||||
}
|
}
|
||||||
@@ -1432,7 +1553,7 @@ public class MainWindow extends JFrame {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void menuSelected(MenuEvent menuEvent) {
|
public void menuSelected(MenuEvent menuEvent) {
|
||||||
Set<Path> current = new HashSet<>(wrapper.getOpenPaths());
|
Set<Path> current = new HashSet<>(project.getFilePaths());
|
||||||
List<JMenuItem> items = settings.getRecentProjects()
|
List<JMenuItem> items = settings.getRecentProjects()
|
||||||
.stream()
|
.stream()
|
||||||
.filter(path -> !current.contains(path))
|
.filter(path -> !current.contains(path))
|
||||||
|
|||||||
@@ -309,6 +309,7 @@ public class TabbedPane extends JTabbedPane {
|
|||||||
public void closeCodePanel(ContentPanel contentPanel) {
|
public void closeCodePanel(ContentPanel contentPanel) {
|
||||||
openTabs.remove(contentPanel.getNode());
|
openTabs.remove(contentPanel.getNode());
|
||||||
remove(contentPanel);
|
remove(contentPanel);
|
||||||
|
contentPanel.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -373,49 +374,56 @@ public class TabbedPane extends JTabbedPane {
|
|||||||
jumps.reset();
|
jumps.reset();
|
||||||
curTab = null;
|
curTab = null;
|
||||||
lastTab = null;
|
lastTab = null;
|
||||||
|
FocusManager.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public Component getFocusedComp() {
|
public Component getFocusedComp() {
|
||||||
return FocusManager.isActive() ? FocusManager.focusedComp : null;
|
return FocusManager.getFocusedComp();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class FocusManager implements FocusListener {
|
private static class FocusManager implements FocusListener {
|
||||||
static boolean active = false;
|
private static final FocusManager INSTANCE = new FocusManager();
|
||||||
static FocusManager listener = new FocusManager();
|
private static @Nullable Component focusedComp;
|
||||||
static Component focusedComp;
|
|
||||||
|
|
||||||
static boolean isActive() {
|
static boolean isActive() {
|
||||||
return active;
|
return focusedComp != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void reset() {
|
||||||
|
focusedComp = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Component getFocusedComp() {
|
||||||
|
return focusedComp;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void focusGained(FocusEvent e) {
|
public void focusGained(FocusEvent e) {
|
||||||
active = true;
|
|
||||||
focusedComp = (Component) e.getSource();
|
focusedComp = (Component) e.getSource();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void focusLost(FocusEvent e) {
|
public void focusLost(FocusEvent e) {
|
||||||
active = false;
|
focusedComp = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void listen(ContentPanel pane) {
|
static void listen(ContentPanel pane) {
|
||||||
if (pane instanceof ClassCodeContentPanel) {
|
if (pane instanceof ClassCodeContentPanel) {
|
||||||
((ClassCodeContentPanel) pane).getCodeArea().addFocusListener(listener);
|
((ClassCodeContentPanel) pane).getCodeArea().addFocusListener(INSTANCE);
|
||||||
((ClassCodeContentPanel) pane).getSmaliCodeArea().addFocusListener(listener);
|
((ClassCodeContentPanel) pane).getSmaliCodeArea().addFocusListener(INSTANCE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (pane instanceof AbstractCodeContentPanel) {
|
if (pane instanceof AbstractCodeContentPanel) {
|
||||||
((AbstractCodeContentPanel) pane).getCodeArea().addFocusListener(listener);
|
((AbstractCodeContentPanel) pane).getCodeArea().addFocusListener(INSTANCE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (pane instanceof HtmlPanel) {
|
if (pane instanceof HtmlPanel) {
|
||||||
((HtmlPanel) pane).getHtmlArea().addFocusListener(listener);
|
((HtmlPanel) pane).getHtmlArea().addFocusListener(INSTANCE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (pane instanceof ImagePanel) {
|
if (pane instanceof ImagePanel) {
|
||||||
pane.addFocusListener(listener);
|
pane.addFocusListener(INSTANCE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// throw new JadxRuntimeException("Add the new ContentPanel to TabbedPane.FocusManager: " + pane);
|
// throw new JadxRuntimeException("Add the new ContentPanel to TabbedPane.FocusManager: " + pane);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package jadx.gui.ui.codearea;
|
package jadx.gui.ui.codearea;
|
||||||
|
|
||||||
|
import java.awt.Component;
|
||||||
import java.awt.Dimension;
|
import java.awt.Dimension;
|
||||||
import java.awt.Point;
|
import java.awt.Point;
|
||||||
import java.awt.Rectangle;
|
import java.awt.Rectangle;
|
||||||
@@ -8,15 +9,21 @@ import java.awt.event.FocusEvent;
|
|||||||
import java.awt.event.FocusListener;
|
import java.awt.event.FocusListener;
|
||||||
import java.awt.event.KeyAdapter;
|
import java.awt.event.KeyAdapter;
|
||||||
import java.awt.event.KeyEvent;
|
import java.awt.event.KeyEvent;
|
||||||
|
import java.awt.event.MouseListener;
|
||||||
|
import java.awt.event.MouseMotionListener;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import javax.swing.AbstractAction;
|
import javax.swing.AbstractAction;
|
||||||
|
import javax.swing.Action;
|
||||||
import javax.swing.JCheckBoxMenuItem;
|
import javax.swing.JCheckBoxMenuItem;
|
||||||
|
import javax.swing.JMenuItem;
|
||||||
import javax.swing.JPopupMenu;
|
import javax.swing.JPopupMenu;
|
||||||
import javax.swing.JViewport;
|
import javax.swing.JViewport;
|
||||||
import javax.swing.SwingUtilities;
|
import javax.swing.SwingUtilities;
|
||||||
import javax.swing.event.CaretEvent;
|
import javax.swing.event.CaretEvent;
|
||||||
import javax.swing.event.CaretListener;
|
import javax.swing.event.CaretListener;
|
||||||
import javax.swing.event.PopupMenuEvent;
|
import javax.swing.event.PopupMenuEvent;
|
||||||
|
import javax.swing.event.PopupMenuListener;
|
||||||
import javax.swing.text.BadLocationException;
|
import javax.swing.text.BadLocationException;
|
||||||
import javax.swing.text.Caret;
|
import javax.swing.text.Caret;
|
||||||
import javax.swing.text.DefaultCaret;
|
import javax.swing.text.DefaultCaret;
|
||||||
@@ -64,12 +71,12 @@ public abstract class AbstractCodeArea extends RSyntaxTextArea {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected final ContentPanel contentPanel;
|
protected ContentPanel contentPanel;
|
||||||
protected final JNode node;
|
protected JNode node;
|
||||||
|
|
||||||
public AbstractCodeArea(ContentPanel contentPanel, JNode node) {
|
public AbstractCodeArea(ContentPanel contentPanel, JNode node) {
|
||||||
this.contentPanel = contentPanel;
|
this.contentPanel = contentPanel;
|
||||||
this.node = node;
|
this.node = Objects.requireNonNull(node);
|
||||||
|
|
||||||
setMarkOccurrences(false);
|
setMarkOccurrences(false);
|
||||||
setEditable(false);
|
setEditable(false);
|
||||||
@@ -348,4 +355,41 @@ public abstract class AbstractCodeArea extends RSyntaxTextArea {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isDisposed() {
|
||||||
|
return node == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void dispose() {
|
||||||
|
// code area reference can still be used somewhere in UI objects,
|
||||||
|
// reset node reference to allow to GC jadx objects tree
|
||||||
|
node = null;
|
||||||
|
contentPanel = null;
|
||||||
|
|
||||||
|
// also clear internals
|
||||||
|
setIgnoreRepaint(true);
|
||||||
|
setText("");
|
||||||
|
setEnabled(false);
|
||||||
|
setSyntaxEditingStyle(SYNTAX_STYLE_NONE);
|
||||||
|
setLinkGenerator(null);
|
||||||
|
for (MouseListener mouseListener : getMouseListeners()) {
|
||||||
|
removeMouseListener(mouseListener);
|
||||||
|
}
|
||||||
|
for (MouseMotionListener mouseMotionListener : getMouseMotionListeners()) {
|
||||||
|
removeMouseMotionListener(mouseMotionListener);
|
||||||
|
}
|
||||||
|
JPopupMenu popupMenu = getPopupMenu();
|
||||||
|
for (PopupMenuListener popupMenuListener : popupMenu.getPopupMenuListeners()) {
|
||||||
|
popupMenu.removePopupMenuListener(popupMenuListener);
|
||||||
|
}
|
||||||
|
for (Component component : popupMenu.getComponents()) {
|
||||||
|
if (component instanceof JMenuItem) {
|
||||||
|
Action action = ((JMenuItem) component).getAction();
|
||||||
|
if (action instanceof JNodeAction) {
|
||||||
|
((JNodeAction) action).dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
popupMenu.removeAll();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package jadx.gui.ui.codearea;
|
package jadx.gui.ui.codearea;
|
||||||
|
|
||||||
import java.awt.BorderLayout;
|
import java.awt.BorderLayout;
|
||||||
|
import java.awt.Component;
|
||||||
import java.awt.Dimension;
|
import java.awt.Dimension;
|
||||||
import java.awt.Point;
|
import java.awt.Point;
|
||||||
|
|
||||||
@@ -164,10 +165,27 @@ public final class ClassCodeContentPanel extends AbstractCodeContentPanel implem
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.debug("Failed to restore view position: {}", viewState.getViewPoint(), e);
|
LOG.debug("Failed to restore view position: {}", viewState.getViewPoint(), e);
|
||||||
}
|
}
|
||||||
|
int caretPos = viewState.getCaretPos();
|
||||||
try {
|
try {
|
||||||
activePanel.getCodeArea().setCaretPosition(viewState.getCaretPos());
|
AbstractCodeArea codeArea = activePanel.getCodeArea();
|
||||||
|
int codeLen = codeArea.getDocument().getLength();
|
||||||
|
if (caretPos >= 0 && caretPos < codeLen) {
|
||||||
|
codeArea.setCaretPosition(caretPos);
|
||||||
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.debug("Failed to restore caret position: {}", viewState.getCaretPos(), e);
|
LOG.debug("Failed to restore caret position: {}", caretPos, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose() {
|
||||||
|
javaCodePanel.dispose();
|
||||||
|
smaliCodePanel.dispose();
|
||||||
|
for (Component component : areaTabbedPane.getComponents()) {
|
||||||
|
if (component instanceof CodePanel) {
|
||||||
|
((CodePanel) component).dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,11 +16,10 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import jadx.api.ICodeInfo;
|
import jadx.api.ICodeInfo;
|
||||||
import jadx.api.JadxDecompiler;
|
|
||||||
import jadx.api.JavaClass;
|
import jadx.api.JavaClass;
|
||||||
import jadx.api.JavaNode;
|
import jadx.api.JavaNode;
|
||||||
import jadx.api.metadata.ICodeAnnotation;
|
import jadx.api.metadata.ICodeAnnotation;
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.gui.JadxWrapper;
|
||||||
import jadx.gui.settings.JadxProject;
|
import jadx.gui.settings.JadxProject;
|
||||||
import jadx.gui.treemodel.JClass;
|
import jadx.gui.treemodel.JClass;
|
||||||
import jadx.gui.treemodel.JNode;
|
import jadx.gui.treemodel.JNode;
|
||||||
@@ -59,7 +58,7 @@ public final class CodeArea extends AbstractCodeArea {
|
|||||||
addMouseListener(new MouseAdapter() {
|
addMouseListener(new MouseAdapter() {
|
||||||
@Override
|
@Override
|
||||||
public void mouseClicked(MouseEvent e) {
|
public void mouseClicked(MouseEvent e) {
|
||||||
if (e.getClickCount() % 2 == 0 || e.isControlDown()) {
|
if (e.isControlDown() || jumpOnDoubleClick(e)) {
|
||||||
navToDecl(e.getPoint(), codeLinkGenerator);
|
navToDecl(e.getPoint(), codeLinkGenerator);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -70,6 +69,10 @@ public final class CodeArea extends AbstractCodeArea {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean jumpOnDoubleClick(MouseEvent e) {
|
||||||
|
return e.getClickCount() == 2 && getMainWindow().getSettings().isJumpOnDoubleClick();
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
private void navToDecl(Point point, CodeLinkGenerator codeLinkGenerator) {
|
private void navToDecl(Point point, CodeLinkGenerator codeLinkGenerator) {
|
||||||
int offs = viewToModel(point);
|
int offs = viewToModel(point);
|
||||||
@@ -82,6 +85,10 @@ public final class CodeArea extends AbstractCodeArea {
|
|||||||
@Override
|
@Override
|
||||||
public ICodeInfo getCodeInfo() {
|
public ICodeInfo getCodeInfo() {
|
||||||
if (cachedCodeInfo == null) {
|
if (cachedCodeInfo == null) {
|
||||||
|
if (isDisposed()) {
|
||||||
|
LOG.debug("CodeArea used after dispose!");
|
||||||
|
return ICodeInfo.EMPTY;
|
||||||
|
}
|
||||||
cachedCodeInfo = Objects.requireNonNull(node.getCodeInfo());
|
cachedCodeInfo = Objects.requireNonNull(node.getCodeInfo());
|
||||||
}
|
}
|
||||||
return cachedCodeInfo;
|
return cachedCodeInfo;
|
||||||
@@ -223,7 +230,7 @@ public final class CodeArea extends AbstractCodeArea {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return getDecompiler().getJavaNodeAtPosition(getCodeInfo(), offset);
|
return getJadxWrapper().getDecompiler().getJavaNodeAtPosition(getCodeInfo(), offset);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error("Can't get java node by offset: {}", offset, e);
|
LOG.error("Can't get java node by offset: {}", offset, e);
|
||||||
}
|
}
|
||||||
@@ -232,7 +239,7 @@ public final class CodeArea extends AbstractCodeArea {
|
|||||||
|
|
||||||
public JavaNode getClosestJavaNode(int offset) {
|
public JavaNode getClosestJavaNode(int offset) {
|
||||||
try {
|
try {
|
||||||
return getDecompiler().getClosestJavaNode(getCodeInfo(), offset);
|
return getJadxWrapper().getDecompiler().getClosestJavaNode(getCodeInfo(), offset);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error("Can't get java node by offset: {}", offset, e);
|
LOG.error("Can't get java node by offset: {}", offset, e);
|
||||||
return null;
|
return null;
|
||||||
@@ -244,8 +251,8 @@ public final class CodeArea extends AbstractCodeArea {
|
|||||||
ICodeInfo codeInfo = getCodeInfo();
|
ICodeInfo codeInfo = getCodeInfo();
|
||||||
if (codeInfo.hasMetadata()) {
|
if (codeInfo.hasMetadata()) {
|
||||||
ICodeAnnotation ann = codeInfo.getCodeMetadata().getAt(pos);
|
ICodeAnnotation ann = codeInfo.getCodeMetadata().getAt(pos);
|
||||||
if (ann instanceof ClassNode) {
|
if (ann != null && ann.getAnnType() == ICodeAnnotation.AnnType.CLASS) {
|
||||||
return getDecompiler().getJavaClassByNode(((ClassNode) ann));
|
return (JavaClass) getJadxWrapper().getDecompiler().getJavaNodeByCodeAnnotation(codeInfo, ann);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -276,11 +283,17 @@ public final class CodeArea extends AbstractCodeArea {
|
|||||||
return contentPanel.getTabbedPane().getMainWindow();
|
return contentPanel.getTabbedPane().getMainWindow();
|
||||||
}
|
}
|
||||||
|
|
||||||
public JadxDecompiler getDecompiler() {
|
public JadxWrapper getJadxWrapper() {
|
||||||
return getMainWindow().getWrapper().getDecompiler();
|
return getMainWindow().getWrapper();
|
||||||
}
|
}
|
||||||
|
|
||||||
public JadxProject getProject() {
|
public JadxProject getProject() {
|
||||||
return getMainWindow().getProject();
|
return getMainWindow().getProject();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
cachedCodeInfo = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,4 +62,9 @@ public final class CodeContentPanel extends AbstractCodeContentPanel implements
|
|||||||
codePanel.getCodeScrollPane().getViewport().setViewPosition(viewState.getViewPoint());
|
codePanel.getCodeScrollPane().getViewport().setViewPosition(viewState.getViewPoint());
|
||||||
codePanel.getCodeArea().setCaretPosition(viewState.getCaretPos());
|
codePanel.getCodeArea().setCaretPosition(viewState.getCaretPos());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose() {
|
||||||
|
codePanel.dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -195,4 +195,8 @@ public class CodePanel extends JPanel {
|
|||||||
return this.codeArea.getContentPanel().getTabbedPane()
|
return this.codeArea.getContentPanel().getTabbedPane()
|
||||||
.getMainWindow().getSettings();
|
.getMainWindow().getSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void dispose() {
|
||||||
|
codeArea.dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import jadx.api.ICodeInfo;
|
import jadx.api.ICodeInfo;
|
||||||
import jadx.api.JadxDecompiler;
|
|
||||||
import jadx.api.JavaClass;
|
|
||||||
import jadx.api.JavaMethod;
|
import jadx.api.JavaMethod;
|
||||||
import jadx.api.JavaNode;
|
import jadx.api.JavaNode;
|
||||||
import jadx.api.data.ICodeComment;
|
import jadx.api.data.ICodeComment;
|
||||||
@@ -25,8 +23,8 @@ import jadx.api.metadata.ICodeMetadata;
|
|||||||
import jadx.api.metadata.ICodeNodeRef;
|
import jadx.api.metadata.ICodeNodeRef;
|
||||||
import jadx.api.metadata.annotations.InsnCodeOffset;
|
import jadx.api.metadata.annotations.InsnCodeOffset;
|
||||||
import jadx.api.metadata.annotations.NodeDeclareRef;
|
import jadx.api.metadata.annotations.NodeDeclareRef;
|
||||||
|
import jadx.gui.JadxWrapper;
|
||||||
import jadx.gui.treemodel.JClass;
|
import jadx.gui.treemodel.JClass;
|
||||||
import jadx.gui.treemodel.JNode;
|
|
||||||
import jadx.gui.ui.dialog.CommentDialog;
|
import jadx.gui.ui.dialog.CommentDialog;
|
||||||
import jadx.gui.utils.DefaultPopupMenuListener;
|
import jadx.gui.utils.DefaultPopupMenuListener;
|
||||||
import jadx.gui.utils.NLS;
|
import jadx.gui.utils.NLS;
|
||||||
@@ -39,28 +37,29 @@ public class CommentAction extends AbstractAction implements DefaultPopupMenuLis
|
|||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(CommentAction.class);
|
private static final Logger LOG = LoggerFactory.getLogger(CommentAction.class);
|
||||||
private final CodeArea codeArea;
|
private final CodeArea codeArea;
|
||||||
private final JavaClass topCls;
|
private final boolean enabled;
|
||||||
|
|
||||||
private ICodeComment actionComment;
|
private ICodeComment actionComment;
|
||||||
|
|
||||||
public CommentAction(CodeArea codeArea) {
|
public CommentAction(CodeArea codeArea) {
|
||||||
super(NLS.str("popup.add_comment") + " (;)");
|
super(NLS.str("popup.add_comment") + " (;)");
|
||||||
this.codeArea = codeArea;
|
this.codeArea = codeArea;
|
||||||
JNode topNode = codeArea.getNode();
|
this.enabled = codeArea.getNode() instanceof JClass;
|
||||||
if (topNode instanceof JClass) {
|
if (enabled) {
|
||||||
this.topCls = ((JClass) topNode).getCls();
|
UiUtils.addKeyBinding(codeArea, getKeyStroke(KeyEvent.VK_SEMICOLON, 0), "popup.add_comment",
|
||||||
} else {
|
() -> showCommentDialog(getCommentRef(codeArea.getCaretPosition())));
|
||||||
this.topCls = null;
|
|
||||||
}
|
}
|
||||||
UiUtils.addKeyBinding(codeArea, getKeyStroke(KeyEvent.VK_SEMICOLON, 0), "popup.add_comment",
|
|
||||||
() -> showCommentDialog(getCommentRef(codeArea.getCaretPosition())));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
|
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
|
||||||
ICodeComment codeComment = getCommentRef(UiUtils.getOffsetAtMousePosition(codeArea));
|
if (enabled) {
|
||||||
setEnabled(codeComment != null);
|
ICodeComment codeComment = getCommentRef(UiUtils.getOffsetAtMousePosition(codeArea));
|
||||||
this.actionComment = codeComment;
|
setEnabled(codeComment != null);
|
||||||
|
this.actionComment = codeComment;
|
||||||
|
} else {
|
||||||
|
setEnabled(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -83,11 +82,11 @@ public class CommentAction extends AbstractAction implements DefaultPopupMenuLis
|
|||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
private ICodeComment getCommentRef(int pos) {
|
private ICodeComment getCommentRef(int pos) {
|
||||||
if (pos == -1 || this.topCls == null) {
|
if (pos == -1) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
JadxDecompiler decompiler = codeArea.getDecompiler();
|
JadxWrapper wrapper = codeArea.getJadxWrapper();
|
||||||
ICodeInfo codeInfo = codeArea.getCodeInfo();
|
ICodeInfo codeInfo = codeArea.getCodeInfo();
|
||||||
ICodeMetadata metadata = codeInfo.getCodeMetadata();
|
ICodeMetadata metadata = codeInfo.getCodeMetadata();
|
||||||
int lineStartPos = codeArea.getLineStartFor(pos);
|
int lineStartPos = codeArea.getLineStartFor(pos);
|
||||||
@@ -95,7 +94,7 @@ public class CommentAction extends AbstractAction implements DefaultPopupMenuLis
|
|||||||
// add method line comment by instruction offset
|
// add method line comment by instruction offset
|
||||||
ICodeAnnotation offsetAnn = metadata.searchUp(pos, lineStartPos, AnnType.OFFSET);
|
ICodeAnnotation offsetAnn = metadata.searchUp(pos, lineStartPos, AnnType.OFFSET);
|
||||||
if (offsetAnn instanceof InsnCodeOffset) {
|
if (offsetAnn instanceof InsnCodeOffset) {
|
||||||
JavaNode node = decompiler.getJavaNodeByRef(metadata.getNodeAt(pos));
|
JavaNode node = wrapper.getJavaNodeByRef(metadata.getNodeAt(pos));
|
||||||
if (node instanceof JavaMethod) {
|
if (node instanceof JavaMethod) {
|
||||||
int rawOffset = ((InsnCodeOffset) offsetAnn).getOffset();
|
int rawOffset = ((InsnCodeOffset) offsetAnn).getOffset();
|
||||||
JadxNodeRef nodeRef = JadxNodeRef.forMth((JavaMethod) node);
|
JadxNodeRef nodeRef = JadxNodeRef.forMth((JavaMethod) node);
|
||||||
@@ -114,7 +113,7 @@ public class CommentAction extends AbstractAction implements DefaultPopupMenuLis
|
|||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
if (nodeDef != null) {
|
if (nodeDef != null) {
|
||||||
JadxNodeRef nodeRef = JadxNodeRef.forJavaNode(decompiler.getJavaNodeByRef(nodeDef));
|
JadxNodeRef nodeRef = JadxNodeRef.forJavaNode(wrapper.getJavaNodeByRef(nodeDef));
|
||||||
return new JadxCodeComment(nodeRef, "");
|
return new JadxCodeComment(nodeRef, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,7 +127,7 @@ public class CommentAction extends AbstractAction implements DefaultPopupMenuLis
|
|||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
if (nodeRef != null) {
|
if (nodeRef != null) {
|
||||||
JavaNode defNode = decompiler.getJavaNodeByRef(nodeRef);
|
JavaNode defNode = wrapper.getJavaNodeByRef(nodeRef);
|
||||||
return new JadxCodeComment(JadxNodeRef.forJavaNode(defNode), "");
|
return new JadxCodeComment(JadxNodeRef.forJavaNode(defNode), "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,15 +92,24 @@ public final class FridaAction extends JNodeAction {
|
|||||||
} else {
|
} else {
|
||||||
functionUntilImplementation = String.format("%s[\"%s\"].implementation", shortClassName, methodName);
|
functionUntilImplementation = String.format("%s[\"%s\"].implementation", shortClassName, methodName);
|
||||||
}
|
}
|
||||||
String functionParametersString = String.join(", ", collectMethodArgNames(javaMethod));
|
|
||||||
|
List<String> methodArgNames = collectMethodArgNames(javaMethod);
|
||||||
|
|
||||||
|
String functionParametersString = String.join(", ", methodArgNames);
|
||||||
|
String logParametersString =
|
||||||
|
methodArgNames.stream().map(e -> String.format("'%s: ' + %s", e, e)).collect(Collectors.joining(" + ', ' + "));
|
||||||
|
if (logParametersString.length() > 0) {
|
||||||
|
logParametersString = " + ', ' + " + logParametersString;
|
||||||
|
}
|
||||||
String functionParameterAndBody = String.format(
|
String functionParameterAndBody = String.format(
|
||||||
"%s = function(%s){\n"
|
"%s = function (%s) {\n"
|
||||||
+ " console.log('%s is called');\n"
|
+ " console.log('%s is called'%s);\n"
|
||||||
+ " let ret = this.%s(%s);\n"
|
+ " let ret = this.%s(%s);\n"
|
||||||
+ " console.log('%s ret value is ' + ret);\n"
|
+ " console.log('%s ret value is ' + ret);\n"
|
||||||
+ " return ret;\n"
|
+ " return ret;\n"
|
||||||
+ "};",
|
+ "};",
|
||||||
functionUntilImplementation, functionParametersString, methodName, methodName, functionParametersString, methodName);
|
functionUntilImplementation, functionParametersString, methodName, logParametersString, methodName,
|
||||||
|
functionParametersString, methodName);
|
||||||
|
|
||||||
return generateClassSnippet(jMth.getJParent()) + "\n" + functionParameterAndBody;
|
return generateClassSnippet(jMth.getJParent()) + "\n" + functionParameterAndBody;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package jadx.gui.ui.codearea;
|
package jadx.gui.ui.codearea;
|
||||||
|
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.beans.PropertyChangeListener;
|
||||||
|
|
||||||
import javax.swing.AbstractAction;
|
import javax.swing.AbstractAction;
|
||||||
import javax.swing.KeyStroke;
|
import javax.swing.KeyStroke;
|
||||||
@@ -16,7 +17,7 @@ import jadx.gui.utils.UiUtils;
|
|||||||
public abstract class JNodeAction extends AbstractAction {
|
public abstract class JNodeAction extends AbstractAction {
|
||||||
private static final long serialVersionUID = -2600154727884853550L;
|
private static final long serialVersionUID = -2600154727884853550L;
|
||||||
|
|
||||||
private final transient CodeArea codeArea;
|
private transient CodeArea codeArea;
|
||||||
private transient @Nullable JNode node;
|
private transient @Nullable JNode node;
|
||||||
|
|
||||||
public JNodeAction(String name, CodeArea codeArea) {
|
public JNodeAction(String name, CodeArea codeArea) {
|
||||||
@@ -26,7 +27,7 @@ public abstract class JNodeAction extends AbstractAction {
|
|||||||
|
|
||||||
public abstract void runAction(JNode node);
|
public abstract void runAction(JNode node);
|
||||||
|
|
||||||
public boolean isActionEnabled(JNode node) {
|
public boolean isActionEnabled(@Nullable JNode node) {
|
||||||
return node != null;
|
return node != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,7 +48,7 @@ public abstract class JNodeAction extends AbstractAction {
|
|||||||
runAction(node);
|
runAction(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void changeNode(JNode node) {
|
public void changeNode(@Nullable JNode node) {
|
||||||
this.node = node;
|
this.node = node;
|
||||||
setEnabled(isActionEnabled(node));
|
setEnabled(isActionEnabled(node));
|
||||||
}
|
}
|
||||||
@@ -55,4 +56,12 @@ public abstract class JNodeAction extends AbstractAction {
|
|||||||
public CodeArea getCodeArea() {
|
public CodeArea getCodeArea() {
|
||||||
return codeArea;
|
return codeArea;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void dispose() {
|
||||||
|
node = null;
|
||||||
|
codeArea = null;
|
||||||
|
for (PropertyChangeListener changeListener : getPropertyChangeListeners()) {
|
||||||
|
removePropertyChangeListener(changeListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.swing.event.PopupMenuEvent;
|
import javax.swing.event.PopupMenuEvent;
|
||||||
|
import javax.swing.event.PopupMenuListener;
|
||||||
|
|
||||||
import jadx.gui.treemodel.JNode;
|
import jadx.gui.treemodel.JNode;
|
||||||
import jadx.gui.utils.DefaultPopupMenuListener;
|
|
||||||
|
|
||||||
public final class JNodePopupListener implements DefaultPopupMenuListener {
|
public final class JNodePopupListener implements PopupMenuListener {
|
||||||
private final CodeArea codeArea;
|
private final CodeArea codeArea;
|
||||||
private final List<JNodeAction> actions = new ArrayList<>();
|
private final List<JNodeAction> actions = new ArrayList<>();
|
||||||
|
|
||||||
@@ -16,13 +16,28 @@ public final class JNodePopupListener implements DefaultPopupMenuListener {
|
|||||||
this.codeArea = codeArea;
|
this.codeArea = codeArea;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
|
|
||||||
JNode node = codeArea.getNodeUnderMouse();
|
|
||||||
actions.forEach(action -> action.changeNode(node));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addActions(JNodeAction action) {
|
public void addActions(JNodeAction action) {
|
||||||
actions.add(action);
|
actions.add(action);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateNode(JNode node) {
|
||||||
|
for (JNodeAction action : actions) {
|
||||||
|
action.changeNode(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
|
||||||
|
updateNode(codeArea.getNodeUnderMouse());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
|
||||||
|
// this event can be called just before running action, so can't reset node here
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void popupMenuCanceled(PopupMenuEvent e) {
|
||||||
|
updateNode(null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,9 +29,12 @@ public final class JadxTokenMaker extends JavaTokenMaker {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Token getTokenList(Segment text, int initialTokenType, int startOffset) {
|
public Token getTokenList(Segment text, int initialTokenType, int startOffset) {
|
||||||
|
if (codeArea.isDisposed()) {
|
||||||
|
return new TokenImpl();
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
Token tokens = super.getTokenList(text, initialTokenType, startOffset);
|
Token tokens = super.getTokenList(text, initialTokenType, startOffset);
|
||||||
if (tokens.getType() != TokenTypes.NULL) {
|
if (tokens != null && tokens.getType() != TokenTypes.NULL) {
|
||||||
processTokens(tokens);
|
processTokens(tokens);
|
||||||
}
|
}
|
||||||
return tokens;
|
return tokens;
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ public class CommentDialog extends JDialog {
|
|||||||
Collections.sort(list);
|
Collections.sort(list);
|
||||||
codeData.setComments(list);
|
codeData.setComments(list);
|
||||||
project.setCodeData(codeData);
|
project.setCodeData(codeData);
|
||||||
codeArea.getMainWindow().getWrapper().getDecompiler().reloadCodeData();
|
codeArea.getMainWindow().getWrapper().reloadCodeData();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error("Comment action failed", e);
|
LOG.error("Comment action failed", e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ public class ExcludePkgDialog extends JDialog {
|
|||||||
|
|
||||||
btnOk.addActionListener(e -> {
|
btnOk.addActionListener(e -> {
|
||||||
mainWindow.getWrapper().setExcludedPackages(getExcludes());
|
mainWindow.getWrapper().setExcludedPackages(getExcludes());
|
||||||
mainWindow.reOpenFile();
|
mainWindow.reopen();
|
||||||
dispose();
|
dispose();
|
||||||
});
|
});
|
||||||
btnAll.addActionListener(e -> {
|
btnAll.addActionListener(e -> {
|
||||||
@@ -121,7 +121,7 @@ public class ExcludePkgDialog extends JDialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void initPackageList() {
|
private void initPackageList() {
|
||||||
List<String> pkgs = mainWindow.getWrapper().getDecompiler().getPackages()
|
List<String> pkgs = mainWindow.getWrapper().getPackages()
|
||||||
.stream()
|
.stream()
|
||||||
.map(JavaPackage::getFullName)
|
.map(JavaPackage::getFullName)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|||||||
@@ -27,7 +27,12 @@ import jadx.gui.utils.NLS;
|
|||||||
public class FileDialog {
|
public class FileDialog {
|
||||||
|
|
||||||
public enum OpenMode {
|
public enum OpenMode {
|
||||||
OPEN, ADD, SAVE_PROJECT, EXPORT
|
OPEN,
|
||||||
|
ADD,
|
||||||
|
SAVE_PROJECT,
|
||||||
|
EXPORT,
|
||||||
|
CUSTOM_SAVE,
|
||||||
|
CUSTOM_OPEN
|
||||||
}
|
}
|
||||||
|
|
||||||
private final MainWindow mainWindow;
|
private final MainWindow mainWindow;
|
||||||
@@ -44,6 +49,26 @@ public class FileDialog {
|
|||||||
initForMode(mode);
|
initForMode(mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setTitle(String title) {
|
||||||
|
this.title = title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFileExtList(List<String> fileExtList) {
|
||||||
|
this.fileExtList = fileExtList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSelectionMode(int selectionMode) {
|
||||||
|
this.selectionMode = selectionMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSelectedFile(Path path) {
|
||||||
|
this.selectedFile = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCurrentDir(Path currentDir) {
|
||||||
|
this.currentDir = currentDir;
|
||||||
|
}
|
||||||
|
|
||||||
public List<Path> show() {
|
public List<Path> show() {
|
||||||
FileChooser fileChooser = buildFileChooser();
|
FileChooser fileChooser = buildFileChooser();
|
||||||
int ret = isOpen ? fileChooser.showOpenDialog(mainWindow) : fileChooser.showSaveDialog(mainWindow);
|
int ret = isOpen ? fileChooser.showOpenDialog(mainWindow) : fileChooser.showSaveDialog(mainWindow);
|
||||||
@@ -51,17 +76,21 @@ public class FileDialog {
|
|||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
currentDir = fileChooser.getCurrentDirectory().toPath();
|
currentDir = fileChooser.getCurrentDirectory().toPath();
|
||||||
return FileUtils.toPaths(fileChooser.getSelectedFiles());
|
File[] selectedFiles = fileChooser.getSelectedFiles();
|
||||||
|
if (selectedFiles.length != 0) {
|
||||||
|
return FileUtils.toPaths(selectedFiles);
|
||||||
|
}
|
||||||
|
File chosenFile = fileChooser.getSelectedFile();
|
||||||
|
if (chosenFile != null) {
|
||||||
|
return Collections.singletonList(chosenFile.toPath());
|
||||||
|
}
|
||||||
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Path getCurrentDir() {
|
public Path getCurrentDir() {
|
||||||
return currentDir;
|
return currentDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSelectedFile(Path path) {
|
|
||||||
this.selectedFile = path;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initForMode(OpenMode mode) {
|
private void initForMode(OpenMode mode) {
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case OPEN:
|
case OPEN:
|
||||||
@@ -93,6 +122,14 @@ public class FileDialog {
|
|||||||
currentDir = mainWindow.getSettings().getLastSaveFilePath();
|
currentDir = mainWindow.getSettings().getLastSaveFilePath();
|
||||||
isOpen = false;
|
isOpen = false;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case CUSTOM_SAVE:
|
||||||
|
isOpen = false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CUSTOM_OPEN:
|
||||||
|
isOpen = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,7 +139,7 @@ public class FileDialog {
|
|||||||
fileChooser.setFileSelectionMode(selectionMode);
|
fileChooser.setFileSelectionMode(selectionMode);
|
||||||
fileChooser.setMultiSelectionEnabled(isOpen);
|
fileChooser.setMultiSelectionEnabled(isOpen);
|
||||||
fileChooser.setAcceptAllFileFilterUsed(true);
|
fileChooser.setAcceptAllFileFilterUsed(true);
|
||||||
if (!fileExtList.isEmpty()) {
|
if (Utils.notEmpty(fileExtList)) {
|
||||||
String description = NLS.str("file_dialog.supported_files") + ": (" + Utils.listToString(fileExtList) + ')';
|
String description = NLS.str("file_dialog.supported_files") + ": (" + Utils.listToString(fileExtList) + ')';
|
||||||
fileChooser.setFileFilter(new FileNameExtensionFilter(description, fileExtList.toArray(new String[0])));
|
fileChooser.setFileFilter(new FileNameExtensionFilter(description, fileExtList.toArray(new String[0])));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,8 +38,6 @@ import jadx.api.data.impl.JadxCodeData;
|
|||||||
import jadx.api.data.impl.JadxCodeRef;
|
import jadx.api.data.impl.JadxCodeRef;
|
||||||
import jadx.api.data.impl.JadxCodeRename;
|
import jadx.api.data.impl.JadxCodeRename;
|
||||||
import jadx.api.data.impl.JadxNodeRef;
|
import jadx.api.data.impl.JadxNodeRef;
|
||||||
import jadx.core.dex.nodes.RootNode;
|
|
||||||
import jadx.core.dex.visitors.rename.RenameVisitor;
|
|
||||||
import jadx.core.utils.Utils;
|
import jadx.core.utils.Utils;
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
import jadx.gui.jobs.TaskStatus;
|
import jadx.gui.jobs.TaskStatus;
|
||||||
@@ -168,12 +166,11 @@ public class RenameDialog extends JDialog {
|
|||||||
Collections.sort(list);
|
Collections.sort(list);
|
||||||
codeData.setRenames(list);
|
codeData.setRenames(list);
|
||||||
project.setCodeData(codeData);
|
project.setCodeData(codeData);
|
||||||
mainWindow.getWrapper().getDecompiler().reloadCodeData();
|
mainWindow.getWrapper().reloadCodeData();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void refreshState() {
|
private void refreshState() {
|
||||||
RootNode rootNode = mainWindow.getWrapper().getDecompiler().getRoot();
|
mainWindow.getWrapper().reInitRenameVisitor();
|
||||||
new RenameVisitor().init(rootNode);
|
|
||||||
|
|
||||||
JNodeCache nodeCache = cache.getNodeCache();
|
JNodeCache nodeCache = cache.getNodeCache();
|
||||||
JavaNode javaNode = node.getJavaNode();
|
JavaNode javaNode = node.getJavaNode();
|
||||||
|
|||||||
@@ -143,11 +143,18 @@ public class SearchDialog extends CommonSearchDialog {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void dispose() {
|
public void dispose() {
|
||||||
stopSearchTask();
|
|
||||||
if (searchDisposable != null && !searchDisposable.isDisposed()) {
|
if (searchDisposable != null && !searchDisposable.isDisposed()) {
|
||||||
searchDisposable.dispose();
|
searchDisposable.dispose();
|
||||||
}
|
}
|
||||||
|
resultsModel.clear();
|
||||||
removeActiveTabListener();
|
removeActiveTabListener();
|
||||||
|
if (searchTask != null) {
|
||||||
|
searchTask.cancel();
|
||||||
|
mainWindow.getBackgroundExecutor().execute(NLS.str("progress.load"), () -> {
|
||||||
|
stopSearchTask();
|
||||||
|
unloadTempData();
|
||||||
|
});
|
||||||
|
}
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -300,7 +307,7 @@ public class SearchDialog extends CommonSearchDialog {
|
|||||||
Flowable<String> textChanges = onTextFieldChanges(searchField);
|
Flowable<String> textChanges = onTextFieldChanges(searchField);
|
||||||
Flowable<String> searchEvents = Flowable.merge(textChanges, searchEmitter.getFlowable());
|
Flowable<String> searchEvents = Flowable.merge(textChanges, searchEmitter.getFlowable());
|
||||||
searchDisposable = searchEvents
|
searchDisposable = searchEvents
|
||||||
.debounce(100, TimeUnit.MILLISECONDS)
|
.debounce(500, TimeUnit.MILLISECONDS)
|
||||||
.observeOn(SwingSchedulers.edt())
|
.observeOn(SwingSchedulers.edt())
|
||||||
.subscribe(this::search);
|
.subscribe(this::search);
|
||||||
}
|
}
|
||||||
@@ -335,6 +342,7 @@ public class SearchDialog extends CommonSearchDialog {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateTableHighlight();
|
||||||
startSearch();
|
startSearch();
|
||||||
searchTask.setResultsLimit(100);
|
searchTask.setResultsLimit(100);
|
||||||
searchTask.setProgressListener(this::updateProgress);
|
searchTask.setProgressListener(this::updateProgress);
|
||||||
@@ -399,6 +407,7 @@ public class SearchDialog extends CommonSearchDialog {
|
|||||||
if (searchTask != null) {
|
if (searchTask != null) {
|
||||||
searchTask.cancel();
|
searchTask.cancel();
|
||||||
searchTask.waitTask();
|
searchTask.waitTask();
|
||||||
|
searchTask = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -468,16 +477,21 @@ public class SearchDialog extends CommonSearchDialog {
|
|||||||
private synchronized void searchComplete() {
|
private synchronized void searchComplete() {
|
||||||
UiUtils.uiThreadGuard();
|
UiUtils.uiThreadGuard();
|
||||||
LOG.debug("Search complete");
|
LOG.debug("Search complete");
|
||||||
updateTableHighlight();
|
|
||||||
updateTable();
|
updateTable();
|
||||||
|
|
||||||
boolean complete = searchTask == null || searchTask.isSearchComplete();
|
boolean complete = searchTask == null || searchTask.isSearchComplete();
|
||||||
loadAllButton.setEnabled(!complete);
|
loadAllButton.setEnabled(!complete);
|
||||||
loadMoreButton.setEnabled(!complete);
|
loadMoreButton.setEnabled(!complete);
|
||||||
updateProgressLabel(complete);
|
updateProgressLabel(complete);
|
||||||
|
unloadTempData();
|
||||||
progressFinishedCommon();
|
progressFinishedCommon();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void unloadTempData() {
|
||||||
|
mainWindow.getWrapper().unloadClasses();
|
||||||
|
System.gc();
|
||||||
|
}
|
||||||
|
|
||||||
private static Flowable<String> onTextFieldChanges(final JTextField textField) {
|
private static Flowable<String> onTextFieldChanges(final JTextField textField) {
|
||||||
return Flowable.<String>create(emitter -> {
|
return Flowable.<String>create(emitter -> {
|
||||||
DocumentListener listener = new DocumentListener() {
|
DocumentListener listener = new DocumentListener() {
|
||||||
|
|||||||
@@ -16,11 +16,11 @@ import javax.swing.SwingConstants;
|
|||||||
import javax.swing.WindowConstants;
|
import javax.swing.WindowConstants;
|
||||||
|
|
||||||
import jadx.api.ICodeInfo;
|
import jadx.api.ICodeInfo;
|
||||||
import jadx.api.JadxDecompiler;
|
|
||||||
import jadx.api.JavaClass;
|
import jadx.api.JavaClass;
|
||||||
import jadx.api.JavaMethod;
|
import jadx.api.JavaMethod;
|
||||||
import jadx.api.JavaNode;
|
import jadx.api.JavaNode;
|
||||||
import jadx.api.utils.CodeUtils;
|
import jadx.api.utils.CodeUtils;
|
||||||
|
import jadx.gui.JadxWrapper;
|
||||||
import jadx.gui.jobs.TaskStatus;
|
import jadx.gui.jobs.TaskStatus;
|
||||||
import jadx.gui.treemodel.CodeNode;
|
import jadx.gui.treemodel.CodeNode;
|
||||||
import jadx.gui.treemodel.JMethod;
|
import jadx.gui.treemodel.JMethod;
|
||||||
@@ -97,18 +97,14 @@ public class UsageDialog extends CommonSearchDialog {
|
|||||||
private void processUsage(JavaNode searchNode, JavaClass topUseClass) {
|
private void processUsage(JavaNode searchNode, JavaClass topUseClass) {
|
||||||
ICodeInfo codeInfo = topUseClass.getCodeInfo();
|
ICodeInfo codeInfo = topUseClass.getCodeInfo();
|
||||||
String code = codeInfo.getCodeStr();
|
String code = codeInfo.getCodeStr();
|
||||||
JadxDecompiler decompiler = mainWindow.getWrapper().getDecompiler();
|
JadxWrapper wrapper = mainWindow.getWrapper();
|
||||||
List<Integer> usePositions = topUseClass.getUsePlacesFor(codeInfo, searchNode);
|
List<Integer> usePositions = topUseClass.getUsePlacesFor(codeInfo, searchNode);
|
||||||
for (int pos : usePositions) {
|
for (int pos : usePositions) {
|
||||||
if (searchNode.getTopParentClass().equals(topUseClass) && pos == searchNode.getDefPos()) {
|
|
||||||
// skip declaration
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
String line = CodeUtils.getLineForPos(code, pos);
|
String line = CodeUtils.getLineForPos(code, pos);
|
||||||
if (line.startsWith("import ")) {
|
if (line.startsWith("import ")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
JavaNode enclosingNode = decompiler.getEnclosingNode(codeInfo, pos);
|
JavaNode enclosingNode = wrapper.getEnclosingNode(codeInfo, pos);
|
||||||
JavaNode usageNode = enclosingNode == null ? topUseClass : enclosingNode;
|
JavaNode usageNode = enclosingNode == null ? topUseClass : enclosingNode;
|
||||||
usageList.add(new CodeNode(getNodeCache().makeFrom(usageNode), line.trim(), pos));
|
usageList.add(new CodeNode(getNodeCache().makeFrom(usageNode), line.trim(), pos));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ public abstract class ContentPanel extends JPanel {
|
|||||||
|
|
||||||
private static final long serialVersionUID = 3237031760631677822L;
|
private static final long serialVersionUID = 3237031760631677822L;
|
||||||
|
|
||||||
protected final TabbedPane tabbedPane;
|
protected TabbedPane tabbedPane;
|
||||||
protected final JNode node;
|
protected JNode node;
|
||||||
|
|
||||||
protected ContentPanel(TabbedPane panel, JNode jnode) {
|
protected ContentPanel(TabbedPane panel, JNode jnode) {
|
||||||
tabbedPane = panel;
|
tabbedPane = panel;
|
||||||
@@ -44,4 +44,9 @@ public abstract class ContentPanel extends JPanel {
|
|||||||
}
|
}
|
||||||
return node.getName();
|
return node.getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void dispose() {
|
||||||
|
tabbedPane = null;
|
||||||
|
node = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ public class JPackagePopupMenu extends JPopupMenu {
|
|||||||
} else {
|
} else {
|
||||||
wrapper.removeExcludedPackage(fullName);
|
wrapper.removeExcludedPackage(fullName);
|
||||||
}
|
}
|
||||||
mainWindow.reOpenFile();
|
mainWindow.reopen();
|
||||||
});
|
});
|
||||||
return excludeItem;
|
return excludeItem;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import javax.swing.ImageIcon;
|
|||||||
import org.apache.commons.text.StringEscapeUtils;
|
import org.apache.commons.text.StringEscapeUtils;
|
||||||
|
|
||||||
import jadx.api.ICodeInfo;
|
import jadx.api.ICodeInfo;
|
||||||
import jadx.api.JadxDecompiler;
|
|
||||||
import jadx.api.impl.SimpleCodeInfo;
|
import jadx.api.impl.SimpleCodeInfo;
|
||||||
import jadx.core.dex.attributes.IAttributeNode;
|
import jadx.core.dex.attributes.IAttributeNode;
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
@@ -21,6 +20,7 @@ import jadx.core.dex.nodes.MethodNode;
|
|||||||
import jadx.core.dex.nodes.ProcessState;
|
import jadx.core.dex.nodes.ProcessState;
|
||||||
import jadx.core.utils.ErrorsCounter;
|
import jadx.core.utils.ErrorsCounter;
|
||||||
import jadx.core.utils.Utils;
|
import jadx.core.utils.Utils;
|
||||||
|
import jadx.gui.JadxWrapper;
|
||||||
import jadx.gui.treemodel.JClass;
|
import jadx.gui.treemodel.JClass;
|
||||||
import jadx.gui.treemodel.JNode;
|
import jadx.gui.treemodel.JNode;
|
||||||
import jadx.gui.ui.MainWindow;
|
import jadx.gui.ui.MainWindow;
|
||||||
@@ -35,9 +35,11 @@ public class SummaryNode extends JNode {
|
|||||||
private static final ImageIcon ICON = UiUtils.openSvgIcon("nodes/detailView");
|
private static final ImageIcon ICON = UiUtils.openSvgIcon("nodes/detailView");
|
||||||
|
|
||||||
private final MainWindow mainWindow;
|
private final MainWindow mainWindow;
|
||||||
|
private final JadxWrapper wrapper;
|
||||||
|
|
||||||
public SummaryNode(MainWindow mainWindow) {
|
public SummaryNode(MainWindow mainWindow) {
|
||||||
this.mainWindow = mainWindow;
|
this.mainWindow = mainWindow;
|
||||||
|
this.wrapper = mainWindow.getWrapper();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -60,17 +62,16 @@ public class SummaryNode extends JNode {
|
|||||||
|
|
||||||
private void writeInputSummary(StringEscapeUtils.Builder builder) throws IOException {
|
private void writeInputSummary(StringEscapeUtils.Builder builder) throws IOException {
|
||||||
builder.append("<h2>Input</h2>");
|
builder.append("<h2>Input</h2>");
|
||||||
JadxDecompiler jadx = mainWindow.getWrapper().getDecompiler();
|
|
||||||
builder.append("<h3>Files</h3>");
|
builder.append("<h3>Files</h3>");
|
||||||
builder.append("<ul>");
|
builder.append("<ul>");
|
||||||
for (File inputFile : jadx.getArgs().getInputFiles()) {
|
for (File inputFile : wrapper.getArgs().getInputFiles()) {
|
||||||
builder.append("<li>");
|
builder.append("<li>");
|
||||||
builder.escape(inputFile.getCanonicalFile().getAbsolutePath());
|
builder.escape(inputFile.getCanonicalFile().getAbsolutePath());
|
||||||
builder.append("</li>");
|
builder.append("</li>");
|
||||||
}
|
}
|
||||||
builder.append("</ul>");
|
builder.append("</ul>");
|
||||||
|
|
||||||
List<ClassNode> classes = jadx.getRoot().getClasses(true);
|
List<ClassNode> classes = wrapper.getRootNode().getClasses(true);
|
||||||
List<String> codeSources = classes.stream()
|
List<String> codeSources = classes.stream()
|
||||||
.map(ClassNode::getInputFileName)
|
.map(ClassNode::getInputFileName)
|
||||||
.distinct()
|
.distinct()
|
||||||
@@ -108,18 +109,21 @@ public class SummaryNode extends JNode {
|
|||||||
|
|
||||||
private void writeDecompilationSummary(StringEscapeUtils.Builder builder) {
|
private void writeDecompilationSummary(StringEscapeUtils.Builder builder) {
|
||||||
builder.append("<h2>Decompilation</h2>");
|
builder.append("<h2>Decompilation</h2>");
|
||||||
JadxDecompiler jadx = mainWindow.getWrapper().getDecompiler();
|
List<ClassNode> classes = wrapper.getRootNode().getClassesWithoutInner();
|
||||||
List<ClassNode> classes = jadx.getRoot().getClasses(false);
|
|
||||||
int classesCount = classes.size();
|
int classesCount = classes.size();
|
||||||
|
long notLoadedClasses = classes.stream().filter(c -> c.getState() == ProcessState.NOT_LOADED).count();
|
||||||
|
long loadedClasses = classes.stream().filter(c -> c.getState() == ProcessState.LOADED).count();
|
||||||
long processedClasses = classes.stream().filter(c -> c.getState() == ProcessState.PROCESS_COMPLETE).count();
|
long processedClasses = classes.stream().filter(c -> c.getState() == ProcessState.PROCESS_COMPLETE).count();
|
||||||
long generatedClasses = classes.stream().filter(c -> c.getState() == ProcessState.GENERATED_AND_UNLOADED).count();
|
long generatedClasses = classes.stream().filter(c -> c.getState() == ProcessState.GENERATED_AND_UNLOADED).count();
|
||||||
builder.append("<ul>");
|
builder.append("<ul>");
|
||||||
builder.append("<li>Top level classes: " + classesCount + "</li>");
|
builder.append("<li>Top level classes: " + classesCount + "</li>");
|
||||||
builder.append("<li>At process stage: " + valueAndPercent(processedClasses, classesCount) + "</li>");
|
builder.append("<li>Not loaded: " + valueAndPercent(notLoadedClasses, classesCount) + "</li>");
|
||||||
|
builder.append("<li>Loaded: " + valueAndPercent(loadedClasses, classesCount) + "</li>");
|
||||||
|
builder.append("<li>Processed: " + valueAndPercent(processedClasses, classesCount) + "</li>");
|
||||||
builder.append("<li>Code generated: " + valueAndPercent(generatedClasses, classesCount) + "</li>");
|
builder.append("<li>Code generated: " + valueAndPercent(generatedClasses, classesCount) + "</li>");
|
||||||
builder.append("</ul>");
|
builder.append("</ul>");
|
||||||
|
|
||||||
ErrorsCounter counter = jadx.getRoot().getErrorsCounter();
|
ErrorsCounter counter = wrapper.getRootNode().getErrorsCounter();
|
||||||
Set<IAttributeNode> problemNodes = new HashSet<>();
|
Set<IAttributeNode> problemNodes = new HashSet<>();
|
||||||
problemNodes.addAll(counter.getErrorNodes());
|
problemNodes.addAll(counter.getErrorNodes());
|
||||||
problemNodes.addAll(counter.getWarnNodes());
|
problemNodes.addAll(counter.getWarnNodes());
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import com.google.gson.Gson;
|
|||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
import jadx.api.JadxDecompiler;
|
import jadx.api.JadxDecompiler;
|
||||||
|
import jadx.core.Jadx;
|
||||||
import jadx.gui.update.data.Release;
|
import jadx.gui.update.data.Release;
|
||||||
|
|
||||||
@SuppressWarnings("SameParameterValue")
|
@SuppressWarnings("SameParameterValue")
|
||||||
@@ -56,8 +57,7 @@ public class JadxUpdate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static Release checkForNewRelease() throws IOException {
|
private static Release checkForNewRelease() throws IOException {
|
||||||
String version = JadxDecompiler.getVersion();
|
if (Jadx.isDevVersion()) {
|
||||||
if (version.contains("dev")) {
|
|
||||||
LOG.debug("Ignore check for update: development version");
|
LOG.debug("Ignore check for update: development version");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -65,11 +65,12 @@ public class JadxUpdate {
|
|||||||
if (latest == null) {
|
if (latest == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
String currentVersion = JadxDecompiler.getVersion();
|
||||||
String latestName = latest.getName();
|
String latestName = latest.getName();
|
||||||
if (latestName.equalsIgnoreCase(version)) {
|
if (latestName.equalsIgnoreCase(currentVersion)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (VersionComparator.checkAndCompare(version, latestName) >= 0) {
|
if (VersionComparator.checkAndCompare(currentVersion, latestName) >= 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
LOG.info("Found new jadx version: {}", latest);
|
LOG.info("Found new jadx version: {}", latest);
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user