Compare commits
61 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 | |||
| 21e94d8d5c | |||
| 7b1c7b967a | |||
| e4b19ab560 | |||
| 49137c9751 | |||
| 0606c90f22 | |||
| 65ade379a6 | |||
| a06df187c9 | |||
| e784c7f7df | |||
| a717392379 | |||
| a71b3a71d8 | |||
| 3366bf3dec | |||
| a505534197 | |||
| 357706b070 | |||
| e02434d135 | |||
| 4586015fc0 | |||
| 1832f2aee3 |
@@ -0,0 +1,7 @@
|
||||
version: 2
|
||||
updates:
|
||||
# Set update schedule for GitHub Actions
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
@@ -0,0 +1,86 @@
|
||||
name: Build Artifacts
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master, build-test ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'adopt'
|
||||
java-version: 8
|
||||
|
||||
- name: Set jadx version
|
||||
run: |
|
||||
JADX_LAST_TAG=$(git describe --abbrev=0 --tags)
|
||||
JADX_VERSION="${JADX_LAST_TAG:1}.$GITHUB_RUN_NUMBER-${GITHUB_SHA:0:8}"
|
||||
echo "JADX_VERSION=$JADX_VERSION" >> $GITHUB_ENV
|
||||
|
||||
- uses: burrunan/gradle-cache-action@v1
|
||||
name: Build with Gradle
|
||||
env:
|
||||
TERM: dumb
|
||||
with:
|
||||
arguments: clean dist copyExe
|
||||
|
||||
- name: Save bundle artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ format('jadx-{0}', env.JADX_VERSION) }}
|
||||
# Waiting fix for https://github.com/actions/upload-artifact/issues/39 to upload zip file
|
||||
# Upload unpacked files for now
|
||||
path: build/jadx/**/*
|
||||
if-no-files-found: error
|
||||
retention-days: 30
|
||||
|
||||
- name: Save exe artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ format('jadx-gui-{0}-no-jre-win.exe', env.JADX_VERSION) }}
|
||||
path: build/*.exe
|
||||
if-no-files-found: error
|
||||
retention-days: 30
|
||||
|
||||
build-win-bundle:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up JDK
|
||||
uses: oracle-actions/setup-java@v1 # set latest java version by default
|
||||
|
||||
- name: Print Java version
|
||||
shell: bash
|
||||
run: java -version
|
||||
|
||||
- name: Set jadx version
|
||||
shell: bash
|
||||
run: |
|
||||
JADX_LAST_TAG=$(git describe --abbrev=0 --tags)
|
||||
JADX_VERSION="${JADX_LAST_TAG:1}.$GITHUB_RUN_NUMBER-${GITHUB_SHA:0:8}"
|
||||
echo "JADX_VERSION=$JADX_VERSION" >> $GITHUB_ENV
|
||||
|
||||
- uses: gradle/gradle-build-action@v2
|
||||
name: Build with Gradle
|
||||
env:
|
||||
TERM: dumb
|
||||
with:
|
||||
arguments: clean dist -PbundleJRE=true
|
||||
|
||||
- name: Save exe bundle artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ format('jadx-gui-{0}-with-jre-win', env.JADX_VERSION) }}
|
||||
path: jadx-gui/build/*-with-jre-win/*
|
||||
if-no-files-found: error
|
||||
retention-days: 30
|
||||
@@ -0,0 +1,28 @@
|
||||
name: Build Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master, build-test ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'adopt'
|
||||
java-version: 8
|
||||
|
||||
- uses: burrunan/gradle-cache-action@v1
|
||||
name: Build with Gradle
|
||||
env:
|
||||
TERM: dumb
|
||||
with:
|
||||
arguments: clean build dist copyExe --warning-mode=all
|
||||
@@ -1,52 +0,0 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 8
|
||||
|
||||
- name: Set jadx version
|
||||
run: |
|
||||
JADX_LAST_TAG=$(git describe --abbrev=0 --tags)
|
||||
JADX_VERSION="${JADX_LAST_TAG:1}.$GITHUB_RUN_NUMBER-${GITHUB_SHA:0:8}"
|
||||
echo "JADX_VERSION=$JADX_VERSION" >> $GITHUB_ENV
|
||||
|
||||
- uses: burrunan/gradle-cache-action@v1
|
||||
name: Build with Gradle
|
||||
env:
|
||||
TERM: dumb
|
||||
TEST_INPUT_PLUGIN: dx
|
||||
with:
|
||||
arguments: clean build dist copyExe --warning-mode=all
|
||||
|
||||
- name: Save bundle artifact
|
||||
if: success() && github.event_name == 'push'
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ${{ format('jadx-{0}', env.JADX_VERSION) }}
|
||||
# Waiting fix for https://github.com/actions/upload-artifact/issues/39 to upload zip file
|
||||
# Upload unpacked files for now
|
||||
path: build/jadx/**/*
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Save exe artifact
|
||||
if: success() && github.event_name == 'push'
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ${{ format('jadx-gui-{0}-no-jre-win.exe', env.JADX_VERSION) }}
|
||||
path: build/*.exe
|
||||
if-no-files-found: error
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
queries: +security-extended
|
||||
languages: ${{ matrix.language }}
|
||||
@@ -38,4 +38,4 @@ jobs:
|
||||
./gradlew clean build -x checkstyleTest -x checkstyleMain -x test -x ':jadx-core:testClasses'
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
uses: github/codeql-action/analyze@v2
|
||||
|
||||
+2
-1
@@ -35,4 +35,5 @@ jadx-output/
|
||||
*.orig
|
||||
quark.json
|
||||
|
||||
cliff.toml
|
||||
cliff.toml
|
||||
jadx-gui/src/main/resources/logback.xml
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
## JADX
|
||||
|
||||
[](https://github.com/skylot/jadx/actions?query=workflow%3ABuild)
|
||||
[](https://lgtm.com/projects/g/skylot/jadx/alerts/)
|
||||
[](https://github.com/semantic-release/semantic-release)
|
||||

|
||||

|
||||

|
||||

|
||||
[](https://search.maven.org/search?q=g:io.github.skylot%20AND%20jadx)
|
||||
[](http://www.apache.org/licenses/LICENSE-2.0.html)
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
### Download
|
||||
- release from [github: ](https://github.com/skylot/jadx/releases/latest)
|
||||
- latest [unstable build](https://nightly.link/skylot/jadx/workflows/build/master)
|
||||
- release
|
||||
from [github: ](https://github.com/skylot/jadx/releases/latest)
|
||||
- latest [unstable build ](https://nightly.link/skylot/jadx/workflows/build-artifacts/master)
|
||||
|
||||
After download unpack zip file go to `bin` directory and run:
|
||||
- `jadx` - command line version
|
||||
@@ -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).
|
||||
|
||||
### Install
|
||||
1. Arch linux
|
||||
1. Arch linux 
|
||||
```bash
|
||||
sudo pacman -S jadx
|
||||
sudo pacman -S jadx
|
||||
```
|
||||
2. macOS
|
||||
2. macOS 
|
||||
```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
|
||||
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)
|
||||
'overwrite' - don't read, always save
|
||||
'ignore' - don't read and don't save
|
||||
--deobf-rewrite-cfg - set '--deobf-cfg-file-mode' to 'overwrite' (deprecated)
|
||||
--deobf-use-sourcename - use source file name as class name alias
|
||||
--deobf-parse-kotlin-metadata - parse kotlin metadata to class and package names
|
||||
--use-kotlin-methods-for-var-names - use kotlin intrinsic methods to rename variables, values: disable, apply, apply-and-hide, default: apply
|
||||
@@ -130,11 +136,11 @@ options:
|
||||
-h, --help - print this help
|
||||
|
||||
Plugin options (-P<name>=<value>):
|
||||
1) dex-input (Load .dex and .apk files)
|
||||
-Pdex-input.verify-checksum - Verify dex file checksum before load, values: [yes, no], default: yes
|
||||
2) java-convert (Convert .jar and .class files to dex)
|
||||
-Pjava-convert.mode - Convert mode, values: [dx, d8, both], default: both
|
||||
-Pjava-convert.d8-desugar - Use desugar in d8, values: [yes, no], default: no
|
||||
1) dex-input: Load .dex and .apk files
|
||||
- dex-input.verify-checksum - verify dex file checksum before load, values: [yes, no], default: yes
|
||||
2) java-convert: Convert .class, .jar and .aar files to dex
|
||||
- java-convert.mode - convert mode, values: [dx, d8, both], default: both
|
||||
- java-convert.d8-desugar - use desugar in d8, values: [yes, no], default: no
|
||||
|
||||
Examples:
|
||||
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 {
|
||||
id 'com.github.ben-manes.versions' version '0.42.0'
|
||||
id 'com.diffplug.spotless' version '6.4.2'
|
||||
id 'com.diffplug.spotless' version '6.6.1'
|
||||
}
|
||||
|
||||
ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev"
|
||||
@@ -14,7 +14,6 @@ allprojects {
|
||||
version = jadxVersion
|
||||
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
|
||||
compileJava {
|
||||
options.encoding = "UTF-8"
|
||||
@@ -32,7 +31,7 @@ allprojects {
|
||||
|
||||
testImplementation 'ch.qos.logback:logback-classic:1.2.11'
|
||||
testImplementation 'org.hamcrest:hamcrest-library:2.2'
|
||||
testImplementation 'org.mockito:mockito-core:4.4.0'
|
||||
testImplementation 'org.mockito:mockito-core:4.6.0'
|
||||
testImplementation 'org.assertj:assertj-core:3.22.0'
|
||||
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
|
||||
@@ -50,6 +49,11 @@ allprojects {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
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'
|
||||
eclipse().configFile 'config/code-formatter/eclipse.xml'
|
||||
if (JavaVersion.current() < JavaVersion.VERSION_16) {
|
||||
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)')
|
||||
}
|
||||
removeUnusedImports()
|
||||
|
||||
lineEndings(com.diffplug.spotless.LineEnding.UNIX)
|
||||
encoding("UTF-8")
|
||||
|
||||
@@ -1,2 +1,11 @@
|
||||
org.gradle.warning.mode=all
|
||||
org.gradle.parallel=true
|
||||
|
||||
# Flags for google-java-format (optimize imports by spotless) for Java >= 16.
|
||||
# Java < 9 will ignore unsupported flags (thanks to -XX:+IgnoreUnrecognizedVMOptions)
|
||||
org.gradle.jvmargs=-XX:+IgnoreUnrecognizedVMOptions \
|
||||
--add-exports='jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED' \
|
||||
--add-exports='jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED' \
|
||||
--add-exports='jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED' \
|
||||
--add-exports='jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED' \
|
||||
--add-exports='jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED'
|
||||
|
||||
@@ -8,6 +8,7 @@ import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -22,6 +23,7 @@ import jadx.api.plugins.JadxPluginInfo;
|
||||
import jadx.api.plugins.JadxPluginManager;
|
||||
import jadx.api.plugins.options.JadxPluginOptions;
|
||||
import jadx.api.plugins.options.OptionDescription;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
public class JCommanderWrapper<T> {
|
||||
private final JCommander jc;
|
||||
@@ -50,12 +52,24 @@ public class JCommanderWrapper<T> {
|
||||
if (parameter.isAssigned()) {
|
||||
// copy assigned field value to obj
|
||||
Parameterized parameterized = parameter.getParameterized();
|
||||
Object val = parameterized.get(parameter.getObject());
|
||||
parameterized.set(obj, val);
|
||||
Object providedValue = parameterized.get(parameter.getObject());
|
||||
Object newValue = mergeValues(parameterized.getType(), providedValue, () -> parameterized.get(obj));
|
||||
parameterized.set(obj, newValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
private static Object mergeValues(Class<?> type, Object value, Supplier<Object> prevValueProvider) {
|
||||
if (type.isAssignableFrom(Map.class)) {
|
||||
// merge maps instead replacing whole map
|
||||
Map prevMap = (Map) prevValueProvider.get();
|
||||
return Utils.mergeMaps(prevMap, (Map) value); // value map will override keys in prevMap
|
||||
}
|
||||
// simple override
|
||||
return value;
|
||||
}
|
||||
|
||||
public void printUsage() {
|
||||
// print usage in not sorted fields order (by default its sorted by description)
|
||||
PrintStream out = System.out;
|
||||
@@ -179,11 +193,11 @@ public class JCommanderWrapper<T> {
|
||||
return false;
|
||||
}
|
||||
JadxPluginInfo pluginInfo = plugin.getPluginInfo();
|
||||
out.append("\n ").append(k).append(") ");
|
||||
out.append(pluginInfo.getPluginId()).append(" (").append(pluginInfo.getDescription()).append(") ");
|
||||
out.append("\n ").append(k).append(") ");
|
||||
out.append(pluginInfo.getPluginId()).append(": ").append(pluginInfo.getDescription());
|
||||
for (OptionDescription desc : descs) {
|
||||
StringBuilder opt = new StringBuilder();
|
||||
opt.append(" -P").append(desc.name());
|
||||
opt.append(" - ").append(desc.name());
|
||||
addSpaces(opt, maxNamesLen - opt.length());
|
||||
opt.append("- ").append(desc.description());
|
||||
if (!desc.values().isEmpty()) {
|
||||
|
||||
@@ -21,7 +21,7 @@ public class JadxCLI {
|
||||
} catch (JadxArgsValidateException e) {
|
||||
LOG.error("Incorrect arguments: {}", e.getMessage());
|
||||
result = 1;
|
||||
} catch (Exception e) {
|
||||
} catch (Throwable e) {
|
||||
LOG.error("Process error:", e);
|
||||
result = 1;
|
||||
} finally {
|
||||
@@ -66,8 +66,14 @@ public class JadxCLI {
|
||||
|
||||
private static boolean checkForErrors(JadxDecompiler jadx) {
|
||||
if (jadx.getRoot().getClasses().isEmpty()) {
|
||||
LOG.error("Load failed! No classes for decompile!");
|
||||
return true;
|
||||
if (jadx.getArgs().isSkipResources()) {
|
||||
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) {
|
||||
LOG.error("Load with errors! Check log for details");
|
||||
|
||||
@@ -123,9 +123,6 @@ public class JadxCLIArgs {
|
||||
)
|
||||
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")
|
||||
protected boolean deobfuscationUseSourceNameAsAlias = false;
|
||||
|
||||
@@ -259,11 +256,7 @@ public class JadxCLIArgs {
|
||||
args.setReplaceConsts(replaceConsts);
|
||||
args.setDeobfuscationOn(deobfuscationOn);
|
||||
args.setDeobfuscationMapFile(FileUtils.toFile(deobfuscationMapFile));
|
||||
if (deobfuscationForceSave) {
|
||||
args.setDeobfuscationMapFileMode(DeobfuscationMapFileMode.OVERWRITE);
|
||||
} else {
|
||||
args.setDeobfuscationMapFileMode(deobfuscationMapFileMode);
|
||||
}
|
||||
args.setDeobfuscationMapFileMode(deobfuscationMapFileMode);
|
||||
args.setDeobfuscationMinLength(deobfuscationMinLength);
|
||||
args.setDeobfuscationMaxLength(deobfuscationMaxLength);
|
||||
args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias);
|
||||
@@ -377,10 +370,6 @@ public class JadxCLIArgs {
|
||||
return deobfuscationMapFileMode;
|
||||
}
|
||||
|
||||
public boolean isDeobfuscationForceSave() {
|
||||
return deobfuscationForceSave;
|
||||
}
|
||||
|
||||
public boolean isDeobfuscationUseSourceNameAsAlias() {
|
||||
return deobfuscationUseSourceNameAsAlias;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
package jadx.cli;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static jadx.core.utils.Utils.newConstStringMap;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
@@ -47,6 +52,40 @@ public class JadxCLIArgsTest {
|
||||
assertThat(override(args, "").isUseImports(), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPluginOptionsOverride() {
|
||||
// add key to empty base map
|
||||
checkPluginOptionsMerge(
|
||||
Collections.emptyMap(),
|
||||
"-Poption=otherValue",
|
||||
newConstStringMap("option", "otherValue"));
|
||||
|
||||
// override one key
|
||||
checkPluginOptionsMerge(
|
||||
newConstStringMap("option", "value"),
|
||||
"-Poption=otherValue",
|
||||
newConstStringMap("option", "otherValue"));
|
||||
|
||||
// merge different keys
|
||||
checkPluginOptionsMerge(
|
||||
Collections.singletonMap("option1", "value1"),
|
||||
"-Poption2=otherValue2",
|
||||
newConstStringMap("option1", "value1", "option2", "otherValue2"));
|
||||
|
||||
// merge and override
|
||||
checkPluginOptionsMerge(
|
||||
newConstStringMap("option1", "value1", "option2", "value2"),
|
||||
"-Poption2=otherValue2",
|
||||
newConstStringMap("option1", "value1", "option2", "otherValue2"));
|
||||
}
|
||||
|
||||
private void checkPluginOptionsMerge(Map<String, String> baseMap, String providedArgs, Map<String, String> expectedMap) {
|
||||
JadxCLIArgs args = new JadxCLIArgs();
|
||||
args.pluginOptions = baseMap;
|
||||
Map<String, String> resultMap = override(args, providedArgs).getPluginOptions();
|
||||
assertThat(resultMap, Matchers.equalTo(expectedMap));
|
||||
}
|
||||
|
||||
private JadxCLIArgs parse(String... args) {
|
||||
return parse(new JadxCLIArgs(), args);
|
||||
}
|
||||
|
||||
@@ -44,6 +44,33 @@ public class TestInput {
|
||||
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 {
|
||||
List<String> args = new ArrayList<>();
|
||||
Path tempDir = FileUtils.createTempDir(tmpDirName);
|
||||
|
||||
Binary file not shown.
@@ -14,7 +14,7 @@ dependencies {
|
||||
|
||||
testImplementation 'org.apache.commons:commons-lang3:3.12.0'
|
||||
|
||||
testRuntimeOnly(project(':jadx-plugins:jadx-dex-input'))
|
||||
testImplementation(project(':jadx-plugins:jadx-dex-input'))
|
||||
testRuntimeOnly(project(':jadx-plugins:jadx-smali-input'))
|
||||
testRuntimeOnly(project(':jadx-plugins:jadx-java-convert'))
|
||||
testRuntimeOnly(project(':jadx-plugins:jadx-java-input'))
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
package jadx.api;
|
||||
|
||||
public final class CodePosition {
|
||||
|
||||
private final int line;
|
||||
private final int offset;
|
||||
private final int pos;
|
||||
|
||||
public CodePosition(int line, int offset, int pos) {
|
||||
this.line = line;
|
||||
this.offset = offset;
|
||||
this.pos = pos;
|
||||
}
|
||||
|
||||
public CodePosition(int line) {
|
||||
this(line, 0, -1);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public CodePosition(int line, int offset) {
|
||||
this(line, offset, -1);
|
||||
}
|
||||
|
||||
public int getPos() {
|
||||
return pos;
|
||||
}
|
||||
|
||||
public int getLine() {
|
||||
return line;
|
||||
}
|
||||
|
||||
public int getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
CodePosition that = (CodePosition) o;
|
||||
return line == that.line && offset == that.offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return line + 31 * offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(line);
|
||||
if (offset != 0) {
|
||||
sb.append(':').append(offset);
|
||||
}
|
||||
if (pos > 0) {
|
||||
sb.append('@').append(pos);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,21 @@
|
||||
package jadx.api;
|
||||
|
||||
import java.io.Closeable;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public interface ICodeCache {
|
||||
public interface ICodeCache extends Closeable {
|
||||
|
||||
void add(String clsFullName, ICodeInfo codeInfo);
|
||||
|
||||
void remove(String clsFullName);
|
||||
|
||||
@Nullable
|
||||
@NotNull
|
||||
ICodeInfo get(String clsFullName);
|
||||
|
||||
@Nullable
|
||||
String getCode(String clsFullName);
|
||||
|
||||
boolean contains(String clsFullName);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package jadx.api;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import jadx.api.impl.SimpleCodeInfo;
|
||||
import jadx.api.metadata.ICodeMetadata;
|
||||
|
||||
public interface ICodeInfo {
|
||||
|
||||
@@ -10,7 +9,7 @@ public interface ICodeInfo {
|
||||
|
||||
String getCodeStr();
|
||||
|
||||
Map<Integer, Integer> getLineMapping();
|
||||
ICodeMetadata getCodeMetadata();
|
||||
|
||||
Map<CodePosition, Object> getAnnotations();
|
||||
boolean hasMetadata();
|
||||
}
|
||||
|
||||
@@ -2,7 +2,10 @@ package jadx.api;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import jadx.core.dex.attributes.ILineAttributeNode;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import jadx.api.metadata.ICodeAnnotation;
|
||||
import jadx.api.metadata.ICodeNodeRef;
|
||||
|
||||
public interface ICodeWriter {
|
||||
String NL = System.getProperty("line.separator");
|
||||
@@ -38,13 +41,21 @@ public interface ICodeWriter {
|
||||
|
||||
void setIndent(int indent);
|
||||
|
||||
/**
|
||||
* Return current line (only if metadata is supported)
|
||||
*/
|
||||
int getLine();
|
||||
|
||||
void attachDefinition(ILineAttributeNode obj);
|
||||
/**
|
||||
* Return start line position (only if metadata is supported)
|
||||
*/
|
||||
int getLineStartPos();
|
||||
|
||||
void attachAnnotation(Object obj);
|
||||
void attachDefinition(ICodeNodeRef obj);
|
||||
|
||||
void attachLineAnnotation(Object obj);
|
||||
void attachAnnotation(ICodeAnnotation obj);
|
||||
|
||||
void attachLineAnnotation(ICodeAnnotation obj);
|
||||
|
||||
void attachSourceLine(int sourceLine);
|
||||
|
||||
@@ -56,5 +67,6 @@ public interface ICodeWriter {
|
||||
|
||||
StringBuilder getRawBuf();
|
||||
|
||||
Map<CodePosition, Object> getRawAnnotations();
|
||||
@ApiStatus.Internal
|
||||
Map<Integer, ICodeAnnotation> getRawAnnotations();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package jadx.api;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
@@ -11,12 +12,17 @@ import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.args.DeobfuscationMapFileMode;
|
||||
import jadx.api.data.ICodeData;
|
||||
import jadx.api.impl.AnnotatedCodeWriter;
|
||||
import jadx.api.impl.InMemoryCodeCache;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
|
||||
public class JadxArgs {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JadxArgs.class);
|
||||
|
||||
public static final int DEFAULT_THREADS_COUNT = Math.max(1, Runtime.getRuntime().availableProcessors() / 2);
|
||||
|
||||
@@ -55,6 +61,11 @@ public class JadxArgs {
|
||||
*/
|
||||
private Predicate<String> classFilter = null;
|
||||
|
||||
/**
|
||||
* Save dependencies for classes accepted by {@code classFilter}
|
||||
*/
|
||||
private boolean includeDependencies = false;
|
||||
|
||||
private boolean deobfuscationOn = false;
|
||||
private boolean useSourceNameAsClassAlias = false;
|
||||
private boolean parseKotlinMetadata = false;
|
||||
@@ -115,6 +126,19 @@ public class JadxArgs {
|
||||
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() {
|
||||
return inputFiles;
|
||||
}
|
||||
@@ -261,6 +285,14 @@ public class JadxArgs {
|
||||
this.skipSources = skipSources;
|
||||
}
|
||||
|
||||
public void setIncludeDependencies(boolean includeDependencies) {
|
||||
this.includeDependencies = includeDependencies;
|
||||
}
|
||||
|
||||
public boolean isIncludeDependencies() {
|
||||
return includeDependencies;
|
||||
}
|
||||
|
||||
public Predicate<String> getClassFilter() {
|
||||
return classFilter;
|
||||
}
|
||||
@@ -501,6 +533,21 @@ public class JadxArgs {
|
||||
this.pluginOptions = pluginOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash of all options that can change result code
|
||||
*/
|
||||
public String makeCodeArgsHash() {
|
||||
String argStr = "args:" + decompilationMode + useImports + showInconsistentCode
|
||||
+ inlineAnonymousClasses + inlineMethods
|
||||
+ deobfuscationOn + deobfuscationMinLength + deobfuscationMaxLength
|
||||
+ parseKotlinMetadata + useKotlinMethodsForVarNames
|
||||
+ insertDebugLines + extractFinally
|
||||
+ debugInfo + useSourceNameAsClassAlias + escapeUnicode + replaceConsts
|
||||
+ respectBytecodeAccModifiers + fsCaseSensitive + renameFlags
|
||||
+ commentsLevel + useDxInput + pluginOptions;
|
||||
return FileUtils.md5Sum(argStr.getBytes(StandardCharsets.US_ASCII));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "JadxArgs{" + "inputFiles=" + inputFiles
|
||||
@@ -513,12 +560,15 @@ public class JadxArgs {
|
||||
+ ", useImports=" + useImports
|
||||
+ ", skipResources=" + skipResources
|
||||
+ ", skipSources=" + skipSources
|
||||
+ ", includeDependencies=" + includeDependencies
|
||||
+ ", deobfuscationOn=" + deobfuscationOn
|
||||
+ ", deobfuscationMapFile=" + deobfuscationMapFile
|
||||
+ ", deobfuscationMapFileMode=" + deobfuscationMapFileMode
|
||||
+ ", useSourceNameAsClassAlias=" + useSourceNameAsClassAlias
|
||||
+ ", parseKotlinMetadata=" + parseKotlinMetadata
|
||||
+ ", useKotlinMethodsForVarNames=" + useKotlinMethodsForVarNames
|
||||
+ ", insertDebugLines=" + insertDebugLines
|
||||
+ ", extractFinally=" + extractFinally
|
||||
+ ", deobfuscationMinLength=" + deobfuscationMinLength
|
||||
+ ", deobfuscationMaxLength=" + deobfuscationMaxLength
|
||||
+ ", escapeUnicode=" + escapeUnicode
|
||||
|
||||
@@ -13,8 +13,9 @@ public class JadxArgsValidator {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JadxArgsValidator.class);
|
||||
|
||||
public static void validate(JadxArgs args) {
|
||||
checkInputFiles(args);
|
||||
public static void validate(JadxDecompiler jadx) {
|
||||
JadxArgs args = jadx.getArgs();
|
||||
checkInputFiles(jadx, args);
|
||||
validateOutDirs(args);
|
||||
|
||||
if (LOG.isDebugEnabled()) {
|
||||
@@ -22,9 +23,9 @@ public class JadxArgsValidator {
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkInputFiles(JadxArgs args) {
|
||||
private static void checkInputFiles(JadxDecompiler jadx, JadxArgs args) {
|
||||
List<File> inputFiles = args.getInputFiles();
|
||||
if (inputFiles.isEmpty()) {
|
||||
if (inputFiles.isEmpty() && jadx.getCustomLoads().isEmpty()) {
|
||||
throw new JadxArgsValidateException("Please specify input file");
|
||||
}
|
||||
for (File inputFile : inputFiles) {
|
||||
@@ -66,19 +67,22 @@ public class JadxArgsValidator {
|
||||
|
||||
@NotNull
|
||||
private static File makeDirFromInput(JadxArgs args) {
|
||||
File outDir;
|
||||
String outDirName;
|
||||
File file = args.getInputFiles().get(0);
|
||||
String name = file.getName();
|
||||
int pos = name.lastIndexOf('.');
|
||||
if (pos != -1) {
|
||||
outDirName = name.substring(0, pos);
|
||||
List<File> inputFiles = args.getInputFiles();
|
||||
if (inputFiles.isEmpty()) {
|
||||
outDirName = JadxArgs.DEFAULT_OUT_DIR;
|
||||
} else {
|
||||
outDirName = name + '-' + JadxArgs.DEFAULT_OUT_DIR;
|
||||
File file = inputFiles.get(0);
|
||||
String name = file.getName();
|
||||
int pos = name.lastIndexOf('.');
|
||||
if (pos != -1) {
|
||||
outDirName = name.substring(0, pos);
|
||||
} else {
|
||||
outDirName = name + '-' + JadxArgs.DEFAULT_OUT_DIR;
|
||||
}
|
||||
}
|
||||
LOG.info("output directory: {}", outDirName);
|
||||
outDir = new File(outDirName);
|
||||
return outDir;
|
||||
return new File(outDirName);
|
||||
}
|
||||
|
||||
private static void checkFile(File file) {
|
||||
|
||||
@@ -25,7 +25,11 @@ import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.data.annotations.VarRef;
|
||||
import jadx.api.metadata.ICodeAnnotation;
|
||||
import jadx.api.metadata.ICodeNodeRef;
|
||||
import jadx.api.metadata.annotations.NodeDeclareRef;
|
||||
import jadx.api.metadata.annotations.VarNode;
|
||||
import jadx.api.metadata.annotations.VarRef;
|
||||
import jadx.api.plugins.JadxPlugin;
|
||||
import jadx.api.plugins.JadxPluginManager;
|
||||
import jadx.api.plugins.input.JadxInputPlugin;
|
||||
@@ -35,7 +39,6 @@ import jadx.core.Jadx;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.InlinedAttr;
|
||||
import jadx.core.dex.attributes.nodes.LineAttrNode;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
@@ -96,7 +99,9 @@ public final class JadxDecompiler implements Closeable {
|
||||
private final Map<MethodNode, JavaMethod> methodsMap = new ConcurrentHashMap<>();
|
||||
private final Map<FieldNode, JavaField> fieldsMap = new ConcurrentHashMap<>();
|
||||
|
||||
private final IDecompileScheduler decompileScheduler = new DecompilerScheduler(this);
|
||||
private final IDecompileScheduler decompileScheduler = new DecompilerScheduler();
|
||||
|
||||
private final List<ILoadResult> customLoads = new ArrayList<>();
|
||||
|
||||
public JadxDecompiler() {
|
||||
this(new JadxArgs());
|
||||
@@ -108,7 +113,7 @@ public final class JadxDecompiler implements Closeable {
|
||||
|
||||
public void load() {
|
||||
reset();
|
||||
JadxArgsValidator.validate(args);
|
||||
JadxArgsValidator.validate(this);
|
||||
LOG.info("loading ...");
|
||||
loadPlugins(args);
|
||||
loadInputFiles();
|
||||
@@ -132,11 +137,20 @@ public final class JadxDecompiler implements Closeable {
|
||||
loadedInputs.add(loadResult);
|
||||
}
|
||||
}
|
||||
loadedInputs.addAll(customLoads);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Loaded using {} inputs plugin in {} ms", loadedInputs.size(), System.currentTimeMillis() - start);
|
||||
}
|
||||
}
|
||||
|
||||
public void addCustomLoad(ILoadResult customLoad) {
|
||||
customLoads.add(customLoad);
|
||||
}
|
||||
|
||||
public List<ILoadResult> getCustomLoads() {
|
||||
return customLoads;
|
||||
}
|
||||
|
||||
private void reset() {
|
||||
root = null;
|
||||
classes = null;
|
||||
@@ -147,8 +161,13 @@ public final class JadxDecompiler implements Closeable {
|
||||
classesMap.clear();
|
||||
methodsMap.clear();
|
||||
fieldsMap.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
reset();
|
||||
closeInputs();
|
||||
args.close();
|
||||
}
|
||||
|
||||
private void closeInputs() {
|
||||
@@ -162,11 +181,6 @@ public final class JadxDecompiler implements Closeable {
|
||||
loadedInputs.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
reset();
|
||||
}
|
||||
|
||||
private void loadPlugins(JadxArgs args) {
|
||||
pluginManager.providesSuggestion("java-input", args.isUseDxInput() ? "java-convert" : "java-input");
|
||||
pluginManager.load();
|
||||
@@ -188,6 +202,7 @@ public final class JadxDecompiler implements Closeable {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public void registerPlugin(JadxPlugin plugin) {
|
||||
pluginManager.register(plugin);
|
||||
}
|
||||
@@ -229,6 +244,7 @@ public final class JadxDecompiler implements Closeable {
|
||||
save(false, true);
|
||||
}
|
||||
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
private void save(boolean saveSources, boolean saveResources) {
|
||||
ExecutorService ex = getSaveExecutor(saveSources, saveResources);
|
||||
ex.shutdown();
|
||||
@@ -320,10 +336,14 @@ public final class JadxDecompiler implements Closeable {
|
||||
List<JavaClass> classes = getClasses();
|
||||
List<JavaClass> processQueue = new ArrayList<>(classes.size());
|
||||
for (JavaClass cls : classes) {
|
||||
if (cls.getClassNode().contains(AFlag.DONT_GENERATE)) {
|
||||
ClassNode clsNode = cls.getClassNode();
|
||||
if (clsNode.contains(AFlag.DONT_GENERATE)) {
|
||||
continue;
|
||||
}
|
||||
if (classFilter != null && !classFilter.test(cls.getFullName())) {
|
||||
if (classFilter != null && !classFilter.test(clsNode.getClassInfo().getFullName())) {
|
||||
if (!args.isIncludeDependencies()) {
|
||||
clsNode.add(AFlag.DONT_GENERATE);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
processQueue.add(cls);
|
||||
@@ -353,14 +373,14 @@ public final class JadxDecompiler implements Closeable {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
if (classes == null) {
|
||||
List<ClassNode> classNodeList = root.getClasses(false);
|
||||
List<ClassNode> classNodeList = root.getClasses();
|
||||
List<JavaClass> clsList = new ArrayList<>(classNodeList.size());
|
||||
classesMap.clear();
|
||||
for (ClassNode classNode : classNodeList) {
|
||||
if (!classNode.contains(AFlag.DONT_GENERATE)) {
|
||||
JavaClass javaClass = new JavaClass(classNode, this);
|
||||
clsList.add(javaClass);
|
||||
classesMap.put(classNode, javaClass);
|
||||
if (classNode.contains(AFlag.DONT_GENERATE)) {
|
||||
continue;
|
||||
}
|
||||
if (!classNode.getClassInfo().isInner()) {
|
||||
clsList.add(convertClassNode(classNode));
|
||||
}
|
||||
}
|
||||
classes = Collections.unmodifiableList(clsList);
|
||||
@@ -368,6 +388,10 @@ public final class JadxDecompiler implements Closeable {
|
||||
return classes;
|
||||
}
|
||||
|
||||
public List<JavaClass> getClassesWithInners() {
|
||||
return Utils.collectionMap(root.getClasses(), this::convertClassNode);
|
||||
}
|
||||
|
||||
public List<ResourceFile> getResources() {
|
||||
if (resources == null) {
|
||||
if (root == null) {
|
||||
@@ -425,6 +449,7 @@ public final class JadxDecompiler implements Closeable {
|
||||
/**
|
||||
* Internal API. Not Stable!
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
public RootNode getRoot() {
|
||||
return root;
|
||||
}
|
||||
@@ -443,23 +468,10 @@ public final class JadxDecompiler implements Closeable {
|
||||
return protoXmlParser;
|
||||
}
|
||||
|
||||
private void loadJavaClass(JavaClass javaClass) {
|
||||
javaClass.getMethods().forEach(mth -> methodsMap.put(mth.getMethodNode(), mth));
|
||||
javaClass.getFields().forEach(fld -> fieldsMap.put(fld.getFieldNode(), fld));
|
||||
|
||||
for (JavaClass innerCls : javaClass.getInnerClasses()) {
|
||||
classesMap.put(innerCls.getClassNode(), innerCls);
|
||||
loadJavaClass(innerCls);
|
||||
}
|
||||
for (JavaClass inlinedCls : javaClass.getInlinedClasses()) {
|
||||
classesMap.put(inlinedCls.getClassNode(), inlinedCls);
|
||||
loadJavaClass(inlinedCls);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get JavaClass by ClassNode without loading and decompilation
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
JavaClass convertClassNode(ClassNode cls) {
|
||||
return classesMap.compute(cls, (node, prevJavaCls) -> {
|
||||
if (prevJavaCls != null && prevJavaCls.getClassNode() == cls) {
|
||||
@@ -473,65 +485,23 @@ public final class JadxDecompiler implements Closeable {
|
||||
});
|
||||
}
|
||||
|
||||
@Nullable("For not generated classes")
|
||||
@ApiStatus.Internal
|
||||
public JavaClass getJavaClassByNode(ClassNode cls) {
|
||||
JavaClass javaClass = classesMap.get(cls);
|
||||
if (javaClass != null && javaClass.getClassNode() == cls) {
|
||||
return javaClass;
|
||||
}
|
||||
// load parent class if inner
|
||||
ClassNode parentClass = cls.getTopParentClass();
|
||||
if (parentClass.contains(AFlag.DONT_GENERATE)) {
|
||||
return null;
|
||||
}
|
||||
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);
|
||||
JavaField convertFieldNode(FieldNode field) {
|
||||
return fieldsMap.computeIfAbsent(field, fldNode -> {
|
||||
JavaClass parentCls = convertClassNode(fldNode.getParentClass());
|
||||
return new JavaField(parentCls, fldNode);
|
||||
});
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private JavaMethod getJavaMethodByNode(MethodNode mth) {
|
||||
JavaMethod javaMethod = methodsMap.get(mth);
|
||||
if (javaMethod != null && javaMethod.getMethodNode() == mth) {
|
||||
return javaMethod;
|
||||
}
|
||||
if (mth.contains(AFlag.DONT_GENERATE)) {
|
||||
return null;
|
||||
}
|
||||
// parent class not loaded yet
|
||||
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);
|
||||
@ApiStatus.Internal
|
||||
JavaMethod convertMethodNode(MethodNode method) {
|
||||
return methodsMap.computeIfAbsent(method, mthNode -> {
|
||||
ClassNode codeCls = getCodeParentClass(mthNode.getParentClass());
|
||||
return new JavaMethod(convertClassNode(codeCls), mthNode);
|
||||
});
|
||||
}
|
||||
|
||||
private ClassNode getCodeParentClass(ClassNode cls) {
|
||||
private static ClassNode getCodeParentClass(ClassNode cls) {
|
||||
ClassNode codeCls;
|
||||
InlinedAttr inlinedAttr = cls.get(AType.INLINED);
|
||||
if (inlinedAttr != null) {
|
||||
@@ -545,34 +515,12 @@ public final class JadxDecompiler implements Closeable {
|
||||
return getCodeParentClass(codeCls);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private 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
|
||||
public JavaClass searchJavaClassByOrigFullName(String fullName) {
|
||||
return getRoot().getClasses().stream()
|
||||
.filter(cls -> cls.getClassInfo().getFullName().equals(fullName))
|
||||
.findFirst()
|
||||
.map(this::getJavaClassByNode)
|
||||
.map(this::convertClassNode)
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
@@ -593,9 +541,9 @@ public final class JadxDecompiler implements Closeable {
|
||||
.orElse(null);
|
||||
if (node != null) {
|
||||
if (node.contains(AFlag.DONT_GENERATE)) {
|
||||
return getJavaClassByNode(node.getTopParentClass());
|
||||
return convertClassNode(node.getTopParentClass());
|
||||
} else {
|
||||
return getJavaClassByNode(node);
|
||||
return convertClassNode(node);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@@ -606,86 +554,92 @@ public final class JadxDecompiler implements Closeable {
|
||||
return getRoot().getClasses().stream()
|
||||
.filter(cls -> cls.getClassInfo().getAliasFullName().equals(fullName))
|
||||
.findFirst()
|
||||
.map(this::getJavaClassByNode)
|
||||
.map(this::convertClassNode)
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
JavaNode convertNode(Object obj) {
|
||||
if (obj instanceof VarRef) {
|
||||
VarRef varRef = (VarRef) obj;
|
||||
MethodNode mthNode = varRef.getMth();
|
||||
JavaMethod mth = getJavaMethodByNode(mthNode);
|
||||
if (mth == null) {
|
||||
public JavaNode getJavaNodeByRef(ICodeNodeRef ann) {
|
||||
return getJavaNodeByCodeAnnotation(null, ann);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public JavaNode getJavaNodeByCodeAnnotation(@Nullable ICodeInfo codeInfo, @Nullable ICodeAnnotation ann) {
|
||||
if (ann == null) {
|
||||
return null;
|
||||
}
|
||||
switch (ann.getAnnType()) {
|
||||
case CLASS:
|
||||
return convertClassNode((ClassNode) ann);
|
||||
case METHOD:
|
||||
return convertMethodNode((MethodNode) ann);
|
||||
case FIELD:
|
||||
return convertFieldNode((FieldNode) ann);
|
||||
case DECLARATION:
|
||||
return getJavaNodeByCodeAnnotation(codeInfo, ((NodeDeclareRef) ann).getNode());
|
||||
case VAR:
|
||||
return resolveVarNode((VarNode) ann);
|
||||
case VAR_REF:
|
||||
return resolveVarRef(codeInfo, (VarRef) ann);
|
||||
case OFFSET:
|
||||
// offset annotation don't have java node object
|
||||
return null;
|
||||
default:
|
||||
throw new JadxRuntimeException("Unknown annotation type: " + ann.getAnnType() + ", class: " + ann.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private JavaVariable resolveVarNode(VarNode varNode) {
|
||||
MethodNode mthNode = varNode.getMth();
|
||||
JavaMethod mth = convertMethodNode(mthNode);
|
||||
if (mth == null) {
|
||||
return null;
|
||||
}
|
||||
return new JavaVariable(mth, varNode);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private JavaVariable resolveVarRef(ICodeInfo codeInfo, VarRef varRef) {
|
||||
if (codeInfo == null) {
|
||||
throw new JadxRuntimeException("Missing code info for resolve VarRef: " + varRef);
|
||||
}
|
||||
ICodeAnnotation varNodeAnn = codeInfo.getCodeMetadata().getAt(varRef.getRefPos());
|
||||
if (varNodeAnn != null && varNodeAnn.getAnnType() == ICodeAnnotation.AnnType.DECLARATION) {
|
||||
ICodeNodeRef nodeRef = ((NodeDeclareRef) varNodeAnn).getNode();
|
||||
if (nodeRef.getAnnType() == ICodeAnnotation.AnnType.VAR) {
|
||||
return resolveVarNode((VarNode) nodeRef);
|
||||
}
|
||||
return new JavaVariable(mth, varRef);
|
||||
}
|
||||
if (!(obj instanceof LineAttrNode)) {
|
||||
return null;
|
||||
}
|
||||
LineAttrNode node = (LineAttrNode) obj;
|
||||
if (node.contains(AFlag.DONT_GENERATE)) {
|
||||
return null;
|
||||
}
|
||||
if (obj instanceof ClassNode) {
|
||||
return convertClassNode((ClassNode) obj);
|
||||
}
|
||||
if (obj instanceof MethodNode) {
|
||||
return getJavaMethodByNode(((MethodNode) obj));
|
||||
}
|
||||
if (obj instanceof FieldNode) {
|
||||
return getJavaFieldByNode((FieldNode) obj);
|
||||
}
|
||||
throw new JadxRuntimeException("Unexpected node type: " + obj);
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO: make interface for all nodes in code annotations and add common method instead this
|
||||
Object getInternalNode(JavaNode javaNode) {
|
||||
if (javaNode instanceof JavaClass) {
|
||||
return ((JavaClass) javaNode).getClassNode();
|
||||
}
|
||||
if (javaNode instanceof JavaMethod) {
|
||||
return ((JavaMethod) javaNode).getMethodNode();
|
||||
}
|
||||
if (javaNode instanceof JavaField) {
|
||||
return ((JavaField) javaNode).getFieldNode();
|
||||
}
|
||||
if (javaNode instanceof JavaVariable) {
|
||||
return ((JavaVariable) javaNode).getVarRef();
|
||||
}
|
||||
throw new JadxRuntimeException("Unexpected node type: " + javaNode);
|
||||
}
|
||||
|
||||
List<JavaNode> convertNodes(Collection<?> nodesList) {
|
||||
List<JavaNode> convertNodes(Collection<? extends ICodeNodeRef> nodesList) {
|
||||
return nodesList.stream()
|
||||
.map(this::convertNode)
|
||||
.map(this::getJavaNodeByRef)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public JavaNode getJavaNodeAtPosition(ICodeInfo codeInfo, int line, int offset) {
|
||||
Map<CodePosition, Object> map = codeInfo.getAnnotations();
|
||||
if (map.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
Object obj = map.get(new CodePosition(line, offset));
|
||||
if (obj == null) {
|
||||
return null;
|
||||
}
|
||||
return convertNode(obj);
|
||||
public JavaNode getJavaNodeAtPosition(ICodeInfo codeInfo, int pos) {
|
||||
ICodeAnnotation ann = codeInfo.getCodeMetadata().getAt(pos);
|
||||
return getJavaNodeByCodeAnnotation(codeInfo, ann);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public CodePosition getDefinitionPosition(JavaNode javaNode) {
|
||||
JavaClass jCls = javaNode.getTopParentClass();
|
||||
jCls.decompile();
|
||||
int defLine = javaNode.getDecompiledLine();
|
||||
if (defLine == 0) {
|
||||
public JavaNode getClosestJavaNode(ICodeInfo codeInfo, int pos) {
|
||||
ICodeAnnotation ann = codeInfo.getCodeMetadata().getClosestUp(pos);
|
||||
return getJavaNodeByCodeAnnotation(codeInfo, ann);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public JavaNode getEnclosingNode(ICodeInfo codeInfo, int pos) {
|
||||
ICodeNodeRef obj = codeInfo.getCodeMetadata().getNodeAt(pos);
|
||||
if (obj == null) {
|
||||
return null;
|
||||
}
|
||||
return new CodePosition(defLine, 0, javaNode.getDefPos());
|
||||
return getJavaNodeByRef(obj);
|
||||
}
|
||||
|
||||
public void reloadCodeData() {
|
||||
|
||||
@@ -8,8 +8,13 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.metadata.ICodeAnnotation;
|
||||
import jadx.api.metadata.ICodeNodeRef;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.AnonymousClassAttr;
|
||||
@@ -17,8 +22,10 @@ import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.utils.ListUtils;
|
||||
|
||||
public final class JavaClass implements JavaNode {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JavaClass.class);
|
||||
|
||||
private final JadxDecompiler decompiler;
|
||||
private final ClassNode cls;
|
||||
@@ -46,24 +53,21 @@ public final class JavaClass implements JavaNode {
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
ICodeInfo code = getCodeInfo();
|
||||
if (code == null) {
|
||||
return "";
|
||||
}
|
||||
return code.getCodeStr();
|
||||
return getCodeInfo().getCodeStr();
|
||||
}
|
||||
|
||||
public ICodeInfo getCodeInfo() {
|
||||
public @NotNull ICodeInfo getCodeInfo() {
|
||||
load();
|
||||
return cls.decompile();
|
||||
}
|
||||
|
||||
public void decompile() {
|
||||
cls.decompile();
|
||||
load();
|
||||
}
|
||||
|
||||
public synchronized void reload() {
|
||||
public synchronized ICodeInfo reload() {
|
||||
listsLoaded = false;
|
||||
cls.reloadCode();
|
||||
return cls.reloadCode();
|
||||
}
|
||||
|
||||
public void unload() {
|
||||
@@ -75,10 +79,22 @@ public final class JavaClass implements JavaNode {
|
||||
return cls.contains(AFlag.DONT_GENERATE);
|
||||
}
|
||||
|
||||
public boolean isInner() {
|
||||
return cls.isInner();
|
||||
}
|
||||
|
||||
public synchronized String getSmali() {
|
||||
return cls.getDisassembledCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOwnCodeAnnotation(ICodeAnnotation ann) {
|
||||
if (ann.getAnnType() == ICodeAnnotation.AnnType.CLASS) {
|
||||
return ann.equals(cls);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal API. Not Stable!
|
||||
*/
|
||||
@@ -87,13 +103,21 @@ public final class JavaClass implements JavaNode {
|
||||
return cls;
|
||||
}
|
||||
|
||||
private synchronized void loadLists() {
|
||||
/**
|
||||
* Decompile class and loads internal lists of fields, methods, etc.
|
||||
* Do nothing if already loaded.
|
||||
*/
|
||||
@Nullable
|
||||
private synchronized void load() {
|
||||
if (listsLoaded) {
|
||||
return;
|
||||
}
|
||||
listsLoaded = true;
|
||||
decompile();
|
||||
JadxDecompiler rootDecompiler = getRootDecompiler();
|
||||
ICodeCache codeCache = rootDecompiler.getArgs().getCodeCache();
|
||||
if (!codeCache.contains(cls.getRawName())) {
|
||||
cls.decompile();
|
||||
}
|
||||
|
||||
int inClsCount = cls.getInnerClasses().size();
|
||||
if (inClsCount != 0) {
|
||||
@@ -101,7 +125,7 @@ public final class JavaClass implements JavaNode {
|
||||
for (ClassNode inner : cls.getInnerClasses()) {
|
||||
if (!inner.contains(AFlag.DONT_GENERATE)) {
|
||||
JavaClass javaClass = rootDecompiler.convertClassNode(inner);
|
||||
javaClass.loadLists();
|
||||
javaClass.load();
|
||||
list.add(javaClass);
|
||||
}
|
||||
}
|
||||
@@ -112,7 +136,7 @@ public final class JavaClass implements JavaNode {
|
||||
List<JavaClass> list = new ArrayList<>(inlinedClsCount);
|
||||
for (ClassNode inner : cls.getInlinedClasses()) {
|
||||
JavaClass javaClass = rootDecompiler.convertClassNode(inner);
|
||||
javaClass.loadLists();
|
||||
javaClass.load();
|
||||
list.add(javaClass);
|
||||
}
|
||||
this.inlinedClasses = Collections.unmodifiableList(list);
|
||||
@@ -123,8 +147,7 @@ public final class JavaClass implements JavaNode {
|
||||
List<JavaField> flds = new ArrayList<>(fieldsCount);
|
||||
for (FieldNode f : cls.getFields()) {
|
||||
if (!f.contains(AFlag.DONT_GENERATE)) {
|
||||
JavaField javaField = new JavaField(this, f);
|
||||
flds.add(javaField);
|
||||
flds.add(rootDecompiler.convertFieldNode(f));
|
||||
}
|
||||
}
|
||||
this.fields = Collections.unmodifiableList(flds);
|
||||
@@ -135,8 +158,7 @@ public final class JavaClass implements JavaNode {
|
||||
List<JavaMethod> mths = new ArrayList<>(methodsCount);
|
||||
for (MethodNode m : cls.getMethods()) {
|
||||
if (!m.contains(AFlag.DONT_GENERATE)) {
|
||||
JavaMethod javaMethod = new JavaMethod(this, m);
|
||||
mths.add(javaMethod);
|
||||
mths.add(rootDecompiler.convertMethodNode(m));
|
||||
}
|
||||
}
|
||||
mths.sort(Comparator.comparing(JavaMethod::getName));
|
||||
@@ -144,56 +166,47 @@ public final class JavaClass implements JavaNode {
|
||||
}
|
||||
}
|
||||
|
||||
protected JadxDecompiler getRootDecompiler() {
|
||||
JadxDecompiler getRootDecompiler() {
|
||||
if (parent != null) {
|
||||
return parent.getRootDecompiler();
|
||||
}
|
||||
return decompiler;
|
||||
}
|
||||
|
||||
public Map<CodePosition, Object> getCodeAnnotations() {
|
||||
ICodeInfo code = getCodeInfo();
|
||||
if (code == null) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
return code.getAnnotations();
|
||||
public ICodeAnnotation getAnnotationAt(int pos) {
|
||||
return getCodeInfo().getCodeMetadata().getAt(pos);
|
||||
}
|
||||
|
||||
public Object getAnnotationAt(CodePosition pos) {
|
||||
return getCodeAnnotations().get(pos);
|
||||
}
|
||||
|
||||
public Map<CodePosition, JavaNode> getUsageMap() {
|
||||
Map<CodePosition, Object> map = getCodeAnnotations();
|
||||
public Map<Integer, JavaNode> getUsageMap() {
|
||||
Map<Integer, ICodeAnnotation> map = getCodeInfo().getCodeMetadata().getAsMap();
|
||||
if (map.isEmpty() || decompiler == null) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
Map<CodePosition, JavaNode> resultMap = new HashMap<>(map.size());
|
||||
for (Map.Entry<CodePosition, Object> entry : map.entrySet()) {
|
||||
CodePosition codePosition = entry.getKey();
|
||||
Object obj = entry.getValue();
|
||||
JavaNode node = getRootDecompiler().convertNode(obj);
|
||||
if (node != null) {
|
||||
resultMap.put(codePosition, node);
|
||||
Map<Integer, JavaNode> resultMap = new HashMap<>(map.size());
|
||||
for (Map.Entry<Integer, ICodeAnnotation> entry : map.entrySet()) {
|
||||
int codePosition = entry.getKey();
|
||||
ICodeAnnotation obj = entry.getValue();
|
||||
if (obj instanceof ICodeNodeRef) {
|
||||
JavaNode node = getRootDecompiler().getJavaNodeByRef((ICodeNodeRef) obj);
|
||||
if (node != null) {
|
||||
resultMap.put(codePosition, node);
|
||||
}
|
||||
}
|
||||
}
|
||||
return resultMap;
|
||||
}
|
||||
|
||||
public List<CodePosition> getUsageFor(JavaNode javaNode) {
|
||||
Map<CodePosition, Object> map = getCodeAnnotations();
|
||||
if (map.isEmpty() || decompiler == null) {
|
||||
public List<Integer> getUsePlacesFor(ICodeInfo codeInfo, JavaNode javaNode) {
|
||||
if (!codeInfo.hasMetadata()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
Object internalNode = getRootDecompiler().getInternalNode(javaNode);
|
||||
List<CodePosition> result = new ArrayList<>();
|
||||
for (Map.Entry<CodePosition, Object> entry : map.entrySet()) {
|
||||
CodePosition codePosition = entry.getKey();
|
||||
Object obj = entry.getValue();
|
||||
if (internalNode.equals(obj)) {
|
||||
result.add(codePosition);
|
||||
List<Integer> result = new ArrayList<>();
|
||||
codeInfo.getCodeMetadata().searchDown(0, (pos, ann) -> {
|
||||
if (javaNode.isOwnCodeAnnotation(ann)) {
|
||||
result.add(pos);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -202,20 +215,8 @@ public final class JavaClass implements JavaNode {
|
||||
return getRootDecompiler().convertNodes(cls.getUseIn());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Deprecated
|
||||
public JavaNode getJavaNodeAtPosition(int line, int offset) {
|
||||
return getRootDecompiler().getJavaNodeAtPosition(getCodeInfo(), line, offset);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Deprecated
|
||||
public CodePosition getDefinitionPosition() {
|
||||
return getRootDecompiler().getDefinitionPosition(this);
|
||||
}
|
||||
|
||||
public Integer getSourceLine(int decompiledLine) {
|
||||
return getCodeInfo().getLineMapping().get(decompiledLine);
|
||||
return getCodeInfo().getCodeMetadata().getLineMapping().get(decompiledLine);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -261,22 +262,22 @@ public final class JavaClass implements JavaNode {
|
||||
}
|
||||
|
||||
public List<JavaClass> getInnerClasses() {
|
||||
loadLists();
|
||||
load();
|
||||
return innerClasses;
|
||||
}
|
||||
|
||||
public List<JavaClass> getInlinedClasses() {
|
||||
loadLists();
|
||||
load();
|
||||
return inlinedClasses;
|
||||
}
|
||||
|
||||
public List<JavaField> getFields() {
|
||||
loadLists();
|
||||
load();
|
||||
return fields;
|
||||
}
|
||||
|
||||
public List<JavaMethod> getMethods() {
|
||||
loadLists();
|
||||
load();
|
||||
return methods;
|
||||
}
|
||||
|
||||
@@ -286,7 +287,16 @@ public final class JavaClass implements JavaNode {
|
||||
if (methodNode == null) {
|
||||
return null;
|
||||
}
|
||||
return new JavaMethod(this, methodNode);
|
||||
return getRootDecompiler().convertMethodNode(methodNode);
|
||||
}
|
||||
|
||||
public List<JavaClass> getDependencies() {
|
||||
JadxDecompiler d = getRootDecompiler();
|
||||
return ListUtils.map(cls.getDependencies(), d::convertClassNode);
|
||||
}
|
||||
|
||||
public int getTotalDepsCount() {
|
||||
return cls.getTotalDepsCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -294,11 +304,6 @@ public final class JavaClass implements JavaNode {
|
||||
this.cls.getClassInfo().removeAlias();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDecompiledLine() {
|
||||
return cls.getDecompiledLine();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDefPos() {
|
||||
return cls.getDefPosition();
|
||||
|
||||
@@ -4,6 +4,7 @@ import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import jadx.api.metadata.ICodeAnnotation;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
@@ -50,11 +51,6 @@ public final class JavaField implements JavaNode {
|
||||
return ArgType.tryToResolveClassAlias(field.root(), field.getType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDecompiledLine() {
|
||||
return field.getDecompiledLine();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDefPos() {
|
||||
return field.getDefPosition();
|
||||
@@ -70,6 +66,14 @@ public final class JavaField implements JavaNode {
|
||||
this.field.getFieldInfo().removeAlias();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOwnCodeAnnotation(ICodeAnnotation ann) {
|
||||
if (ann.getAnnType() == ICodeAnnotation.AnnType.FIELD) {
|
||||
return ann.equals(field);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal API. Not Stable!
|
||||
*/
|
||||
|
||||
@@ -9,6 +9,7 @@ import org.jetbrains.annotations.ApiStatus;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.metadata.ICodeAnnotation;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
@@ -18,6 +19,7 @@ import jadx.core.utils.Utils;
|
||||
|
||||
public final class JavaMethod implements JavaNode {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JavaMethod.class);
|
||||
|
||||
private final MethodNode mth;
|
||||
private final JavaClass parent;
|
||||
|
||||
@@ -78,7 +80,7 @@ public final class JavaMethod implements JavaNode {
|
||||
JadxDecompiler decompiler = getDeclaringClass().getRootDecompiler();
|
||||
return ovrdAttr.getRelatedMthNodes().stream()
|
||||
.map(m -> {
|
||||
JavaMethod javaMth = (JavaMethod) decompiler.convertNode(m);
|
||||
JavaMethod javaMth = decompiler.convertMethodNode(m);
|
||||
if (javaMth == null) {
|
||||
LOG.warn("Failed convert to java method: {}", m);
|
||||
}
|
||||
@@ -96,11 +98,6 @@ public final class JavaMethod implements JavaNode {
|
||||
return mth.getMethodInfo().isClassInit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDecompiledLine() {
|
||||
return mth.getDecompiledLine();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDefPos() {
|
||||
return mth.getDefPosition();
|
||||
@@ -111,6 +108,14 @@ public final class JavaMethod implements JavaNode {
|
||||
this.mth.getMethodInfo().removeAlias();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOwnCodeAnnotation(ICodeAnnotation ann) {
|
||||
if (ann.getAnnType() == ICodeAnnotation.AnnType.METHOD) {
|
||||
return ann.equals(mth);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal API. Not Stable!
|
||||
*/
|
||||
|
||||
@@ -2,6 +2,8 @@ package jadx.api;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import jadx.api.metadata.ICodeAnnotation;
|
||||
|
||||
public interface JavaNode {
|
||||
|
||||
String getName();
|
||||
@@ -12,12 +14,12 @@ public interface JavaNode {
|
||||
|
||||
JavaClass getTopParentClass();
|
||||
|
||||
int getDecompiledLine();
|
||||
|
||||
int getDefPos();
|
||||
|
||||
List<JavaNode> getUseIn();
|
||||
|
||||
default void removeAlias() {
|
||||
}
|
||||
|
||||
boolean isOwnCodeAnnotation(ICodeAnnotation ann);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import jadx.api.metadata.ICodeAnnotation;
|
||||
|
||||
public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
|
||||
private final String name;
|
||||
private final List<JavaClass> classes;
|
||||
@@ -39,11 +41,6 @@ public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDecompiledLine() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDefPos() {
|
||||
return 0;
|
||||
@@ -54,6 +51,11 @@ public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOwnCodeAnnotation(ICodeAnnotation ann) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull JavaPackage o) {
|
||||
return name.compareTo(o.name);
|
||||
|
||||
@@ -5,16 +5,18 @@ import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import jadx.api.data.annotations.VarDeclareRef;
|
||||
import jadx.api.data.annotations.VarRef;
|
||||
import jadx.api.metadata.ICodeAnnotation;
|
||||
import jadx.api.metadata.annotations.VarNode;
|
||||
import jadx.api.metadata.annotations.VarRef;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
|
||||
public class JavaVariable implements JavaNode {
|
||||
private final JavaMethod mth;
|
||||
private final VarRef varRef;
|
||||
private final VarNode varNode;
|
||||
|
||||
public JavaVariable(JavaMethod mth, VarRef varRef) {
|
||||
public JavaVariable(JavaMethod mth, VarNode varNode) {
|
||||
this.mth = mth;
|
||||
this.varRef = varRef;
|
||||
this.varNode = varNode;
|
||||
}
|
||||
|
||||
public JavaMethod getMth() {
|
||||
@@ -22,26 +24,30 @@ public class JavaVariable implements JavaNode {
|
||||
}
|
||||
|
||||
public int getReg() {
|
||||
return varRef.getReg();
|
||||
return varNode.getReg();
|
||||
}
|
||||
|
||||
public int getSsa() {
|
||||
return varRef.getSsa();
|
||||
return varNode.getSsa();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return varRef.getName();
|
||||
return varNode.getName();
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public VarRef getVarRef() {
|
||||
return varRef;
|
||||
public VarNode getVarNode() {
|
||||
return varNode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFullName() {
|
||||
return varRef.getType() + " " + varRef.getName() + " (r" + varRef.getReg() + "v" + varRef.getSsa() + ")";
|
||||
return varNode.getType() + " " + varNode.getName() + " (r" + varNode.getReg() + "v" + varNode.getSsa() + ")";
|
||||
}
|
||||
|
||||
public ArgType getType() {
|
||||
return ArgType.tryToResolveClassAlias(mth.getMethodNode().root(), varNode.getType());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -54,20 +60,9 @@ public class JavaVariable implements JavaNode {
|
||||
return mth.getTopParentClass();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDecompiledLine() {
|
||||
if (varRef instanceof VarDeclareRef) {
|
||||
return ((VarDeclareRef) varRef).getDecompiledLine();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDefPos() {
|
||||
if (varRef instanceof VarDeclareRef) {
|
||||
return ((VarDeclareRef) varRef).getDefPosition();
|
||||
}
|
||||
return 0;
|
||||
return varNode.getDefPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -75,9 +70,18 @@ public class JavaVariable implements JavaNode {
|
||||
return Collections.singletonList(mth);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOwnCodeAnnotation(ICodeAnnotation ann) {
|
||||
if (ann.getAnnType() == ICodeAnnotation.AnnType.VAR_REF) {
|
||||
VarRef varRef = (VarRef) ann;
|
||||
return varRef.getRefPos() == getDefPos();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return varRef.hashCode();
|
||||
return varNode.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -88,6 +92,6 @@ public class JavaVariable implements JavaNode {
|
||||
if (!(o instanceof JavaVariable)) {
|
||||
return false;
|
||||
}
|
||||
return varRef.equals(((JavaVariable) o).varRef);
|
||||
return varNode.equals(((JavaVariable) o).varNode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
package jadx.api.data.annotations;
|
||||
|
||||
public interface ICodeRawOffset {
|
||||
int getOffset();
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package jadx.api.data.annotations;
|
||||
|
||||
import jadx.core.dex.attributes.ILineAttributeNode;
|
||||
import jadx.core.dex.instructions.args.CodeVar;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
|
||||
public class VarDeclareRef extends VarRef implements ILineAttributeNode {
|
||||
|
||||
public static VarDeclareRef get(MethodNode mth, CodeVar codeVar) {
|
||||
VarDeclareRef ref = new VarDeclareRef(mth, codeVar);
|
||||
codeVar.setCachedVarRef(ref);
|
||||
return ref;
|
||||
}
|
||||
|
||||
private int sourceLine;
|
||||
private int decompiledLine;
|
||||
private int defPosition;
|
||||
|
||||
private VarDeclareRef(MethodNode mth, CodeVar codeVar) {
|
||||
super(mth, codeVar.getAnySsaVar());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSourceLine() {
|
||||
return sourceLine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSourceLine(int sourceLine) {
|
||||
this.sourceLine = sourceLine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDecompiledLine() {
|
||||
return decompiledLine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDecompiledLine(int decompiledLine) {
|
||||
this.decompiledLine = decompiledLine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDefPosition() {
|
||||
return defPosition;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDefPosition(int pos) {
|
||||
this.defPosition = pos;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "VarDeclareRef{r" + getReg() + 'v' + getSsa() + '}';
|
||||
}
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
package jadx.api.data.annotations;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.CodeVar;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
|
||||
public class VarRef {
|
||||
|
||||
@Nullable
|
||||
public static VarRef get(MethodNode mth, RegisterArg reg) {
|
||||
SSAVar ssaVar = reg.getSVar();
|
||||
if (ssaVar == null) {
|
||||
return null;
|
||||
}
|
||||
CodeVar codeVar = ssaVar.getCodeVar();
|
||||
VarRef cachedVarRef = codeVar.getCachedVarRef();
|
||||
if (cachedVarRef != null) {
|
||||
if (cachedVarRef.getName() == null) {
|
||||
cachedVarRef.setName(codeVar.getName());
|
||||
}
|
||||
return cachedVarRef;
|
||||
}
|
||||
VarRef newVarRef = new VarRef(mth, ssaVar);
|
||||
codeVar.setCachedVarRef(newVarRef);
|
||||
return newVarRef;
|
||||
}
|
||||
|
||||
private final MethodNode mth;
|
||||
private final int reg;
|
||||
private final int ssa;
|
||||
private final ArgType type;
|
||||
private String name;
|
||||
|
||||
protected VarRef(MethodNode mth, SSAVar ssaVar) {
|
||||
this(mth, ssaVar.getRegNum(), ssaVar.getVersion(),
|
||||
ssaVar.getCodeVar().getType(), ssaVar.getCodeVar().getName());
|
||||
}
|
||||
|
||||
private VarRef(MethodNode mth, int reg, int ssa, ArgType type, String name) {
|
||||
this.mth = mth;
|
||||
this.reg = reg;
|
||||
this.ssa = ssa;
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public MethodNode getMth() {
|
||||
return mth;
|
||||
}
|
||||
|
||||
public int getReg() {
|
||||
return reg;
|
||||
}
|
||||
|
||||
public int getSsa() {
|
||||
return ssa;
|
||||
}
|
||||
|
||||
public ArgType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof VarRef)) {
|
||||
return false;
|
||||
}
|
||||
VarRef other = (VarRef) o;
|
||||
return getReg() == other.getReg()
|
||||
&& getSsa() == other.getSsa()
|
||||
&& getMth().equals(other.getMth());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 31 * getReg() + getSsa();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "VarUseRef{r" + reg + 'v' + ssa + '}';
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ package jadx.api.data.impl;
|
||||
import jadx.api.JavaVariable;
|
||||
import jadx.api.data.CodeRefType;
|
||||
import jadx.api.data.IJavaCodeRef;
|
||||
import jadx.api.data.annotations.VarRef;
|
||||
import jadx.api.metadata.annotations.VarNode;
|
||||
|
||||
public class JadxCodeRef implements IJavaCodeRef {
|
||||
|
||||
@@ -23,8 +23,8 @@ public class JadxCodeRef implements IJavaCodeRef {
|
||||
return forVar(javaVariable.getReg(), javaVariable.getSsa());
|
||||
}
|
||||
|
||||
public static JadxCodeRef forVar(VarRef varRef) {
|
||||
return forVar(varRef.getReg(), varRef.getSsa());
|
||||
public static JadxCodeRef forVar(VarNode varNode) {
|
||||
return forVar(varNode.getReg(), varNode.getSsa());
|
||||
}
|
||||
|
||||
public static JadxCodeRef forCatch(int handlerOffset) {
|
||||
|
||||
@@ -2,23 +2,19 @@ package jadx.api.impl;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import jadx.api.CodePosition;
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.api.metadata.ICodeAnnotation;
|
||||
import jadx.api.metadata.ICodeMetadata;
|
||||
import jadx.api.metadata.impl.CodeMetadataStorage;
|
||||
|
||||
public class AnnotatedCodeInfo implements ICodeInfo {
|
||||
|
||||
private final String code;
|
||||
private final Map<Integer, Integer> lineMapping;
|
||||
private final Map<CodePosition, Object> annotations;
|
||||
private final ICodeMetadata metadata;
|
||||
|
||||
public AnnotatedCodeInfo(ICodeInfo codeInfo) {
|
||||
this(codeInfo.getCodeStr(), codeInfo.getLineMapping(), codeInfo.getAnnotations());
|
||||
}
|
||||
|
||||
public AnnotatedCodeInfo(String code, Map<Integer, Integer> lineMapping, Map<CodePosition, Object> annotations) {
|
||||
public AnnotatedCodeInfo(String code, Map<Integer, Integer> lineMapping, Map<Integer, ICodeAnnotation> annotations) {
|
||||
this.code = code;
|
||||
this.lineMapping = lineMapping;
|
||||
this.annotations = annotations;
|
||||
this.metadata = CodeMetadataStorage.build(lineMapping, annotations);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -27,13 +23,13 @@ public class AnnotatedCodeInfo implements ICodeInfo {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Integer, Integer> getLineMapping() {
|
||||
return lineMapping;
|
||||
public ICodeMetadata getCodeMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<CodePosition, Object> getAnnotations() {
|
||||
return annotations;
|
||||
public boolean hasMetadata() {
|
||||
return metadata != ICodeMetadata.EMPTY;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -5,18 +5,20 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import jadx.api.CodePosition;
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.core.dex.attributes.ILineAttributeNode;
|
||||
import jadx.api.metadata.ICodeAnnotation;
|
||||
import jadx.api.metadata.ICodeNodeRef;
|
||||
import jadx.api.metadata.annotations.NodeDeclareRef;
|
||||
import jadx.api.metadata.annotations.VarRef;
|
||||
import jadx.core.utils.StringUtils;
|
||||
|
||||
public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter {
|
||||
|
||||
private int line = 1;
|
||||
private int offset;
|
||||
private Map<CodePosition, Object> annotations = Collections.emptyMap();
|
||||
private Map<Integer, ICodeAnnotation> annotations = Collections.emptyMap();
|
||||
private Map<Integer, Integer> lineMap = Collections.emptyMap();
|
||||
|
||||
public AnnotatedCodeWriter() {
|
||||
@@ -59,19 +61,17 @@ public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter
|
||||
|
||||
@Override
|
||||
public ICodeWriter add(ICodeWriter cw) {
|
||||
if ((!(cw instanceof AnnotatedCodeWriter))) {
|
||||
if (!cw.isMetadataSupported()) {
|
||||
buf.append(cw.getCodeStr());
|
||||
return this;
|
||||
}
|
||||
AnnotatedCodeWriter code = ((AnnotatedCodeWriter) cw);
|
||||
line--;
|
||||
int startLine = line;
|
||||
int startPos = getLength();
|
||||
for (Map.Entry<CodePosition, Object> entry : code.annotations.entrySet()) {
|
||||
CodePosition codePos = entry.getKey();
|
||||
int newLine = startLine + codePos.getLine();
|
||||
int newPos = startPos + codePos.getPos();
|
||||
attachAnnotation(entry.getValue(), new CodePosition(newLine, codePos.getOffset(), newPos));
|
||||
for (Map.Entry<Integer, ICodeAnnotation> entry : code.annotations.entrySet()) {
|
||||
int pos = entry.getKey();
|
||||
int newPos = startPos + pos;
|
||||
attachAnnotation(entry.getValue(), newPos);
|
||||
}
|
||||
for (Map.Entry<Integer, Integer> entry : code.lineMap.entrySet()) {
|
||||
attachSourceLine(line + entry.getKey(), entry.getValue());
|
||||
@@ -101,44 +101,36 @@ public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter
|
||||
return line;
|
||||
}
|
||||
|
||||
private static final class DefinitionWrapper {
|
||||
private final ILineAttributeNode node;
|
||||
|
||||
private DefinitionWrapper(ILineAttributeNode node) {
|
||||
this.node = node;
|
||||
}
|
||||
|
||||
public ILineAttributeNode getNode() {
|
||||
return node;
|
||||
}
|
||||
@Override
|
||||
public int getLineStartPos() {
|
||||
return getLength() - offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attachDefinition(ILineAttributeNode obj) {
|
||||
public void attachDefinition(ICodeNodeRef obj) {
|
||||
if (obj == null) {
|
||||
return;
|
||||
}
|
||||
attachAnnotation(obj);
|
||||
attachAnnotation(new DefinitionWrapper(obj), new CodePosition(line, offset, getLength()));
|
||||
attachAnnotation(new NodeDeclareRef(obj));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attachAnnotation(Object obj) {
|
||||
public void attachAnnotation(ICodeAnnotation obj) {
|
||||
if (obj == null) {
|
||||
return;
|
||||
}
|
||||
attachAnnotation(obj, new CodePosition(line, offset + 1, getLength()));
|
||||
attachAnnotation(obj, getLength());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attachLineAnnotation(Object obj) {
|
||||
public void attachLineAnnotation(ICodeAnnotation obj) {
|
||||
if (obj == null) {
|
||||
return;
|
||||
}
|
||||
attachAnnotation(obj, new CodePosition(line, 0, getLength() - offset));
|
||||
attachAnnotation(obj, getLineStartPos());
|
||||
}
|
||||
|
||||
private void attachAnnotation(Object obj, CodePosition pos) {
|
||||
private void attachAnnotation(ICodeAnnotation obj, int pos) {
|
||||
if (annotations.isEmpty()) {
|
||||
annotations = new HashMap<>();
|
||||
}
|
||||
@@ -164,29 +156,39 @@ public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter
|
||||
public ICodeInfo finish() {
|
||||
removeFirstEmptyLine();
|
||||
processDefinitionAnnotations();
|
||||
validateAnnotations();
|
||||
String code = buf.toString();
|
||||
buf = null;
|
||||
return new AnnotatedCodeInfo(code, lineMap, annotations);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<CodePosition, Object> getRawAnnotations() {
|
||||
public Map<Integer, ICodeAnnotation> getRawAnnotations() {
|
||||
return annotations;
|
||||
}
|
||||
|
||||
private void processDefinitionAnnotations() {
|
||||
if (!annotations.isEmpty()) {
|
||||
annotations.entrySet().removeIf(entry -> {
|
||||
Object v = entry.getValue();
|
||||
if (v instanceof DefinitionWrapper) {
|
||||
ILineAttributeNode l = ((DefinitionWrapper) v).getNode();
|
||||
CodePosition codePos = entry.getKey();
|
||||
l.setDecompiledLine(codePos.getLine());
|
||||
l.setDefPosition(codePos.getPos());
|
||||
return true;
|
||||
annotations.forEach((k, v) -> {
|
||||
if (v instanceof NodeDeclareRef) {
|
||||
NodeDeclareRef declareRef = (NodeDeclareRef) v;
|
||||
declareRef.setDefPos(k);
|
||||
declareRef.getNode().setDefPosition(k);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void validateAnnotations() {
|
||||
if (annotations.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
annotations.values().removeIf(v -> {
|
||||
if (v.getAnnType() == ICodeAnnotation.AnnType.VAR_REF) {
|
||||
VarRef varRef = (VarRef) v;
|
||||
return varRef.getRefPos() == 0;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package jadx.api.impl;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.ICodeCache;
|
||||
import jadx.api.ICodeInfo;
|
||||
|
||||
public abstract class DelegateCodeCache implements ICodeCache {
|
||||
|
||||
protected final ICodeCache backCache;
|
||||
|
||||
public DelegateCodeCache(ICodeCache backCache) {
|
||||
this.backCache = backCache;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(String clsFullName, ICodeInfo codeInfo) {
|
||||
backCache.add(clsFullName, codeInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(String clsFullName) {
|
||||
backCache.remove(clsFullName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ICodeInfo get(String clsFullName) {
|
||||
return backCache.get(clsFullName);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public String getCode(String clsFullName) {
|
||||
return backCache.getCode(clsFullName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(String clsFullName) {
|
||||
return backCache.contains(clsFullName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
backCache.close();
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
package jadx.api.impl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.ICodeCache;
|
||||
@@ -22,9 +24,33 @@ public class InMemoryCodeCache implements ICodeCache {
|
||||
storage.remove(clsFullName);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public @Nullable ICodeInfo get(String clsFullName) {
|
||||
return storage.get(clsFullName);
|
||||
public ICodeInfo get(String clsFullName) {
|
||||
ICodeInfo codeInfo = storage.get(clsFullName);
|
||||
if (codeInfo == null) {
|
||||
return ICodeInfo.EMPTY;
|
||||
}
|
||||
return codeInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String getCode(String clsFullName) {
|
||||
ICodeInfo codeInfo = storage.get(clsFullName);
|
||||
if (codeInfo == null) {
|
||||
return null;
|
||||
}
|
||||
return codeInfo.getCodeStr();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(String clsFullName) {
|
||||
return storage.containsKey(clsFullName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
storage.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package jadx.api.impl;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.ICodeCache;
|
||||
@@ -7,6 +8,8 @@ import jadx.api.ICodeInfo;
|
||||
|
||||
public class NoOpCodeCache implements ICodeCache {
|
||||
|
||||
public static final NoOpCodeCache INSTANCE = new NoOpCodeCache();
|
||||
|
||||
@Override
|
||||
public void add(String clsFullName, ICodeInfo codeInfo) {
|
||||
// do nothing
|
||||
@@ -18,10 +21,26 @@ public class NoOpCodeCache implements ICodeCache {
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ICodeInfo get(String clsFullName) {
|
||||
@NotNull
|
||||
public ICodeInfo get(String clsFullName) {
|
||||
return ICodeInfo.EMPTY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String getCode(String clsFullName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(String clsFullName) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "NoOpCodeCache";
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
package jadx.api.impl;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import jadx.api.CodePosition;
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.api.metadata.ICodeMetadata;
|
||||
|
||||
public class SimpleCodeInfo implements ICodeInfo {
|
||||
|
||||
@@ -20,13 +17,13 @@ public class SimpleCodeInfo implements ICodeInfo {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Integer, Integer> getLineMapping() {
|
||||
return Collections.emptyMap();
|
||||
public ICodeMetadata getCodeMetadata() {
|
||||
return ICodeMetadata.EMPTY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<CodePosition, Object> getAnnotations() {
|
||||
return Collections.emptyMap();
|
||||
public boolean hasMetadata() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -6,11 +6,11 @@ import java.util.Map;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.CodePosition;
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.core.dex.attributes.ILineAttributeNode;
|
||||
import jadx.api.metadata.ICodeAnnotation;
|
||||
import jadx.api.metadata.ICodeNodeRef;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
/**
|
||||
@@ -193,17 +193,22 @@ public class SimpleCodeWriter implements ICodeWriter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attachDefinition(ILineAttributeNode obj) {
|
||||
public int getLineStartPos() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attachDefinition(ICodeNodeRef obj) {
|
||||
// no op
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attachAnnotation(Object obj) {
|
||||
public void attachAnnotation(ICodeAnnotation obj) {
|
||||
// no op
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attachLineAnnotation(Object obj) {
|
||||
public void attachLineAnnotation(ICodeAnnotation obj) {
|
||||
// no op
|
||||
}
|
||||
|
||||
@@ -238,7 +243,7 @@ public class SimpleCodeWriter implements ICodeWriter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<CodePosition, Object> getRawAnnotations() {
|
||||
public Map<Integer, ICodeAnnotation> getRawAnnotations() {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package jadx.api.metadata;
|
||||
|
||||
public interface ICodeAnnotation {
|
||||
|
||||
enum AnnType {
|
||||
CLASS,
|
||||
FIELD,
|
||||
METHOD,
|
||||
VAR,
|
||||
VAR_REF,
|
||||
DECLARATION,
|
||||
OFFSET
|
||||
}
|
||||
|
||||
AnnType getAnnType();
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package jadx.api.metadata;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.metadata.impl.CodeMetadataStorage;
|
||||
|
||||
public interface ICodeMetadata {
|
||||
|
||||
ICodeMetadata EMPTY = CodeMetadataStorage.empty();
|
||||
|
||||
@Nullable
|
||||
ICodeAnnotation getAt(int position);
|
||||
|
||||
@Nullable
|
||||
ICodeAnnotation getClosestUp(int position);
|
||||
|
||||
@Nullable
|
||||
ICodeAnnotation searchUp(int position, ICodeAnnotation.AnnType annType);
|
||||
|
||||
@Nullable
|
||||
ICodeAnnotation searchUp(int position, int limitPos, ICodeAnnotation.AnnType annType);
|
||||
|
||||
/**
|
||||
* Iterate code annotations from {@code startPos} to smaller positions.
|
||||
*
|
||||
* @param visitor
|
||||
* return not null value to stop iterations
|
||||
*/
|
||||
@Nullable
|
||||
<T> T searchUp(int startPos, BiFunction<Integer, ICodeAnnotation, T> visitor);
|
||||
|
||||
/**
|
||||
* Iterate code annotations from {@code startPos} to higher positions.
|
||||
*
|
||||
* @param visitor
|
||||
* return not null value to stop iterations
|
||||
*/
|
||||
@Nullable
|
||||
<T> T searchDown(int startPos, BiFunction<Integer, ICodeAnnotation, T> visitor);
|
||||
|
||||
/**
|
||||
* Get current node at position (can be enclosing class or method)
|
||||
*/
|
||||
@Nullable
|
||||
ICodeNodeRef getNodeAt(int position);
|
||||
|
||||
/**
|
||||
* Any definition of class or method below position
|
||||
*/
|
||||
@Nullable
|
||||
ICodeNodeRef getNodeBelow(int position);
|
||||
|
||||
Map<Integer, ICodeAnnotation> getAsMap();
|
||||
|
||||
Map<Integer, Integer> getLineMapping();
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package jadx.api.metadata;
|
||||
|
||||
public interface ICodeNodeRef extends ICodeAnnotation {
|
||||
int getDefPosition();
|
||||
|
||||
void setDefPosition(int pos);
|
||||
}
|
||||
+8
-3
@@ -1,11 +1,12 @@
|
||||
package jadx.api.data.annotations;
|
||||
package jadx.api.metadata.annotations;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.api.metadata.ICodeAnnotation;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
|
||||
public class InsnCodeOffset implements ICodeRawOffset {
|
||||
public class InsnCodeOffset implements ICodeAnnotation {
|
||||
|
||||
public static void attach(ICodeWriter code, InsnNode insn) {
|
||||
if (insn == null) {
|
||||
@@ -40,11 +41,15 @@ public class InsnCodeOffset implements ICodeRawOffset {
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnnType getAnnType() {
|
||||
return AnnType.OFFSET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "offset=" + offset;
|
||||
@@ -0,0 +1,39 @@
|
||||
package jadx.api.metadata.annotations;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import jadx.api.metadata.ICodeAnnotation;
|
||||
import jadx.api.metadata.ICodeNodeRef;
|
||||
|
||||
public class NodeDeclareRef implements ICodeAnnotation {
|
||||
|
||||
private final ICodeNodeRef node;
|
||||
|
||||
private int defPos;
|
||||
|
||||
public NodeDeclareRef(ICodeNodeRef node) {
|
||||
this.node = Objects.requireNonNull(node);
|
||||
}
|
||||
|
||||
public ICodeNodeRef getNode() {
|
||||
return node;
|
||||
}
|
||||
|
||||
public int getDefPos() {
|
||||
return defPos;
|
||||
}
|
||||
|
||||
public void setDefPos(int defPos) {
|
||||
this.defPos = defPos;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnnType getAnnType() {
|
||||
return AnnType.DECLARATION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "NodeDeclareRef{" + node + '}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
package jadx.api.metadata.annotations;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.metadata.ICodeAnnotation;
|
||||
import jadx.api.metadata.ICodeNodeRef;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.CodeVar;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
|
||||
/**
|
||||
* Variable info
|
||||
*/
|
||||
public class VarNode implements ICodeNodeRef {
|
||||
|
||||
@Nullable
|
||||
public static VarNode get(MethodNode mth, RegisterArg reg) {
|
||||
SSAVar ssaVar = reg.getSVar();
|
||||
if (ssaVar == null) {
|
||||
return null;
|
||||
}
|
||||
return get(mth, ssaVar);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static VarNode get(MethodNode mth, CodeVar codeVar) {
|
||||
return get(mth, codeVar.getAnySsaVar());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static VarNode get(MethodNode mth, SSAVar ssaVar) {
|
||||
CodeVar codeVar = ssaVar.getCodeVar();
|
||||
if (codeVar.isThis()) {
|
||||
return null;
|
||||
}
|
||||
VarNode cachedVarNode = codeVar.getCachedVarNode();
|
||||
if (cachedVarNode != null) {
|
||||
return cachedVarNode;
|
||||
}
|
||||
VarNode newVarNode = new VarNode(mth, ssaVar);
|
||||
codeVar.setCachedVarNode(newVarNode);
|
||||
return newVarNode;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static ICodeAnnotation getRef(MethodNode mth, RegisterArg reg) {
|
||||
VarNode varNode = get(mth, reg);
|
||||
if (varNode == null) {
|
||||
return null;
|
||||
}
|
||||
return varNode.getVarRef();
|
||||
}
|
||||
|
||||
private final MethodNode mth;
|
||||
private final int reg;
|
||||
private final int ssa;
|
||||
private final ArgType type;
|
||||
private @Nullable String name;
|
||||
private int defPos;
|
||||
|
||||
private final VarRef varRef;
|
||||
|
||||
protected VarNode(MethodNode mth, SSAVar ssaVar) {
|
||||
this(mth, ssaVar.getRegNum(), ssaVar.getVersion(),
|
||||
ssaVar.getCodeVar().getType(), ssaVar.getCodeVar().getName());
|
||||
}
|
||||
|
||||
public VarNode(MethodNode mth, int reg, int ssa, ArgType type, String name) {
|
||||
this.mth = mth;
|
||||
this.reg = reg;
|
||||
this.ssa = ssa;
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
this.varRef = VarRef.fromVarNode(this);
|
||||
}
|
||||
|
||||
public MethodNode getMth() {
|
||||
return mth;
|
||||
}
|
||||
|
||||
public int getReg() {
|
||||
return reg;
|
||||
}
|
||||
|
||||
public int getSsa() {
|
||||
return ssa;
|
||||
}
|
||||
|
||||
public ArgType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public VarRef getVarRef() {
|
||||
return varRef;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDefPosition() {
|
||||
return defPos;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDefPosition(int pos) {
|
||||
this.defPos = pos;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnnType getAnnType() {
|
||||
return AnnType.VAR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int h = 31 * getReg() + getSsa();
|
||||
return 31 * h + mth.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof VarNode)) {
|
||||
return false;
|
||||
}
|
||||
VarNode other = (VarNode) o;
|
||||
return getReg() == other.getReg()
|
||||
&& getSsa() == other.getSsa()
|
||||
&& getMth().equals(other.getMth());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "VarNode{r" + reg + 'v' + ssa + '}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package jadx.api.metadata.annotations;
|
||||
|
||||
import jadx.api.metadata.ICodeAnnotation;
|
||||
|
||||
/**
|
||||
* Variable reference by position of VarNode in code metadata.
|
||||
* <br>
|
||||
* Because on creation position not yet known,
|
||||
* VarRef created using VarNode as a source of ref pos during serialization.
|
||||
* <br>
|
||||
* On metadata deserialization created with ref pos directly.
|
||||
*/
|
||||
public abstract class VarRef implements ICodeAnnotation {
|
||||
|
||||
public static VarRef fromPos(int refPos) {
|
||||
if (refPos == 0) {
|
||||
throw new IllegalArgumentException("Zero refPos");
|
||||
}
|
||||
return new FixedVarRef(refPos);
|
||||
}
|
||||
|
||||
public static VarRef fromVarNode(VarNode varNode) {
|
||||
return new RelatedVarRef(varNode);
|
||||
}
|
||||
|
||||
public abstract int getRefPos();
|
||||
|
||||
@Override
|
||||
public AnnType getAnnType() {
|
||||
return AnnType.VAR_REF;
|
||||
}
|
||||
|
||||
public static final class FixedVarRef extends VarRef {
|
||||
private final int refPos;
|
||||
|
||||
public FixedVarRef(int refPos) {
|
||||
this.refPos = refPos;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRefPos() {
|
||||
return refPos;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class RelatedVarRef extends VarRef {
|
||||
private final VarNode varNode;
|
||||
|
||||
public RelatedVarRef(VarNode varNode) {
|
||||
this.varNode = varNode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRefPos() {
|
||||
return varNode.getDefPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "VarRef{" + varNode + ", name=" + varNode.getName() + ", mth=" + varNode.getMth() + '}';
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "VarRef{" + getRefPos() + '}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
package jadx.api.metadata.impl;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
import java.util.NavigableMap;
|
||||
import java.util.TreeMap;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.metadata.ICodeAnnotation;
|
||||
import jadx.api.metadata.ICodeMetadata;
|
||||
import jadx.api.metadata.ICodeNodeRef;
|
||||
import jadx.api.metadata.annotations.NodeDeclareRef;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
public class CodeMetadataStorage implements ICodeMetadata {
|
||||
|
||||
public static ICodeMetadata build(Map<Integer, Integer> lines, Map<Integer, ICodeAnnotation> map) {
|
||||
if (map.isEmpty() && lines.isEmpty()) {
|
||||
return ICodeMetadata.EMPTY;
|
||||
}
|
||||
Comparator<Integer> reverseCmp = Comparator.comparingInt(Integer::intValue).reversed();
|
||||
NavigableMap<Integer, ICodeAnnotation> navMap = new TreeMap<>(reverseCmp);
|
||||
navMap.putAll(map);
|
||||
return new CodeMetadataStorage(lines, navMap);
|
||||
}
|
||||
|
||||
public static ICodeMetadata empty() {
|
||||
return new CodeMetadataStorage(Collections.emptyMap(), Collections.emptyNavigableMap());
|
||||
}
|
||||
|
||||
private final Map<Integer, Integer> lines;
|
||||
|
||||
private final NavigableMap<Integer, ICodeAnnotation> navMap;
|
||||
|
||||
private CodeMetadataStorage(Map<Integer, Integer> lines, NavigableMap<Integer, ICodeAnnotation> navMap) {
|
||||
this.lines = lines;
|
||||
this.navMap = navMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ICodeAnnotation getAt(int position) {
|
||||
return navMap.get(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ICodeAnnotation getClosestUp(int position) {
|
||||
Map.Entry<Integer, ICodeAnnotation> entryBefore = navMap.higherEntry(position);
|
||||
return entryBefore != null ? entryBefore.getValue() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ICodeAnnotation searchUp(int position, ICodeAnnotation.AnnType annType) {
|
||||
for (ICodeAnnotation v : navMap.tailMap(position, true).values()) {
|
||||
if (v.getAnnType() == annType) {
|
||||
return v;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ICodeAnnotation searchUp(int position, int limitPos, ICodeAnnotation.AnnType annType) {
|
||||
for (ICodeAnnotation v : navMap.subMap(position, true, limitPos, true).values()) {
|
||||
if (v.getAnnType() == annType) {
|
||||
return v;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> @Nullable T searchUp(int startPos, BiFunction<Integer, ICodeAnnotation, T> visitor) {
|
||||
for (Map.Entry<Integer, ICodeAnnotation> entry : navMap.tailMap(startPos, true).entrySet()) {
|
||||
T value = visitor.apply(entry.getKey(), entry.getValue());
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> @Nullable T searchDown(int startPos, BiFunction<Integer, ICodeAnnotation, T> visitor) {
|
||||
NavigableMap<Integer, ICodeAnnotation> map = navMap.headMap(startPos, true).descendingMap();
|
||||
for (Map.Entry<Integer, ICodeAnnotation> entry : map.entrySet()) {
|
||||
T value = visitor.apply(entry.getKey(), entry.getValue());
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ICodeNodeRef getNodeAt(int position) {
|
||||
return navMap.tailMap(position, true)
|
||||
.values().stream()
|
||||
.flatMap(CodeMetadataStorage::mapEnclosingNode)
|
||||
.findFirst().orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ICodeNodeRef getNodeBelow(int position) {
|
||||
return navMap.headMap(position, true).descendingMap()
|
||||
.values().stream()
|
||||
.flatMap(CodeMetadataStorage::mapEnclosingNode)
|
||||
.findFirst().orElse(null);
|
||||
}
|
||||
|
||||
private static Stream<ICodeNodeRef> mapEnclosingNode(ICodeAnnotation ann) {
|
||||
if (ann instanceof NodeDeclareRef) {
|
||||
ICodeNodeRef node = ((NodeDeclareRef) ann).getNode();
|
||||
if (node instanceof ClassNode || node instanceof MethodNode) {
|
||||
return Stream.of(node);
|
||||
}
|
||||
}
|
||||
return Stream.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public NavigableMap<Integer, ICodeAnnotation> getAsMap() {
|
||||
return navMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Integer, Integer> getLineMapping() {
|
||||
return lines;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CodeMetadata{lines=" + lines
|
||||
+ ", annotations=\n" + Utils.listToString(navMap.entrySet(), "\n") + "\n}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package jadx.api.utils;
|
||||
|
||||
import jadx.api.ICodeWriter;
|
||||
|
||||
public class CodeUtils {
|
||||
|
||||
public static String getLineForPos(String code, int pos) {
|
||||
int start = getLineStartForPos(code, pos);
|
||||
int end = getLineEndForPos(code, pos);
|
||||
return code.substring(start, end);
|
||||
}
|
||||
|
||||
public static int getLineStartForPos(String code, int pos) {
|
||||
String newLine = ICodeWriter.NL;
|
||||
int start = code.lastIndexOf(newLine, pos);
|
||||
return start == -1 ? 0 : start + newLine.length();
|
||||
}
|
||||
|
||||
public static int getLineEndForPos(String code, int pos) {
|
||||
int end = code.indexOf(ICodeWriter.NL, pos);
|
||||
return end == -1 ? code.length() : end;
|
||||
}
|
||||
|
||||
public static int getLineNumForPos(String code, int pos) {
|
||||
String newLine = ICodeWriter.NL;
|
||||
int newLineLen = newLine.length();
|
||||
int line = 1;
|
||||
int prev = 0;
|
||||
while (true) {
|
||||
int next = code.indexOf(newLine, prev);
|
||||
if (next >= pos) {
|
||||
return line;
|
||||
}
|
||||
prev = next + newLineLen;
|
||||
line++;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ public class Consts {
|
||||
public static final boolean DEBUG_TYPE_INFERENCE = false;
|
||||
public static final boolean DEBUG_OVERLOADED_CASTS = 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_STRING = "java.lang.String";
|
||||
|
||||
@@ -238,9 +238,17 @@ public class Jadx {
|
||||
private static String version;
|
||||
|
||||
public static String getVersion() {
|
||||
if (version != null) {
|
||||
return version;
|
||||
if (version == null) {
|
||||
version = searchJadxVersion();
|
||||
}
|
||||
return version;
|
||||
}
|
||||
|
||||
public static boolean isDevVersion() {
|
||||
return getVersion().equals(VERSION_DEV);
|
||||
}
|
||||
|
||||
private static String searchJadxVersion() {
|
||||
try {
|
||||
ClassLoader classLoader = Jadx.class.getClassLoader();
|
||||
if (classLoader != null) {
|
||||
@@ -250,7 +258,6 @@ public class Jadx {
|
||||
Manifest manifest = new Manifest(is);
|
||||
String ver = manifest.getMainAttributes().getValue("jadx-version");
|
||||
if (ver != null) {
|
||||
version = ver;
|
||||
return ver;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,6 +99,10 @@ public class ProcessClass {
|
||||
return generateCode(topParentClass);
|
||||
}
|
||||
try {
|
||||
if (cls.contains(AFlag.DONT_GENERATE)) {
|
||||
process(cls, false);
|
||||
return ICodeInfo.EMPTY;
|
||||
}
|
||||
for (ClassNode depCls : cls.getDependencies()) {
|
||||
process(depCls, false);
|
||||
}
|
||||
|
||||
@@ -203,6 +203,9 @@ public class ClspGraph {
|
||||
if (isNew) {
|
||||
addSuperTypes(parentCls, result);
|
||||
}
|
||||
} else {
|
||||
// parent type is unknown
|
||||
result.add(parentType.getObject());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -614,21 +614,23 @@ public class ClassGen {
|
||||
if (useCls.equals(extClsInfo)) {
|
||||
return shortName;
|
||||
}
|
||||
if (extClsInfo.getPackage().equals("java.lang") && extClsInfo.getParentClass() == null) {
|
||||
return shortName;
|
||||
}
|
||||
if (isClassInnerFor(useCls, extClsInfo)) {
|
||||
return shortName;
|
||||
}
|
||||
if (extClsInfo.isInner()) {
|
||||
return expandInnerClassName(useCls, extClsInfo);
|
||||
}
|
||||
if (searchCollision(cls.root(), useCls, extClsInfo)) {
|
||||
if (checkInnerCollision(cls.root(), useCls, extClsInfo)
|
||||
|| checkInPackageCollision(cls.root(), useCls, extClsInfo)) {
|
||||
return fullName;
|
||||
}
|
||||
if (isBothClassesInOneTopClass(useCls, extClsInfo)) {
|
||||
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
|
||||
if (extClsInfo.getPackage().equals(useCls.getPackage()) && !extClsInfo.isInner()) {
|
||||
return shortName;
|
||||
@@ -709,7 +711,7 @@ public class ClassGen {
|
||||
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) {
|
||||
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) {
|
||||
|
||||
@@ -11,9 +11,8 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.CommentsLevel;
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.api.data.annotations.InsnCodeOffset;
|
||||
import jadx.api.data.annotations.VarDeclareRef;
|
||||
import jadx.api.data.annotations.VarRef;
|
||||
import jadx.api.metadata.annotations.InsnCodeOffset;
|
||||
import jadx.api.metadata.annotations.VarNode;
|
||||
import jadx.api.plugins.input.data.MethodHandleType;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
@@ -109,7 +108,7 @@ public class InsnGen {
|
||||
if (arg.isRegister()) {
|
||||
RegisterArg reg = (RegisterArg) arg;
|
||||
if (code.isMetadataSupported()) {
|
||||
code.attachAnnotation(VarRef.get(mth, reg));
|
||||
code.attachAnnotation(VarNode.getRef(mth, reg));
|
||||
}
|
||||
code.add(mgen.getNameGen().useArg(reg));
|
||||
} else if (arg.isLiteral()) {
|
||||
@@ -162,10 +161,18 @@ public class InsnGen {
|
||||
}
|
||||
useType(code, codeVar.getType());
|
||||
code.add(' ');
|
||||
defVar(code, codeVar);
|
||||
}
|
||||
|
||||
/**
|
||||
* Variable definition without type, only var name
|
||||
*/
|
||||
private void defVar(ICodeWriter code, CodeVar codeVar) {
|
||||
String varName = mgen.getNameGen().assignArg(codeVar);
|
||||
if (code.isMetadataSupported()) {
|
||||
code.attachDefinition(VarDeclareRef.get(mth, codeVar));
|
||||
code.attachDefinition(VarNode.get(mth, codeVar));
|
||||
}
|
||||
code.add(mgen.getNameGen().assignArg(codeVar));
|
||||
code.add(varName);
|
||||
}
|
||||
|
||||
private String lit(LiteralArg arg) {
|
||||
@@ -800,14 +807,9 @@ public class InsnGen {
|
||||
break;
|
||||
|
||||
case SUPER:
|
||||
ClassInfo superCallCls = getClassForSuperCall(code, callMth);
|
||||
if (superCallCls != null) {
|
||||
useClass(code, superCallCls);
|
||||
code.add('.');
|
||||
}
|
||||
// use 'super' instead 'this' in 0 arg
|
||||
code.add("super").add('.');
|
||||
k++;
|
||||
callSuper(code, callMth);
|
||||
k++; // use 'super' instead 'this' in 0 arg
|
||||
code.add('.');
|
||||
break;
|
||||
|
||||
case STATIC:
|
||||
@@ -939,7 +941,7 @@ public class InsnGen {
|
||||
code.add(", ");
|
||||
}
|
||||
CodeVar argCodeVar = callArgs.get(i).getSVar().getCodeVar();
|
||||
code.add(nameGen.assignArg(argCodeVar));
|
||||
defVar(code, argCodeVar);
|
||||
}
|
||||
}
|
||||
// force set external arg names into call method args
|
||||
@@ -947,7 +949,8 @@ public class InsnGen {
|
||||
int startArg = customNode.getHandleType() == MethodHandleType.INVOKE_STATIC ? 0 : 1; // skip 'this' arg
|
||||
for (int i = startArg; i < extArgsCount; i++) {
|
||||
RegisterArg extArg = (RegisterArg) customNode.getArg(i);
|
||||
callArgs.get(i).setName(extArg.getName());
|
||||
RegisterArg callRegArg = callArgs.get(i);
|
||||
callRegArg.getSVar().setCodeVar(extArg.getSVar().getCodeVar());
|
||||
}
|
||||
code.add(" -> {");
|
||||
code.incIndent();
|
||||
@@ -957,34 +960,43 @@ public class InsnGen {
|
||||
code.startLine('}');
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private ClassInfo getClassForSuperCall(ICodeWriter code, MethodInfo callMth) {
|
||||
ClassNode useCls = mth.getParentClass();
|
||||
ClassInfo insnCls = useCls.getClassInfo();
|
||||
ClassInfo declClass = callMth.getDeclClass();
|
||||
if (insnCls.equals(declClass)) {
|
||||
return null;
|
||||
private void callSuper(ICodeWriter code, MethodInfo callMth) {
|
||||
ClassInfo superCallCls = getClassForSuperCall(callMth);
|
||||
if (superCallCls == null) {
|
||||
// unknown class, add comment to keep that info
|
||||
code.add("super/*").add(callMth.getDeclClass().getFullName()).add("*/");
|
||||
return;
|
||||
}
|
||||
ClassNode topClass = useCls.getTopParentClass();
|
||||
if (topClass.getClassInfo().equals(declClass)) {
|
||||
return declClass;
|
||||
ClassInfo curClass = mth.getParentClass().getClassInfo();
|
||||
if (superCallCls.equals(curClass)) {
|
||||
code.add("super");
|
||||
return;
|
||||
}
|
||||
// search call class
|
||||
ClassNode nextParent = useCls;
|
||||
do {
|
||||
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);
|
||||
// use custom class
|
||||
useClass(code, superCallCls);
|
||||
code.add(".super");
|
||||
}
|
||||
|
||||
// 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,
|
||||
|
||||
@@ -13,8 +13,8 @@ import org.slf4j.LoggerFactory;
|
||||
import jadx.api.CommentsLevel;
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.api.data.annotations.InsnCodeOffset;
|
||||
import jadx.api.data.annotations.VarDeclareRef;
|
||||
import jadx.api.metadata.annotations.InsnCodeOffset;
|
||||
import jadx.api.metadata.annotations.VarNode;
|
||||
import jadx.api.plugins.input.data.AccessFlags;
|
||||
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||
import jadx.api.plugins.input.data.attributes.JadxAttrType;
|
||||
@@ -243,7 +243,7 @@ public class MethodGen {
|
||||
code.add(' ');
|
||||
String varName = nameGen.assignArg(var);
|
||||
if (code.isMetadataSupported() && ssaVar != null /* for fallback mode */) {
|
||||
code.attachDefinition(VarDeclareRef.get(mth, var));
|
||||
code.attachDefinition(VarNode.get(mth, var));
|
||||
}
|
||||
code.add(varName);
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import java.util.Set;
|
||||
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.deobf.NameMapper;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
@@ -173,7 +174,7 @@ public class NameGen {
|
||||
|
||||
private String makeNameForType(ArgType type) {
|
||||
if (type.isPrimitive()) {
|
||||
return makeNameForPrimitive(type);
|
||||
return type.getPrimitiveType().getShortName().toLowerCase();
|
||||
}
|
||||
if (type.isArray()) {
|
||||
return makeNameForType(type.getArrayRootElement()) + "Arr";
|
||||
@@ -181,10 +182,6 @@ public class NameGen {
|
||||
return makeNameForObject(type);
|
||||
}
|
||||
|
||||
private static String makeNameForPrimitive(ArgType type) {
|
||||
return type.getPrimitiveType().getShortName().toLowerCase();
|
||||
}
|
||||
|
||||
private String makeNameForObject(ArgType type) {
|
||||
if (type.isGenericType()) {
|
||||
return StringUtils.escape(type.getObject().toLowerCase());
|
||||
@@ -194,23 +191,32 @@ public class NameGen {
|
||||
if (alias != null) {
|
||||
return alias;
|
||||
}
|
||||
ClassInfo extClsInfo = ClassInfo.fromType(mth.root(), type);
|
||||
String shortName = extClsInfo.getShortName();
|
||||
String vName = fromName(shortName);
|
||||
if (vName != null) {
|
||||
return vName;
|
||||
}
|
||||
if (shortName != null) {
|
||||
String lower = StringUtils.escape(shortName.toLowerCase());
|
||||
if (shortName.equals(lower)) {
|
||||
return lower + "Var";
|
||||
}
|
||||
return lower;
|
||||
}
|
||||
return makeNameForCheckedClass(ClassInfo.fromType(mth.root(), type));
|
||||
}
|
||||
return StringUtils.escape(type.toString());
|
||||
}
|
||||
|
||||
private String makeNameForCheckedClass(ClassInfo classInfo) {
|
||||
String shortName = classInfo.getAliasShortName();
|
||||
String vName = fromName(shortName);
|
||||
if (vName != null) {
|
||||
return vName;
|
||||
}
|
||||
String lower = StringUtils.escape(shortName.toLowerCase());
|
||||
if (shortName.equals(lower)) {
|
||||
return lower + "Var";
|
||||
}
|
||||
return lower;
|
||||
}
|
||||
|
||||
private String makeNameForClass(ClassInfo classInfo) {
|
||||
String alias = getAliasForObject(classInfo.getFullName());
|
||||
if (alias != null) {
|
||||
return alias;
|
||||
}
|
||||
return makeNameForCheckedClass(classInfo);
|
||||
}
|
||||
|
||||
private static String fromName(String name) {
|
||||
if (name == null || name.isEmpty()) {
|
||||
return null;
|
||||
@@ -241,7 +247,12 @@ public class NameGen {
|
||||
|
||||
case CONSTRUCTOR:
|
||||
ConstructorInsn co = (ConstructorInsn) insn;
|
||||
return makeNameForObject(co.getClassType().getType());
|
||||
MethodNode callMth = mth.root().getMethodUtils().resolveMethod(co);
|
||||
if (callMth != null && callMth.contains(AFlag.ANONYMOUS_CONSTRUCTOR)) {
|
||||
// don't use name of anonymous class
|
||||
return null;
|
||||
}
|
||||
return makeNameForClass(co.getClassType());
|
||||
|
||||
case ARRAY_LENGTH:
|
||||
return "length";
|
||||
@@ -267,11 +278,11 @@ public class NameGen {
|
||||
}
|
||||
|
||||
private String makeNameFromInvoke(MethodInfo callMth) {
|
||||
String name = callMth.getName();
|
||||
ArgType declType = callMth.getDeclClass().getType();
|
||||
String name = callMth.getAlias();
|
||||
ClassInfo declClass = callMth.getDeclClass();
|
||||
if ("getInstance".equals(name)) {
|
||||
// e.g. Cipher.getInstance
|
||||
return makeNameForType(declType);
|
||||
return makeNameForClass(declClass);
|
||||
}
|
||||
if (name.startsWith("get") || name.startsWith("set")) {
|
||||
return fromName(name.substring(3));
|
||||
@@ -280,9 +291,9 @@ public class NameGen {
|
||||
return "it";
|
||||
}
|
||||
if ("toString".equals(name)) {
|
||||
return makeNameForType(declType);
|
||||
return makeNameForClass(declClass);
|
||||
}
|
||||
if ("forName".equals(name) && declType.equals(ArgType.CLASS)) {
|
||||
if ("forName".equals(name) && declClass.getType().equals(ArgType.CLASS)) {
|
||||
return OBJ_ALIAS.get(Consts.CLASS_CLASS);
|
||||
}
|
||||
if (name.startsWith("to")) {
|
||||
|
||||
@@ -10,8 +10,8 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.CommentsLevel;
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.api.data.annotations.InsnCodeOffset;
|
||||
import jadx.api.data.annotations.VarDeclareRef;
|
||||
import jadx.api.metadata.annotations.InsnCodeOffset;
|
||||
import jadx.api.metadata.annotations.VarNode;
|
||||
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||
import jadx.api.plugins.input.data.attributes.JadxAttrType;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
@@ -26,6 +26,7 @@ import jadx.core.dex.instructions.args.CodeVar;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.NamedArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.IBlock;
|
||||
@@ -346,11 +347,11 @@ public class RegionGen extends InsnGen {
|
||||
if (arg == null) {
|
||||
code.add("unknown"); // throwing exception is too late at this point
|
||||
} else if (arg instanceof RegisterArg) {
|
||||
CodeVar codeVar = ((RegisterArg) arg).getSVar().getCodeVar();
|
||||
SSAVar ssaVar = ((RegisterArg) arg).getSVar();
|
||||
if (code.isMetadataSupported()) {
|
||||
code.attachDefinition(VarDeclareRef.get(mth, codeVar));
|
||||
code.attachDefinition(VarNode.get(mth, ssaVar));
|
||||
}
|
||||
code.add(mgen.getNameGen().assignArg(codeVar));
|
||||
code.add(mgen.getNameGen().assignArg(ssaVar.getCodeVar()));
|
||||
} else if (arg instanceof NamedArg) {
|
||||
code.add(mgen.getNameGen().assignNamedArg((NamedArg) arg));
|
||||
} else {
|
||||
|
||||
@@ -12,13 +12,13 @@ import com.google.gson.FieldNamingPolicy;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import jadx.api.CodePosition;
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.api.data.annotations.InsnCodeOffset;
|
||||
import jadx.api.impl.AnnotatedCodeWriter;
|
||||
import jadx.api.impl.SimpleCodeWriter;
|
||||
import jadx.api.metadata.ICodeMetadata;
|
||||
import jadx.api.metadata.annotations.InsnCodeOffset;
|
||||
import jadx.core.codegen.ClassGen;
|
||||
import jadx.core.codegen.MethodGen;
|
||||
import jadx.core.codegen.json.cls.JsonClass;
|
||||
@@ -180,24 +180,27 @@ public class JsonCodeGen {
|
||||
}
|
||||
|
||||
String[] lines = codeStr.split(ICodeWriter.NL);
|
||||
Map<Integer, Integer> lineMapping = code.getLineMapping();
|
||||
Map<CodePosition, Object> annotations = code.getAnnotations();
|
||||
Map<Integer, Integer> lineMapping = code.getCodeMetadata().getLineMapping();
|
||||
ICodeMetadata metadata = code.getCodeMetadata();
|
||||
long mthCodeOffset = mth.getMethodCodeOffset() + 16;
|
||||
|
||||
int linesCount = lines.length;
|
||||
List<JsonCodeLine> codeLines = new ArrayList<>(linesCount);
|
||||
int lineStartPos = 0;
|
||||
int newLineLen = ICodeWriter.NL.length();
|
||||
for (int i = 0; i < linesCount; i++) {
|
||||
String codeLine = lines[i];
|
||||
int line = i + 2;
|
||||
JsonCodeLine jsonCodeLine = new JsonCodeLine();
|
||||
jsonCodeLine.setCode(codeLine);
|
||||
jsonCodeLine.setSourceLine(lineMapping.get(line));
|
||||
Object obj = annotations.get(new CodePosition(line));
|
||||
Object obj = metadata.getAt(lineStartPos);
|
||||
if (obj instanceof InsnCodeOffset) {
|
||||
long offset = ((InsnCodeOffset) obj).getOffset();
|
||||
jsonCodeLine.setOffset("0x" + Long.toHexString(mthCodeOffset + offset * 2));
|
||||
}
|
||||
codeLines.add(jsonCodeLine);
|
||||
lineStartPos += codeLine.length() + newLineLen;
|
||||
}
|
||||
return codeLines;
|
||||
}
|
||||
|
||||
@@ -80,6 +80,7 @@ public enum AFlag {
|
||||
RERUN_SSA_TRANSFORM,
|
||||
|
||||
METHOD_CANDIDATE_FOR_INLINE,
|
||||
USE_LINES_HINTS, // source lines info in methods can be trusted
|
||||
|
||||
DISABLE_BLOCKS_LOCK,
|
||||
|
||||
|
||||
@@ -5,10 +5,6 @@ public interface ILineAttributeNode {
|
||||
|
||||
void setSourceLine(int sourceLine);
|
||||
|
||||
int getDecompiledLine();
|
||||
|
||||
void setDecompiledLine(int line);
|
||||
|
||||
int getDefPosition();
|
||||
|
||||
void setDefPosition(int pos);
|
||||
|
||||
@@ -7,21 +7,11 @@ public abstract class LineAttrNode extends AttrNode implements ILineAttributeNod
|
||||
|
||||
private int sourceLine;
|
||||
|
||||
private int decompiledLine;
|
||||
|
||||
// the position exactly where a node declared at in decompiled java code.
|
||||
/**
|
||||
* Position where a node declared at in decompiled code
|
||||
*/
|
||||
private int defPosition;
|
||||
|
||||
@Override
|
||||
public int getDefPosition() {
|
||||
return this.defPosition;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDefPosition(int defPosition) {
|
||||
this.defPosition = defPosition;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSourceLine() {
|
||||
return sourceLine;
|
||||
@@ -33,13 +23,13 @@ public abstract class LineAttrNode extends AttrNode implements ILineAttributeNod
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDecompiledLine() {
|
||||
return decompiledLine;
|
||||
public int getDefPosition() {
|
||||
return this.defPosition;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDecompiledLine(int decompiledLine) {
|
||||
this.decompiledLine = decompiledLine;
|
||||
public void setDefPosition(int defPosition) {
|
||||
this.defPosition = defPosition;
|
||||
}
|
||||
|
||||
public void addSourceLineFrom(LineAttrNode lineAttrNode) {
|
||||
@@ -50,6 +40,6 @@ public abstract class LineAttrNode extends AttrNode implements ILineAttributeNod
|
||||
|
||||
public void copyLines(LineAttrNode lineAttrNode) {
|
||||
setSourceLine(lineAttrNode.getSourceLine());
|
||||
setDecompiledLine(lineAttrNode.getDecompiledLine());
|
||||
setDefPosition(lineAttrNode.getDefPosition());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,12 +180,12 @@ public final class ClassInfo implements Comparable<ClassInfo> {
|
||||
return makeFullClsName(pkg, name, parentClass, false, true);
|
||||
}
|
||||
|
||||
private String makeAliasFullName() {
|
||||
public String makeAliasFullName() {
|
||||
return makeFullClsName(getAliasPkg(), getAliasShortName(), parentClass, true, false);
|
||||
}
|
||||
|
||||
private String makeAliasRawFullName() {
|
||||
return makeFullClsName(pkg, name, parentClass, true, true);
|
||||
public String makeAliasRawFullName() {
|
||||
return makeFullClsName(getAliasPkg(), getAliasShortName(), parentClass, true, true);
|
||||
}
|
||||
|
||||
public String getAliasFullPath() {
|
||||
|
||||
@@ -12,9 +12,9 @@ import org.jetbrains.annotations.TestOnly;
|
||||
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.dex.visitors.typeinference.TypeCompareEnum;
|
||||
import jadx.core.utils.ListUtils;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
@@ -874,28 +874,37 @@ public abstract class ArgType {
|
||||
}
|
||||
|
||||
public static ArgType tryToResolveClassAlias(RootNode root, ArgType type) {
|
||||
if (!type.isObject() || type.isGenericType()) {
|
||||
if (type.isGenericType()) {
|
||||
return type;
|
||||
}
|
||||
if (type.isArray()) {
|
||||
ArgType rootType = type.getArrayRootElement();
|
||||
ArgType aliasType = tryToResolveClassAlias(root, rootType);
|
||||
if (aliasType == rootType) {
|
||||
return type;
|
||||
}
|
||||
return ArgType.array(aliasType, type.getArrayDimension());
|
||||
}
|
||||
if (type.isObject()) {
|
||||
ArgType wildcardType = type.getWildcardType();
|
||||
if (wildcardType != null) {
|
||||
return new WildcardType(tryToResolveClassAlias(root, wildcardType), type.getWildcardBound());
|
||||
}
|
||||
ClassInfo clsInfo = ClassInfo.fromName(root, type.getObject());
|
||||
ArgType baseType = clsInfo.hasAlias() ? ArgType.object(clsInfo.getAliasFullName()) : type;
|
||||
if (!type.isGeneric()) {
|
||||
return baseType;
|
||||
}
|
||||
List<ArgType> genericTypes = type.getGenericTypes();
|
||||
if (genericTypes != null) {
|
||||
return new GenericObject(baseType.getObject(), tryToResolveClassAlias(root, genericTypes));
|
||||
}
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
ClassNode cls = root.resolveClass(type);
|
||||
if (cls == null) {
|
||||
return type;
|
||||
}
|
||||
ClassInfo clsInfo = cls.getClassInfo();
|
||||
if (!clsInfo.hasAlias()) {
|
||||
return type;
|
||||
}
|
||||
String aliasFullName = clsInfo.getAliasFullName();
|
||||
if (type.isGeneric()) {
|
||||
if (type instanceof GenericObject) {
|
||||
return new GenericObject(aliasFullName, type.getGenericTypes());
|
||||
}
|
||||
if (type instanceof WildcardType) {
|
||||
return new WildcardType(ArgType.object(aliasFullName), type.getWildcardBound());
|
||||
}
|
||||
}
|
||||
return ArgType.object(aliasFullName);
|
||||
public static List<ArgType> tryToResolveClassAlias(RootNode root, List<ArgType> types) {
|
||||
return ListUtils.map(types, t -> tryToResolveClassAlias(root, t));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -4,7 +4,7 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.api.data.annotations.VarRef;
|
||||
import jadx.api.metadata.annotations.VarNode;
|
||||
|
||||
public class CodeVar {
|
||||
private String name;
|
||||
@@ -15,7 +15,7 @@ public class CodeVar {
|
||||
private boolean isThis;
|
||||
private boolean isDeclared;
|
||||
|
||||
private VarRef cachedVarRef; // set and used at codegen stage
|
||||
private VarNode cachedVarNode; // set and used at codegen stage
|
||||
|
||||
public static CodeVar fromMthArg(RegisterArg mthArg, boolean linkRegister) {
|
||||
CodeVar var = new CodeVar();
|
||||
@@ -94,12 +94,12 @@ public class CodeVar {
|
||||
isDeclared = declared;
|
||||
}
|
||||
|
||||
public VarRef getCachedVarRef() {
|
||||
return cachedVarRef;
|
||||
public VarNode getCachedVarNode() {
|
||||
return cachedVarNode;
|
||||
}
|
||||
|
||||
public void setCachedVarRef(VarRef cachedVarRef) {
|
||||
this.cachedVarRef = cachedVarRef;
|
||||
public void setCachedVarNode(VarNode varNode) {
|
||||
this.cachedVarNode = varNode;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -170,6 +170,10 @@ public final class BlockNode extends AttrNode implements IBlock, Comparable<Bloc
|
||||
return contains(AFlag.RETURN);
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return instructions.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return startOffset;
|
||||
|
||||
@@ -374,12 +374,14 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
||||
String clsRawName = getRawName();
|
||||
if (searchInCache) {
|
||||
ICodeInfo code = codeCache.get(clsRawName);
|
||||
if (code != null && code != ICodeInfo.EMPTY) {
|
||||
if (code != ICodeInfo.EMPTY) {
|
||||
return code;
|
||||
}
|
||||
}
|
||||
ICodeInfo codeInfo = root.getProcessClasses().generateCode(this);
|
||||
codeCache.add(clsRawName, codeInfo);
|
||||
if (codeInfo != ICodeInfo.EMPTY) {
|
||||
codeCache.add(clsRawName, codeInfo);
|
||||
}
|
||||
return codeInfo;
|
||||
}
|
||||
|
||||
@@ -817,6 +819,11 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
||||
return clsData == null ? "synthetic" : clsData.getInputFileName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnnType getAnnType() {
|
||||
return AnnType.CLASS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return clsInfo.hashCode();
|
||||
|
||||
@@ -100,6 +100,11 @@ public class FieldNode extends NotificationAttrNode implements ICodeNode {
|
||||
return parentClass.root();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnnType getAnnType() {
|
||||
return AnnType.FIELD;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return fieldInfo.hashCode();
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package jadx.core.dex.nodes;
|
||||
|
||||
import jadx.api.metadata.ICodeNodeRef;
|
||||
import jadx.core.dex.attributes.IAttributeNode;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
|
||||
public interface ICodeNode extends IDexNode, IAttributeNode, IUsageInfoNode {
|
||||
public interface ICodeNode extends IDexNode, IAttributeNode, IUsageInfoNode, ICodeNodeRef {
|
||||
AccessInfo getAccessFlags();
|
||||
|
||||
void setAccessFlags(AccessInfo newAccessFlags);
|
||||
|
||||
@@ -461,6 +461,10 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
|
||||
return regsCount;
|
||||
}
|
||||
|
||||
public int getArgsStartReg() {
|
||||
return argsStartReg;
|
||||
}
|
||||
|
||||
public SSAVar makeNewSVar(@NotNull RegisterArg assignArg) {
|
||||
int regNum = assignArg.getRegNum();
|
||||
return makeNewSVar(regNum, getNextSVarVersion(regNum), assignArg);
|
||||
@@ -592,6 +596,11 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
|
||||
this.useIn = useIn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnnType getAnnType() {
|
||||
return AnnType.METHOD;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return mthInfo.hashCode();
|
||||
|
||||
@@ -115,21 +115,25 @@ public class RootNode {
|
||||
}
|
||||
|
||||
private void addDummyClass(IClassData classData, Exception exc) {
|
||||
String typeStr = classData.getType();
|
||||
String name = null;
|
||||
try {
|
||||
ClassInfo clsInfo = ClassInfo.fromName(this, typeStr);
|
||||
if (clsInfo != null) {
|
||||
name = clsInfo.getShortName();
|
||||
String typeStr = classData.getType();
|
||||
String name = null;
|
||||
try {
|
||||
ClassInfo clsInfo = ClassInfo.fromName(this, typeStr);
|
||||
if (clsInfo != null) {
|
||||
name = clsInfo.getShortName();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to get name for class with type {}", typeStr, e);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to get name for class with type {}", typeStr, e);
|
||||
if (name == null || name.isEmpty()) {
|
||||
name = "CLASS_" + typeStr;
|
||||
}
|
||||
ClassNode clsNode = ClassNode.addSyntheticClass(this, name, classData.getAccessFlags());
|
||||
ErrorsCounter.error(clsNode, "Load error", exc);
|
||||
} catch (Exception innerExc) {
|
||||
LOG.error("Failed to load class from file: {}", classData.getInputFileName(), exc);
|
||||
}
|
||||
if (name == null || name.isEmpty()) {
|
||||
name = "CLASS_" + typeStr;
|
||||
}
|
||||
ClassNode clsNode = ClassNode.addSyntheticClass(this, name, classData.getAccessFlags());
|
||||
ErrorsCounter.error(clsNode, "Load error", exc);
|
||||
}
|
||||
|
||||
private static void markDuplicatedClasses(List<ClassNode> classes) {
|
||||
@@ -265,6 +269,7 @@ public class RootNode {
|
||||
public void runPreDecompileStage() {
|
||||
boolean debugEnabled = LOG.isDebugEnabled();
|
||||
for (IDexTreeVisitor pass : preDecompilePasses) {
|
||||
Utils.checkThreadInterrupt();
|
||||
long start = debugEnabled ? System.currentTimeMillis() : 0;
|
||||
try {
|
||||
pass.init(this);
|
||||
|
||||
@@ -45,6 +45,15 @@ public class MethodUtils {
|
||||
return root.getClsp().getMethodDetails(callMth);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public MethodNode resolveMethod(BaseInvokeNode invokeNode) {
|
||||
IMethodDetails methodDetails = getMethodDetails(invokeNode);
|
||||
if (methodDetails instanceof MethodNode) {
|
||||
return ((MethodNode) methodDetails);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search methods with same name and args count in class hierarchy starting from {@code startCls}
|
||||
* Beware {@code startCls} can be different from {@code mthInfo.getDeclClass()}
|
||||
|
||||
@@ -278,6 +278,16 @@ public final class IfCondition extends AttrNode {
|
||||
return list;
|
||||
}
|
||||
|
||||
public int getSourceLine() {
|
||||
for (InsnNode insn : collectInsns()) {
|
||||
int line = insn.getSourceLine();
|
||||
if (line != 0) {
|
||||
return line;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public InsnNode getFirstInsn() {
|
||||
if (mode == Mode.COMPARE) {
|
||||
|
||||
@@ -14,9 +14,9 @@ import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.IContainer;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.utils.InsnUtils;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public class ExceptionHandler {
|
||||
|
||||
@@ -33,9 +33,14 @@ public class ExceptionHandler {
|
||||
|
||||
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;
|
||||
addCatchType(type);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -43,7 +48,7 @@ public class ExceptionHandler {
|
||||
*
|
||||
* @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 (catchTypes.contains(type)) {
|
||||
return false;
|
||||
@@ -51,14 +56,16 @@ public class ExceptionHandler {
|
||||
return catchTypes.add(type);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
public void addCatchTypes(Collection<ClassInfo> types) {
|
||||
public void addCatchTypes(MethodNode mth, Collection<ClassInfo> 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);
|
||||
if (excHandlerAttr != null) {
|
||||
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
|
||||
return null;
|
||||
}
|
||||
@@ -143,7 +143,7 @@ public class AttachTryCatchVisitor extends AbstractVisitor {
|
||||
} else {
|
||||
insn = insertNOP(insnByOffset, handlerOffset);
|
||||
}
|
||||
ExceptionHandler handler = new ExceptionHandler(handlerOffset, type);
|
||||
ExceptionHandler handler = ExceptionHandler.build(mth, handlerOffset, type);
|
||||
mth.addExceptionHandler(handler);
|
||||
insn.addAttr(new ExcHandlerAttr(handler));
|
||||
return handler;
|
||||
|
||||
@@ -362,8 +362,7 @@ public class ModVisitor extends AbstractVisitor {
|
||||
private static void removeCheckCast(MethodNode mth, BlockNode block, int i, IndexInsnNode insn) {
|
||||
InsnArg castArg = insn.getArg(0);
|
||||
ArgType castType = (ArgType) insn.getIndex();
|
||||
if (!ArgType.isCastNeeded(mth.root(), castArg.getType(), castType)
|
||||
|| isCastDuplicate(insn)) {
|
||||
if (!ArgType.isCastNeeded(mth.root(), castArg.getType(), castType)) {
|
||||
RegisterArg result = insn.getResult();
|
||||
result.setType(castArg.getType());
|
||||
|
||||
@@ -371,10 +370,19 @@ public class ModVisitor extends AbstractVisitor {
|
||||
move.setResult(result);
|
||||
move.addArg(castArg);
|
||||
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);
|
||||
if (arg.isRegister()) {
|
||||
SSAVar sVar = ((RegisterArg) arg).getSVar();
|
||||
@@ -382,11 +390,13 @@ public class ModVisitor extends AbstractVisitor {
|
||||
InsnNode assignInsn = sVar.getAssign().getParentInsn();
|
||||
if (assignInsn != null && assignInsn.getType() == InsnType.CHECK_CAST) {
|
||||
ArgType assignCastType = (ArgType) ((IndexInsnNode) assignInsn).getIndex();
|
||||
return assignCastType.equals(castInsn.getIndex());
|
||||
if (assignCastType.equals(castInsn.getIndex())) {
|
||||
return assignInsn;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -61,7 +61,9 @@ public class MoveInlineVisitor extends AbstractVisitor {
|
||||
}
|
||||
SSAVar ssaVar = resultArg.getSVar();
|
||||
if (ssaVar.isUsedInPhi()) {
|
||||
return deleteMove(mth, move);
|
||||
return false;
|
||||
// TODO: review conditions of 'up' move inline (test TestMoveInline)
|
||||
// return deleteMove(mth, move);
|
||||
}
|
||||
RegDebugInfoAttr debugInfo = moveArg.get(AType.REG_DEBUG_INFO);
|
||||
for (RegisterArg useArg : ssaVar.getUseList()) {
|
||||
|
||||
@@ -549,7 +549,7 @@ public class BlockExceptionHandler {
|
||||
if (handler == resultHandler) {
|
||||
return false;
|
||||
}
|
||||
resultHandler.addCatchTypes(handler.getCatchTypes());
|
||||
resultHandler.addCatchTypes(mth, handler.getCatchTypes());
|
||||
handler.markForRemove();
|
||||
return true;
|
||||
});
|
||||
|
||||
+18
-1
@@ -1,12 +1,15 @@
|
||||
package jadx.core.dex.visitors.debuginfo;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import jadx.api.plugins.input.data.IDebugInfo;
|
||||
import jadx.api.plugins.input.data.ILocalVar;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.nodes.LocalVarsDebugInfoAttr;
|
||||
import jadx.core.dex.attributes.nodes.RegDebugInfoAttr;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
@@ -17,6 +20,7 @@ import jadx.core.dex.visitors.AbstractVisitor;
|
||||
import jadx.core.dex.visitors.JadxVisitor;
|
||||
import jadx.core.dex.visitors.blocks.BlockSplitter;
|
||||
import jadx.core.dex.visitors.ssa.SSATransform;
|
||||
import jadx.core.utils.ListUtils;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
|
||||
@JadxVisitor(
|
||||
@@ -52,17 +56,30 @@ public class DebugInfoAttachVisitor extends AbstractVisitor {
|
||||
if (lineMapping.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Map<Integer, Integer> linesStat = new HashMap<>(); // count repeating lines
|
||||
for (Map.Entry<Integer, Integer> entry : lineMapping.entrySet()) {
|
||||
try {
|
||||
Integer offset = entry.getKey();
|
||||
InsnNode insn = insnArr[offset];
|
||||
if (insn != null) {
|
||||
insn.setSourceLine(entry.getValue());
|
||||
int line = entry.getValue();
|
||||
insn.setSourceLine(line);
|
||||
if (insn.getType() != InsnType.NOP) {
|
||||
linesStat.merge(line, 1, (v, one) -> v + 1);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
mth.addWarnComment("Error attach source line", e);
|
||||
}
|
||||
}
|
||||
// 3 here is allowed maximum for lines repeat,
|
||||
// can occur in indexed 'for' loops (3 instructions with same line)
|
||||
List<Map.Entry<Integer, Integer>> repeatingLines = ListUtils.filter(linesStat.entrySet(), p -> p.getValue() > 3);
|
||||
if (repeatingLines.isEmpty()) {
|
||||
mth.add(AFlag.USE_LINES_HINTS);
|
||||
} else {
|
||||
mth.addDebugComment("Don't trust debug lines info. Repeating lines: " + repeatingLines);
|
||||
}
|
||||
}
|
||||
|
||||
private void attachDebugInfo(MethodNode mth, List<ILocalVar> localVars, InsnNode[] insnArr) {
|
||||
|
||||
@@ -6,9 +6,12 @@ import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.trycatch.ExceptionHandler;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
public class FinallyExtractInfo {
|
||||
private final MethodNode mth;
|
||||
private final ExceptionHandler finallyHandler;
|
||||
private final List<BlockNode> allHandlerBlocks;
|
||||
private final List<InsnsSlice> duplicateSlices = new ArrayList<>();
|
||||
@@ -16,12 +19,17 @@ public class FinallyExtractInfo {
|
||||
private final InsnsSlice finallyInsnsSlice = new InsnsSlice();
|
||||
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.startBlock = startBlock;
|
||||
this.allHandlerBlocks = allHandlerBlocks;
|
||||
}
|
||||
|
||||
public MethodNode getMth() {
|
||||
return mth;
|
||||
}
|
||||
|
||||
public ExceptionHandler getFinallyHandler() {
|
||||
return finallyHandler;
|
||||
}
|
||||
@@ -45,4 +53,12 @@ public class FinallyExtractInfo {
|
||||
public BlockNode getStartBlock() {
|
||||
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.LoggerFactory;
|
||||
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
@@ -38,6 +39,7 @@ import jadx.core.utils.Utils;
|
||||
)
|
||||
public class MarkFinallyVisitor extends AbstractVisitor {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MarkFinallyVisitor.class);
|
||||
// private static final Logger LOG = LoggerFactory.getLogger(MarkFinallyVisitor.class);
|
||||
|
||||
@Override
|
||||
public void visit(MethodNode mth) {
|
||||
@@ -60,7 +62,7 @@ public class MarkFinallyVisitor extends AbstractVisitor {
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Undo finally extract visitor, mth: {}", mth, e);
|
||||
mth.addWarnComment("Undo finally extract visitor", e);
|
||||
undoFinallyVisitor(mth);
|
||||
}
|
||||
}
|
||||
@@ -100,20 +102,23 @@ public class MarkFinallyVisitor extends AbstractVisitor {
|
||||
List<BlockNode> handlerBlocks =
|
||||
new ArrayList<>(BlockUtils.collectBlocksDominatedByWithExcHandlers(mth, handlerBlock, handlerBlock));
|
||||
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)) {
|
||||
// remove empty catch
|
||||
allHandler.getTryBlock().removeHandler(allHandler);
|
||||
return true;
|
||||
}
|
||||
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();
|
||||
List<ExceptionHandler> handlers;
|
||||
if (hasInnerBlocks) {
|
||||
// collect handlers from this and all inner blocks (intentionally not using recursive collect for
|
||||
// now)
|
||||
// collect handlers from this and all inner blocks
|
||||
// (intentionally not using recursive collect for now)
|
||||
handlers = new ArrayList<>(tryBlock.getHandlers());
|
||||
for (TryCatchBlockAttr innerTryBlock : tryBlock.getInnerTryBlocks()) {
|
||||
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;
|
||||
int duplicatesCount = extractInfo.getDuplicateSlices().size();
|
||||
boolean fullTryBlock = duplicatesCount == (handlers.size() - 1);
|
||||
if (fullTryBlock) {
|
||||
if (duplicatesCount == (handlers.size() - 1)) {
|
||||
// all collected handlers have duplicate block
|
||||
mergeInnerTryBlocks = hasInnerBlocks;
|
||||
} else {
|
||||
@@ -170,15 +177,24 @@ public class MarkFinallyVisitor extends AbstractVisitor {
|
||||
if (upPath.size() < handlerBlocks.size()) {
|
||||
continue;
|
||||
}
|
||||
if (Consts.DEBUG_FINALLY) {
|
||||
LOG.debug("Checking dup path starts: {} from {}", upPath, pred);
|
||||
}
|
||||
for (BlockNode block : upPath) {
|
||||
if (searchDuplicateInsns(block, extractInfo)) {
|
||||
found = true;
|
||||
if (Consts.DEBUG_FINALLY) {
|
||||
LOG.debug("Found dup in: {} from {}", block, pred);
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
extractInfo.getFinallyInsnsSlice().resetIncomplete();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Consts.DEBUG_FINALLY) {
|
||||
LOG.debug("Result slices:\n{}", extractInfo);
|
||||
}
|
||||
if (!found) {
|
||||
return false;
|
||||
}
|
||||
@@ -204,6 +220,28 @@ public class MarkFinallyVisitor extends AbstractVisitor {
|
||||
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) {
|
||||
Stream<BlockNode> preds = bottom.getPredecessors().stream().filter(b -> b != bottomFinallyBlock);
|
||||
if (bottom == mth.getExitBlock()) {
|
||||
@@ -219,9 +257,8 @@ public class MarkFinallyVisitor extends AbstractVisitor {
|
||||
for (InsnsSlice dupSlice : extractInfo.getDuplicateSlices()) {
|
||||
List<InsnNode> dupInsnsList = dupSlice.getInsnsList();
|
||||
if (dupInsnsList.size() != finallyInsnsList.size()) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Incorrect finally slice size: {}, expected: {}", dupSlice, finallySlice);
|
||||
}
|
||||
extractInfo.getMth().addDebugComment(
|
||||
"Incorrect finally slice size: " + dupSlice + ", expected: " + finallySlice);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -231,9 +268,8 @@ public class MarkFinallyVisitor extends AbstractVisitor {
|
||||
List<InsnNode> insnsList = dupSlice.getInsnsList();
|
||||
InsnNode dupInsn = insnsList.get(i);
|
||||
if (finallyInsn.getType() != dupInsn.getType()) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Incorrect finally slice insn: {}, expected: {}", dupInsn, finallyInsn);
|
||||
}
|
||||
extractInfo.getMth().addDebugComment(
|
||||
"Incorrect finally slice insn: " + dupInsn + ", expected: " + finallyInsn);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -342,24 +378,29 @@ public class MarkFinallyVisitor extends AbstractVisitor {
|
||||
private static InsnsSlice isStartBlock(BlockNode dupBlock, BlockNode finallyBlock, FinallyExtractInfo extractInfo) {
|
||||
List<InsnNode> dupInsns = dupBlock.getInstructions();
|
||||
List<InsnNode> finallyInsns = finallyBlock.getInstructions();
|
||||
if (dupInsns.size() < finallyInsns.size()) {
|
||||
int dupSize = dupInsns.size();
|
||||
int finSize = finallyInsns.size();
|
||||
if (dupSize < finSize) {
|
||||
return null;
|
||||
}
|
||||
int startPos = dupInsns.size() - finallyInsns.size();
|
||||
int startPos;
|
||||
int endPos = 0;
|
||||
// fast check from end of block
|
||||
if (!checkInsns(dupInsns, finallyInsns, startPos)) {
|
||||
// check from block start
|
||||
if (checkInsns(dupInsns, finallyInsns, 0)) {
|
||||
startPos = 0;
|
||||
endPos = finallyInsns.size();
|
||||
} else {
|
||||
if (dupSize == finSize) {
|
||||
if (!checkInsns(dupInsns, finallyInsns, 0)) {
|
||||
return null;
|
||||
}
|
||||
startPos = 0;
|
||||
} else {
|
||||
// dupSize > finSize
|
||||
startPos = dupSize - finSize;
|
||||
// fast check from end of block
|
||||
if (!checkInsns(dupInsns, finallyInsns, startPos)) {
|
||||
// search start insn
|
||||
boolean found = false;
|
||||
for (int i = 1; i < startPos; i++) {
|
||||
if (checkInsns(dupInsns, finallyInsns, i)) {
|
||||
startPos = i;
|
||||
endPos = finallyInsns.size() + i;
|
||||
endPos = finSize + i;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
@@ -379,7 +420,7 @@ public class MarkFinallyVisitor extends AbstractVisitor {
|
||||
// both slices completed
|
||||
complete = true;
|
||||
} else {
|
||||
endIndex = dupInsns.size();
|
||||
endIndex = dupSize;
|
||||
complete = false;
|
||||
}
|
||||
|
||||
@@ -393,9 +434,8 @@ public class MarkFinallyVisitor extends AbstractVisitor {
|
||||
if (finallySlice.isComplete()) {
|
||||
// compare slices
|
||||
if (finallySlice.getInsnsList().size() != slice.getInsnsList().size()) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Another duplicated slice has different insns count: {}, finally: {}", slice, finallySlice);
|
||||
}
|
||||
extractInfo.getMth().addDebugComment(
|
||||
"Another duplicated slice has different insns count: " + slice + ", finally: " + finallySlice);
|
||||
return null;
|
||||
}
|
||||
// TODO: add additional slices checks
|
||||
@@ -522,7 +562,7 @@ public class MarkFinallyVisitor extends AbstractVisitor {
|
||||
DepthTraversal.visit(visitor, mth);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("Undo finally extract failed, mth: {}", mth, e);
|
||||
mth.addError("Undo finally extract failed", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,15 +38,105 @@ public class IfRegionVisitor extends AbstractVisitor {
|
||||
public boolean enterRegion(MethodNode mth, IRegion region) {
|
||||
if (region instanceof IfRegion) {
|
||||
IfRegion ifRegion = (IfRegion) region;
|
||||
simplifyIfCondition(ifRegion);
|
||||
moveReturnToThenBlock(mth, ifRegion);
|
||||
moveBreakToThenBlock(ifRegion);
|
||||
markElseIfChains(ifRegion);
|
||||
orderBranches(mth, ifRegion);
|
||||
markElseIfChains(mth, ifRegion);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnnecessaryReturnStatement")
|
||||
private static void orderBranches(MethodNode mth, IfRegion ifRegion) {
|
||||
if (RegionUtils.isEmpty(ifRegion.getElseRegion())) {
|
||||
return;
|
||||
}
|
||||
if (RegionUtils.isEmpty(ifRegion.getThenRegion())) {
|
||||
invertIfRegion(ifRegion);
|
||||
return;
|
||||
}
|
||||
if (mth.contains(AFlag.USE_LINES_HINTS)) {
|
||||
int thenLine = RegionUtils.getFirstSourceLine(ifRegion.getThenRegion());
|
||||
int elseLine = RegionUtils.getFirstSourceLine(ifRegion.getElseRegion());
|
||||
if (thenLine != 0 && elseLine != 0) {
|
||||
if (thenLine > elseLine) {
|
||||
invertIfRegion(ifRegion);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (ifRegion.simplifyCondition()) {
|
||||
IfCondition condition = ifRegion.getCondition();
|
||||
if (condition != null && condition.getMode() == Mode.NOT) {
|
||||
invertIfRegion(ifRegion);
|
||||
}
|
||||
}
|
||||
int thenSize = insnsCount(ifRegion.getThenRegion());
|
||||
int elseSize = insnsCount(ifRegion.getElseRegion());
|
||||
if (isSimpleExitBlock(mth, ifRegion.getElseRegion())) {
|
||||
if (isSimpleExitBlock(mth, ifRegion.getThenRegion())) {
|
||||
if (elseSize < thenSize) {
|
||||
invertIfRegion(ifRegion);
|
||||
return;
|
||||
}
|
||||
}
|
||||
boolean lastRegion = ifRegion == RegionUtils.getLastRegion(mth.getRegion());
|
||||
if (elseSize == 1 && lastRegion && mth.isVoidReturn()) {
|
||||
// single return at method end will be removed later
|
||||
return;
|
||||
}
|
||||
if (!lastRegion) {
|
||||
invertIfRegion(ifRegion);
|
||||
}
|
||||
return;
|
||||
}
|
||||
boolean thenExit = RegionUtils.hasExitBlock(ifRegion.getThenRegion());
|
||||
boolean elseExit = RegionUtils.hasExitBlock(ifRegion.getElseRegion());
|
||||
if (elseExit && (!thenExit || elseSize < thenSize)) {
|
||||
invertIfRegion(ifRegion);
|
||||
return;
|
||||
}
|
||||
// move 'if' from 'then' branch to make 'else if' chain
|
||||
if (isIfRegion(ifRegion.getThenRegion())
|
||||
&& !isIfRegion(ifRegion.getElseRegion())
|
||||
&& !thenExit) {
|
||||
invertIfRegion(ifRegion);
|
||||
return;
|
||||
}
|
||||
// move 'break' into 'then' branch
|
||||
if (RegionUtils.hasBreakInsn(ifRegion.getElseRegion())) {
|
||||
invertIfRegion(ifRegion);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isIfRegion(IContainer container) {
|
||||
if (container instanceof IfRegion) {
|
||||
return true;
|
||||
}
|
||||
if (container instanceof IRegion) {
|
||||
List<IContainer> subBlocks = ((IRegion) container).getSubBlocks();
|
||||
return subBlocks.size() == 1 && subBlocks.get(0) instanceof IfRegion;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark if-else-if chains
|
||||
*/
|
||||
private static void markElseIfChains(MethodNode mth, IfRegion ifRegion) {
|
||||
if (isSimpleExitBlock(mth, ifRegion.getThenRegion())) {
|
||||
return;
|
||||
}
|
||||
IContainer elsRegion = ifRegion.getElseRegion();
|
||||
if (elsRegion instanceof Region) {
|
||||
List<IContainer> subBlocks = ((Region) elsRegion).getSubBlocks();
|
||||
if (subBlocks.size() == 1 && subBlocks.get(0) instanceof IfRegion) {
|
||||
subBlocks.get(0).add(AFlag.ELSE_IF_CHAIN);
|
||||
elsRegion.add(AFlag.ELSE_IF_CHAIN);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class RemoveRedundantElseVisitor implements IRegionIterativeVisitor {
|
||||
@Override
|
||||
public boolean visitRegion(MethodNode mth, IRegion region) {
|
||||
@@ -57,76 +147,6 @@ public class IfRegionVisitor extends AbstractVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
private static void simplifyIfCondition(IfRegion ifRegion) {
|
||||
if (ifRegion.simplifyCondition()) {
|
||||
IfCondition condition = ifRegion.getCondition();
|
||||
if (condition.getMode() == Mode.NOT) {
|
||||
invertIfRegion(ifRegion);
|
||||
}
|
||||
}
|
||||
IContainer elseRegion = ifRegion.getElseRegion();
|
||||
if (elseRegion == null || RegionUtils.isEmpty(elseRegion)) {
|
||||
return;
|
||||
}
|
||||
boolean thenIsEmpty = RegionUtils.isEmpty(ifRegion.getThenRegion());
|
||||
if (thenIsEmpty || hasSimpleReturnBlock(ifRegion.getThenRegion())) {
|
||||
invertIfRegion(ifRegion);
|
||||
}
|
||||
|
||||
if (!thenIsEmpty) {
|
||||
// move 'if' from then to make 'else if' chain
|
||||
if (isIfRegion(ifRegion.getThenRegion())
|
||||
&& !isIfRegion(elseRegion)) {
|
||||
invertIfRegion(ifRegion);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isIfRegion(IContainer container) {
|
||||
if (container instanceof IfRegion) {
|
||||
return true;
|
||||
}
|
||||
if (container instanceof IRegion) {
|
||||
List<IContainer> subBlocks = ((IRegion) container).getSubBlocks();
|
||||
if (subBlocks.size() == 1 && subBlocks.get(0) instanceof IfRegion) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void moveReturnToThenBlock(MethodNode mth, IfRegion ifRegion) {
|
||||
if (!mth.isVoidReturn()
|
||||
&& hasSimpleReturnBlock(ifRegion.getElseRegion())
|
||||
/* && insnsCount(ifRegion.getThenRegion()) < 2 */) {
|
||||
invertIfRegion(ifRegion);
|
||||
}
|
||||
}
|
||||
|
||||
private static void moveBreakToThenBlock(IfRegion ifRegion) {
|
||||
if (ifRegion.getElseRegion() != null
|
||||
&& RegionUtils.hasBreakInsn(ifRegion.getElseRegion())) {
|
||||
invertIfRegion(ifRegion);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark if-else-if chains
|
||||
*/
|
||||
private static void markElseIfChains(IfRegion ifRegion) {
|
||||
if (hasSimpleReturnBlock(ifRegion.getThenRegion())) {
|
||||
return;
|
||||
}
|
||||
IContainer elsRegion = ifRegion.getElseRegion();
|
||||
if (elsRegion instanceof Region) {
|
||||
List<IContainer> subBlocks = ((Region) elsRegion).getSubBlocks();
|
||||
if (subBlocks.size() == 1 && subBlocks.get(0) instanceof IfRegion) {
|
||||
subBlocks.get(0).add(AFlag.ELSE_IF_CHAIN);
|
||||
elsRegion.add(AFlag.ELSE_IF_CHAIN);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean removeRedundantElseBlock(MethodNode mth, IfRegion ifRegion) {
|
||||
if (ifRegion.getElseRegion() == null
|
||||
|| ifRegion.contains(AFlag.ELSE_IF_CHAIN)
|
||||
@@ -162,16 +182,16 @@ public class IfRegionVisitor extends AbstractVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean hasSimpleReturnBlock(IContainer region) {
|
||||
if (region == null) {
|
||||
private static boolean isSimpleExitBlock(MethodNode mth, IContainer container) {
|
||||
if (container == null) {
|
||||
return false;
|
||||
}
|
||||
if (region.contains(AFlag.RETURN)) {
|
||||
if (container.contains(AFlag.RETURN) || RegionUtils.isExitBlock(mth, container)) {
|
||||
return true;
|
||||
}
|
||||
if (region instanceof IRegion) {
|
||||
List<IContainer> subBlocks = ((IRegion) region).getSubBlocks();
|
||||
return subBlocks.size() == 1 && subBlocks.get(0).contains(AFlag.RETURN);
|
||||
if (container instanceof IRegion) {
|
||||
List<IContainer> subBlocks = ((IRegion) container).getSubBlocks();
|
||||
return subBlocks.size() == 1 && RegionUtils.isExitBlock(mth, subBlocks.get(0));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -93,7 +93,8 @@ public class TernaryMod extends AbstractRegionVisitor implements IRegionIterativ
|
||||
InsnNode thenInsn = tb.getInstructions().get(0);
|
||||
InsnNode elseInsn = eb.getInstructions().get(0);
|
||||
|
||||
if (thenInsn.getSourceLine() != elseInsn.getSourceLine()) {
|
||||
if (mth.contains(AFlag.USE_LINES_HINTS)
|
||||
&& thenInsn.getSourceLine() != elseInsn.getSourceLine()) {
|
||||
if (thenInsn.getSourceLine() != 0 && elseInsn.getSourceLine() != 0) {
|
||||
// sometimes source lines incorrect
|
||||
if (!checkLineStats(thenInsn, elseInsn)) {
|
||||
@@ -134,6 +135,8 @@ public class TernaryMod extends AbstractRegionVisitor implements IRegionIterativ
|
||||
InsnArg thenArg = InsnArg.wrapInsnIntoArg(thenInsn);
|
||||
InsnArg elseArg = InsnArg.wrapInsnIntoArg(elseInsn);
|
||||
TernaryInsn ternInsn = new TernaryInsn(ifRegion.getCondition(), resArg, thenArg, elseArg);
|
||||
int branchLine = Math.max(thenInsn.getSourceLine(), elseInsn.getSourceLine());
|
||||
ternInsn.setSourceLine(Math.max(ifRegion.getSourceLine(), branchLine));
|
||||
|
||||
InsnRemover.unbindResult(mth, elseInsn);
|
||||
|
||||
|
||||
@@ -182,6 +182,16 @@ public class BlockUtils {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static int getFirstSourceLine(IBlock block) {
|
||||
for (InsnNode insn : block.getInstructions()) {
|
||||
int line = insn.getSourceLine();
|
||||
if (line != 0) {
|
||||
return line;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static InsnNode getFirstInsn(@Nullable IBlock block) {
|
||||
if (block == null) {
|
||||
@@ -207,11 +217,22 @@ public class BlockUtils {
|
||||
}
|
||||
|
||||
public static boolean isExitBlock(MethodNode mth, BlockNode block) {
|
||||
BlockNode exitBlock = mth.getExitBlock();
|
||||
if (block == exitBlock) {
|
||||
if (block == mth.getExitBlock()) {
|
||||
return true;
|
||||
}
|
||||
return exitBlock.getPredecessors().contains(block);
|
||||
return isExitBlock(block);
|
||||
}
|
||||
|
||||
public static boolean isExitBlock(BlockNode block) {
|
||||
List<BlockNode> successors = block.getSuccessors();
|
||||
if (successors.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
if (successors.size() == 1) {
|
||||
BlockNode next = successors.get(0);
|
||||
return next.getSuccessors().isEmpty();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean containsExitInsn(IBlock block) {
|
||||
|
||||
@@ -4,9 +4,9 @@ import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.CodePosition;
|
||||
import jadx.api.CommentsLevel;
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.api.metadata.ICodeAnnotation;
|
||||
import jadx.api.plugins.input.data.attributes.JadxAttrType;
|
||||
import jadx.api.plugins.input.data.attributes.types.SourceFileAttr;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
@@ -95,7 +95,7 @@ public class CodeGenUtils {
|
||||
private static void addMultiLineComment(ICodeWriter code, List<String> comments) {
|
||||
boolean first = true;
|
||||
String indent = "";
|
||||
Object lineAnn = null;
|
||||
ICodeAnnotation lineAnn = null;
|
||||
for (String comment : comments) {
|
||||
for (String line : comment.split("\n")) {
|
||||
if (first) {
|
||||
@@ -104,7 +104,7 @@ public class CodeGenUtils {
|
||||
int startLinePos = buf.lastIndexOf(ICodeWriter.NL) + 1;
|
||||
indent = Utils.strRepeat(" ", buf.length() - startLinePos);
|
||||
if (code.isMetadataSupported()) {
|
||||
lineAnn = code.getRawAnnotations().get(new CodePosition(code.getLine()));
|
||||
lineAnn = code.getRawAnnotations().get(startLinePos);
|
||||
}
|
||||
} else {
|
||||
code.newLine().add(indent);
|
||||
|
||||
@@ -13,9 +13,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.IDecompileScheduler;
|
||||
import jadx.api.JadxDecompiler;
|
||||
import jadx.api.JavaClass;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
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 boolean DEBUG_BATCHES = false;
|
||||
|
||||
private final JadxDecompiler decompiler;
|
||||
|
||||
public DecompilerScheduler(JadxDecompiler decompiler) {
|
||||
this.decompiler = decompiler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<List<JavaClass>> buildBatches(List<JavaClass> classes) {
|
||||
try {
|
||||
long start = System.currentTimeMillis();
|
||||
List<List<ClassNode>> batches = internalBatches(Utils.collectionMap(classes, JavaClass::getClassNode));
|
||||
List<List<JavaClass>> result = Utils.collectionMap(batches, l -> Utils.collectionMapNoNull(l, decompiler::getJavaClassByNode));
|
||||
List<List<JavaClass>> result = internalBatches(classes);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
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.
|
||||
* 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);
|
||||
Set<ClassNode> added = new HashSet<>(classes.size());
|
||||
Comparator<ClassNode> cmpDepSize = Comparator.comparingInt(ClassNode::getTotalDepsCount);
|
||||
List<List<ClassNode>> result = new ArrayList<>();
|
||||
List<ClassNode> mergedBatch = new ArrayList<>(MERGED_BATCH_SIZE);
|
||||
Set<JavaClass> added = new HashSet<>(classes.size());
|
||||
Comparator<JavaClass> cmpDepSize = Comparator.comparingInt(JavaClass::getTotalDepsCount);
|
||||
List<List<JavaClass>> result = new ArrayList<>();
|
||||
List<JavaClass> mergedBatch = new ArrayList<>(MERGED_BATCH_SIZE);
|
||||
for (DepInfo depInfo : deps) {
|
||||
ClassNode cls = depInfo.getCls();
|
||||
JavaClass cls = depInfo.getCls();
|
||||
if (!added.add(cls)) {
|
||||
continue;
|
||||
}
|
||||
@@ -73,9 +64,9 @@ public class DecompilerScheduler implements IDecompileScheduler {
|
||||
mergedBatch = new ArrayList<>(MERGED_BATCH_SIZE);
|
||||
}
|
||||
} else {
|
||||
List<ClassNode> batch = new ArrayList<>(depsSize + 1);
|
||||
for (ClassNode dep : cls.getDependencies()) {
|
||||
ClassNode topDep = dep.getTopParentClass();
|
||||
List<JavaClass> batch = new ArrayList<>(depsSize + 1);
|
||||
for (JavaClass dep : cls.getDependencies()) {
|
||||
JavaClass topDep = dep.getTopParentClass();
|
||||
if (!added.contains(topDep)) {
|
||||
batch.add(topDep);
|
||||
added.add(topDep);
|
||||
@@ -95,11 +86,11 @@ public class DecompilerScheduler implements IDecompileScheduler {
|
||||
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());
|
||||
for (ClassNode cls : classes) {
|
||||
for (JavaClass cls : classes) {
|
||||
int count = 0;
|
||||
for (ClassNode dep : cls.getDependencies()) {
|
||||
for (JavaClass dep : cls.getDependencies()) {
|
||||
count += 1 + dep.getTotalDepsCount();
|
||||
}
|
||||
deps.add(new DepInfo(cls, count));
|
||||
@@ -109,15 +100,15 @@ public class DecompilerScheduler implements IDecompileScheduler {
|
||||
}
|
||||
|
||||
private static final class DepInfo implements Comparable<DepInfo> {
|
||||
private final ClassNode cls;
|
||||
private final JavaClass cls;
|
||||
private final int depsCount;
|
||||
|
||||
private DepInfo(ClassNode cls, int depsCount) {
|
||||
private DepInfo(JavaClass cls, int depsCount) {
|
||||
this.cls = cls;
|
||||
this.depsCount = depsCount;
|
||||
}
|
||||
|
||||
public ClassNode getCls() {
|
||||
public JavaClass getCls() {
|
||||
return cls;
|
||||
}
|
||||
|
||||
@@ -129,7 +120,7 @@ public class DecompilerScheduler implements IDecompileScheduler {
|
||||
public int compareTo(@NotNull DecompilerScheduler.DepInfo o) {
|
||||
int deps = Integer.compare(depsCount, o.depsCount);
|
||||
if (deps == 0) {
|
||||
return cls.compareTo(o.cls);
|
||||
return cls.getClassNode().compareTo(o.cls.getClassNode());
|
||||
}
|
||||
return deps;
|
||||
}
|
||||
@@ -147,9 +138,9 @@ public class DecompilerScheduler implements IDecompileScheduler {
|
||||
.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);
|
||||
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);
|
||||
LOG.info("Batches stats:"
|
||||
+ "\n input classes: " + classes.size()
|
||||
|
||||
@@ -16,6 +16,7 @@ import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.IBlock;
|
||||
import jadx.core.dex.nodes.IBranchRegion;
|
||||
import jadx.core.dex.nodes.IConditionRegion;
|
||||
import jadx.core.dex.nodes.IContainer;
|
||||
import jadx.core.dex.nodes.IRegion;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
@@ -52,6 +53,7 @@ public class RegionUtils {
|
||||
throw new JadxRuntimeException(unknownContainerType(container));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static InsnNode getFirstInsn(IContainer container) {
|
||||
if (container instanceof IBlock) {
|
||||
IBlock block = (IBlock) container;
|
||||
@@ -74,6 +76,37 @@ public class RegionUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static int getFirstSourceLine(IContainer container) {
|
||||
if (container instanceof IBlock) {
|
||||
return BlockUtils.getFirstSourceLine((IBlock) container);
|
||||
}
|
||||
if (container instanceof IConditionRegion) {
|
||||
return ((IConditionRegion) container).getConditionSourceLine();
|
||||
}
|
||||
if (container instanceof IBranchRegion) {
|
||||
IBranchRegion branchRegion = (IBranchRegion) container;
|
||||
return getFirstSourceLine(branchRegion.getBranches());
|
||||
}
|
||||
if (container instanceof IRegion) {
|
||||
IRegion region = (IRegion) container;
|
||||
return getFirstSourceLine(region.getSubBlocks());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static int getFirstSourceLine(List<IContainer> containers) {
|
||||
if (containers.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
for (IContainer container : containers) {
|
||||
int line = getFirstSourceLine(container);
|
||||
if (line != 0) {
|
||||
return line;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static InsnNode getLastInsn(IContainer container) {
|
||||
if (container instanceof IBlock) {
|
||||
IBlock block = (IBlock) container;
|
||||
@@ -112,31 +145,58 @@ public class RegionUtils {
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static IContainer getLastRegion(@Nullable IContainer container) {
|
||||
if (container == null) {
|
||||
return null;
|
||||
}
|
||||
if (container instanceof IBlock || container instanceof IBranchRegion) {
|
||||
return container;
|
||||
}
|
||||
if (container instanceof IRegion) {
|
||||
return getLastRegion(Utils.last(((IRegion) container).getSubBlocks()));
|
||||
}
|
||||
throw new JadxRuntimeException(unknownContainerType(container));
|
||||
}
|
||||
|
||||
public static boolean isExitBlock(MethodNode mth, IContainer container) {
|
||||
if (container instanceof BlockNode) {
|
||||
return BlockUtils.isExitBlock(mth, (BlockNode) container);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if last block in region has no successors or jump out insn (return or break)
|
||||
*/
|
||||
public static boolean hasExitBlock(IContainer container) {
|
||||
if (container == null) {
|
||||
return false;
|
||||
}
|
||||
return hasExitBlock(container, container);
|
||||
}
|
||||
|
||||
private static boolean hasExitBlock(IContainer rootContainer, IContainer container) {
|
||||
if (container instanceof BlockNode) {
|
||||
BlockNode blockNode = (BlockNode) container;
|
||||
if (blockNode.getSuccessors().isEmpty()) {
|
||||
if (BlockUtils.isExitBlock(blockNode)) {
|
||||
return true;
|
||||
}
|
||||
return isInsnExitContainer(rootContainer, (IBlock) container);
|
||||
} else if (container instanceof IBranchRegion) {
|
||||
return false;
|
||||
} else if (container instanceof IBlock) {
|
||||
}
|
||||
if (container instanceof IBranchRegion) {
|
||||
IBranchRegion branchRegion = (IBranchRegion) container;
|
||||
return ListUtils.allMatch(branchRegion.getBranches(), RegionUtils::hasExitBlock);
|
||||
}
|
||||
if (container instanceof IBlock) {
|
||||
return isInsnExitContainer(rootContainer, (IBlock) container);
|
||||
} else if (container instanceof IRegion) {
|
||||
}
|
||||
if (container instanceof IRegion) {
|
||||
List<IContainer> blocks = ((IRegion) container).getSubBlocks();
|
||||
return !blocks.isEmpty()
|
||||
&& hasExitBlock(rootContainer, blocks.get(blocks.size() - 1));
|
||||
} else {
|
||||
throw new JadxRuntimeException(unknownContainerType(container));
|
||||
}
|
||||
throw new JadxRuntimeException(unknownContainerType(container));
|
||||
}
|
||||
|
||||
private static boolean isInsnExitContainer(IContainer rootContainer, IBlock block) {
|
||||
@@ -192,17 +252,25 @@ public class RegionUtils {
|
||||
|
||||
public static int insnsCount(IContainer container) {
|
||||
if (container instanceof IBlock) {
|
||||
return ((IBlock) container).getInstructions().size();
|
||||
} else if (container instanceof IRegion) {
|
||||
List<InsnNode> insnList = ((IBlock) container).getInstructions();
|
||||
int count = 0;
|
||||
for (InsnNode insn : insnList) {
|
||||
if (insn.contains(AFlag.DONT_GENERATE)) {
|
||||
continue;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
if (container instanceof IRegion) {
|
||||
IRegion region = (IRegion) container;
|
||||
int count = 0;
|
||||
for (IContainer block : region.getSubBlocks()) {
|
||||
count += insnsCount(block);
|
||||
}
|
||||
return count;
|
||||
} else {
|
||||
throw new JadxRuntimeException(unknownContainerType(container));
|
||||
}
|
||||
throw new JadxRuntimeException(unknownContainerType(container));
|
||||
}
|
||||
|
||||
public static boolean isEmpty(IContainer container) {
|
||||
|
||||
@@ -428,7 +428,7 @@ public class Utils {
|
||||
}
|
||||
|
||||
public static void checkThreadInterrupt() {
|
||||
if (Thread.interrupted()) {
|
||||
if (Thread.currentThread().isInterrupted()) {
|
||||
throw new JadxRuntimeException("Thread interrupted");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,16 +8,19 @@ import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.FileVisitOption;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarOutputStream;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -100,6 +103,10 @@ public class FileUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static void deleteFileIfExists(Path filePath) throws IOException {
|
||||
Files.deleteIfExists(filePath);
|
||||
}
|
||||
|
||||
public static boolean deleteDir(File dir) {
|
||||
File[] content = dir.listFiles();
|
||||
if (content != null) {
|
||||
@@ -110,13 +117,24 @@ public class FileUtils {
|
||||
return dir.delete();
|
||||
}
|
||||
|
||||
public static void deleteDir(Path dir) {
|
||||
public static void deleteDirIfExists(Path dir) {
|
||||
if (Files.exists(dir)) {
|
||||
try {
|
||||
deleteDir(dir);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to delete dir: " + dir.toAbsolutePath(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void deleteDir(Path dir) {
|
||||
try (Stream<Path> pathStream = Files.walk(dir)) {
|
||||
pathStream.sorted(Comparator.reverseOrder())
|
||||
.map(Path::toFile)
|
||||
.forEach(file -> {
|
||||
if (!file.delete()) {
|
||||
LOG.warn("Failed to remove file: {}", file.getAbsolutePath());
|
||||
.forEach(path -> {
|
||||
try {
|
||||
Files.delete(path);
|
||||
} catch (Exception e) {
|
||||
throw new JadxRuntimeException("Failed to delete path: " + path.toAbsolutePath(), e);
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
@@ -143,11 +161,11 @@ public class FileUtils {
|
||||
}
|
||||
|
||||
public static void deleteTempRootDir() {
|
||||
deleteDir(TEMP_ROOT_DIR);
|
||||
deleteDirIfExists(TEMP_ROOT_DIR);
|
||||
}
|
||||
|
||||
public static void clearTempRootDir() {
|
||||
deleteDir(TEMP_ROOT_DIR);
|
||||
deleteDirIfExists(TEMP_ROOT_DIR);
|
||||
makeDirs(TEMP_ROOT_DIR);
|
||||
}
|
||||
|
||||
@@ -208,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
|
||||
public static File prepareFile(File file) {
|
||||
File saveFile = cutFileName(file);
|
||||
@@ -230,18 +257,41 @@ public class FileUtils {
|
||||
return new File(file.getParentFile(), name);
|
||||
}
|
||||
|
||||
private static String bytesToHex(byte[] bytes) {
|
||||
char[] hexArray = "0123456789abcdef".toCharArray();
|
||||
if (bytes == null || bytes.length <= 0) {
|
||||
return null;
|
||||
private static final byte[] HEX_ARRAY = "0123456789abcdef".getBytes(StandardCharsets.US_ASCII);
|
||||
|
||||
public static String bytesToHex(byte[] bytes) {
|
||||
if (bytes == null || bytes.length == 0) {
|
||||
return "";
|
||||
}
|
||||
char[] hexChars = new char[bytes.length * 2];
|
||||
byte[] hexChars = new byte[bytes.length * 2];
|
||||
for (int j = 0; j < bytes.length; j++) {
|
||||
int v = bytes[j] & 0xFF;
|
||||
hexChars[j * 2] = hexArray[v >>> 4];
|
||||
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
|
||||
hexChars[j * 2] = HEX_ARRAY[v >>> 4];
|
||||
hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
|
||||
}
|
||||
return new String(hexChars);
|
||||
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) {
|
||||
@@ -275,4 +325,30 @@ public class FileUtils {
|
||||
}
|
||||
return new File(path);
|
||||
}
|
||||
|
||||
public static List<Path> toPaths(List<File> files) {
|
||||
return files.stream().map(File::toPath).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static List<Path> toPaths(File[] files) {
|
||||
return Stream.of(files).map(File::toPath).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static List<Path> fileNamesToPaths(List<String> fileNames) {
|
||||
return fileNames.stream().map(Paths::get).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static List<File> toFiles(List<Path> paths) {
|
||||
return paths.stream().map(Path::toFile).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static String md5Sum(byte[] data) {
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||
md.update(data);
|
||||
return bytesToHex(md.digest());
|
||||
} catch (Exception e) {
|
||||
throw new JadxRuntimeException("Failed to build hash", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +89,8 @@ public class BinaryXMLParser extends CommonBinaryParser {
|
||||
is.mark(4);
|
||||
int v = is.readInt16(); // version
|
||||
int h = is.readInt16(); // header size
|
||||
if (v == 0x0003 && h == 0x0008) {
|
||||
// Some APK Manifest.xml the version is 0
|
||||
if (h == 0x0008) {
|
||||
return true;
|
||||
}
|
||||
is.reset();
|
||||
|
||||
@@ -57,7 +57,7 @@ public class JadxArgsValidatorOutDirsTest {
|
||||
}
|
||||
|
||||
private void checkOutDirs(String outDir, String srcDir, String resDir) {
|
||||
JadxArgsValidator.validate(args);
|
||||
JadxArgsValidator.validate(new JadxDecompiler(args));
|
||||
LOG.debug("Got dirs: out={}, src={}, res={}", args.getOutDir(), args.getOutDirSrc(), args.getOutDirRes());
|
||||
assertThat(args.getOutDir(), is(toFile(outDir)));
|
||||
assertThat(args.getOutDirSrc(), is(toFile(srcDir)));
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
package jadx.api;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
import jadx.plugins.input.dex.DexInputPlugin;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
@@ -38,6 +42,20 @@ public class JadxDecompilerTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDirectDexInput() throws IOException {
|
||||
try (JadxDecompiler jadx = new JadxDecompiler();
|
||||
InputStream in = new FileInputStream(getFileFromSampleDir("hello.dex"))) {
|
||||
jadx.addCustomLoad(new DexInputPlugin().loadDexFromInputStream(in, "input"));
|
||||
jadx.load();
|
||||
for (JavaClass cls : jadx.getClasses()) {
|
||||
System.out.println(cls.getCode());
|
||||
}
|
||||
assertThat(jadx.getClasses(), Matchers.hasSize(1));
|
||||
assertThat(jadx.getErrorsCount(), Matchers.is(0));
|
||||
}
|
||||
}
|
||||
|
||||
private static final String TEST_SAMPLES_DIR = "test-samples/";
|
||||
|
||||
public static File getFileFromSampleDir(String fileName) {
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
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;
|
||||
|
||||
public class JadxInternalAccess {
|
||||
@@ -7,4 +10,16 @@ public class JadxInternalAccess {
|
||||
public static RootNode getRoot(JadxDecompiler d) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,6 @@ import org.junit.jupiter.api.BeforeEach;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.CodePosition;
|
||||
import jadx.api.CommentsLevel;
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.api.ICodeWriter;
|
||||
@@ -41,11 +40,9 @@ import jadx.api.JadxDecompiler;
|
||||
import jadx.api.JadxInternalAccess;
|
||||
import jadx.api.JavaClass;
|
||||
import jadx.api.args.DeobfuscationMapFileMode;
|
||||
import jadx.api.data.annotations.InsnCodeOffset;
|
||||
import jadx.api.metadata.ICodeMetadata;
|
||||
import jadx.api.metadata.annotations.InsnCodeOffset;
|
||||
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.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.rightPad;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.empty;
|
||||
import static org.hamcrest.Matchers.emptyArray;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
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.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_DIRECTORY2 = "jadx-core/" + TEST_DIRECTORY;
|
||||
|
||||
private static final String OUT_DIR = "test-out-tmp";
|
||||
|
||||
private static final String DEFAULT_INPUT_PLUGIN = "dx";
|
||||
/**
|
||||
* 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;
|
||||
|
||||
args = new JadxArgs();
|
||||
args.setOutDir(new File(OUT_DIR));
|
||||
args.setOutDir(new File("test-out-tmp"));
|
||||
args.setShowInconsistentCode(true);
|
||||
args.setThreadsCount(1);
|
||||
args.setSkipResources(true);
|
||||
@@ -150,12 +143,16 @@ public abstract class IntegrationTest extends TestUtils {
|
||||
close(decompiledCompiler);
|
||||
}
|
||||
|
||||
private void close(Closeable cloaseble) throws IOException {
|
||||
if (cloaseble != null) {
|
||||
cloaseble.close();
|
||||
private void close(Closeable closeable) throws IOException {
|
||||
if (closeable != null) {
|
||||
closeable.close();
|
||||
}
|
||||
}
|
||||
|
||||
public void setOutDirSuffix(String suffix) {
|
||||
args.setOutDir(new File("test-out-" + suffix + "-tmp"));
|
||||
}
|
||||
|
||||
public String getTestName() {
|
||||
return this.getClass().getSimpleName();
|
||||
}
|
||||
@@ -287,7 +284,7 @@ public abstract class IntegrationTest extends TestUtils {
|
||||
}
|
||||
|
||||
protected void runChecks(List<ClassNode> clsList) {
|
||||
clsList.forEach(this::checkCode);
|
||||
clsList.forEach(cls -> checkCode(cls, allowWarnInCode));
|
||||
compileClassNode(clsList);
|
||||
clsList.forEach(this::runAutoCheck);
|
||||
}
|
||||
@@ -300,7 +297,7 @@ public abstract class IntegrationTest extends TestUtils {
|
||||
|
||||
private void printCodeWithLineNumbers(ICodeInfo code) {
|
||||
String codeStr = code.getCodeStr();
|
||||
Map<Integer, Integer> lineMapping = code.getLineMapping();
|
||||
Map<Integer, Integer> lineMapping = code.getCodeMetadata().getLineMapping();
|
||||
String[] lines = codeStr.split(ICodeWriter.NL);
|
||||
for (int i = 0; i < lines.length; i++) {
|
||||
String line = lines[i];
|
||||
@@ -316,18 +313,18 @@ public abstract class IntegrationTest extends TestUtils {
|
||||
|
||||
private void printCodeWithOffsets(ICodeInfo code) {
|
||||
String codeStr = code.getCodeStr();
|
||||
Map<CodePosition, Object> annotations = code.getAnnotations();
|
||||
String[] lines = codeStr.split(ICodeWriter.NL);
|
||||
for (int i = 0; i < lines.length; i++) {
|
||||
String line = lines[i];
|
||||
int curLine = i + 1;
|
||||
Object ann = annotations.get(new CodePosition(curLine, 0));
|
||||
ICodeMetadata metadata = code.getCodeMetadata();
|
||||
int lineStartPos = 0;
|
||||
int newLineLen = ICodeWriter.NL.length();
|
||||
for (String line : codeStr.split(ICodeWriter.NL)) {
|
||||
Object ann = metadata.getAt(lineStartPos);
|
||||
String offsetStr = "";
|
||||
if (ann instanceof InsnCodeOffset) {
|
||||
int offset = ((InsnCodeOffset) ann).getOffset();
|
||||
offsetStr = "/* " + leftPad(String.valueOf(offset), 5) + " */";
|
||||
}
|
||||
System.out.println(rightPad(offsetStr, 12) + line);
|
||||
lineStartPos += line.length() + newLineLen;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -345,33 +342,6 @@ public abstract class IntegrationTest extends TestUtils {
|
||||
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) {
|
||||
String clsName = cls.getClassInfo().getRawName().replace('/', '.');
|
||||
try {
|
||||
|
||||
@@ -11,7 +11,6 @@ import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.tools.DiagnosticListener;
|
||||
import javax.tools.JavaCompiler;
|
||||
@@ -82,7 +81,7 @@ public class TestCompiler implements Closeable {
|
||||
arguments.addAll(options.getArguments());
|
||||
|
||||
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);
|
||||
CompilationTask compilerTask = compiler.getTask(out, fileManager, diagnostic, arguments, null, jfObjects);
|
||||
if (Boolean.FALSE.equals(compilerTask.call())) {
|
||||
|
||||
@@ -31,6 +31,10 @@ public enum TestProfile implements Consumer<IntegrationTest> {
|
||||
test.useTargetJavaVersion(11);
|
||||
test.useJavaInput();
|
||||
}),
|
||||
JAVA17("java-17", test -> {
|
||||
test.useTargetJavaVersion(17);
|
||||
test.useJavaInput();
|
||||
}),
|
||||
ECJ_DX_J8("ecj-dx-j8", test -> {
|
||||
test.useEclipseCompiler();
|
||||
test.useTargetJavaVersion(8);
|
||||
@@ -52,8 +56,9 @@ public enum TestProfile implements Consumer<IntegrationTest> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(IntegrationTest integrationTest) {
|
||||
this.setup.accept(integrationTest);
|
||||
public void accept(IntegrationTest test) {
|
||||
this.setup.accept(test);
|
||||
test.setOutDirSuffix(description);
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
|
||||
@@ -3,7 +3,21 @@ package jadx.tests.api.utils;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import jadx.NotYetImplementedExtension;
|
||||
import jadx.api.CommentsLevel;
|
||||
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)
|
||||
public class TestUtils {
|
||||
@@ -35,4 +49,31 @@ public class TestUtils {
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ import java.util.Map;
|
||||
import org.assertj.core.api.AbstractObjectAssert;
|
||||
import org.assertj.core.api.Assertions;
|
||||
|
||||
import jadx.api.CodePosition;
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.api.metadata.ICodeAnnotation;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.ICodeNode;
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
@@ -67,8 +67,8 @@ public class JadxClassNodeAssertions extends AbstractObjectAssert<JadxClassNodeA
|
||||
int codePos = code.getCodeStr().indexOf(refStr);
|
||||
assertThat(codePos).describedAs("String '%s' not found", refStr).isNotEqualTo(-1);
|
||||
int refPos = codePos + refOffset;
|
||||
for (Map.Entry<CodePosition, Object> entry : code.getAnnotations().entrySet()) {
|
||||
if (entry.getKey().getPos() == refPos) {
|
||||
for (Map.Entry<Integer, ICodeAnnotation> entry : code.getCodeMetadata().getAsMap().entrySet()) {
|
||||
if (entry.getKey() == refPos) {
|
||||
Assertions.assertThat(entry.getValue()).isEqualTo(node);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import java.util.stream.Collectors;
|
||||
import org.assertj.core.api.AbstractObjectAssert;
|
||||
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.api.data.annotations.ICodeRawOffset;
|
||||
import jadx.api.metadata.annotations.InsnCodeOffset;
|
||||
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
|
||||
@@ -22,9 +22,9 @@ public class JadxCodeInfoAssertions extends AbstractObjectAssert<JadxCodeInfoAss
|
||||
}
|
||||
|
||||
public JadxCodeInfoAssertions checkCodeOffsets() {
|
||||
long dupOffsetCount = actual.getAnnotations().values().stream()
|
||||
.filter(ICodeRawOffset.class::isInstance)
|
||||
.collect(Collectors.groupingBy(o -> ((ICodeRawOffset) o).getOffset(), Collectors.toList()))
|
||||
long dupOffsetCount = actual.getCodeMetadata().getAsMap().values().stream()
|
||||
.filter(InsnCodeOffset.class::isInstance)
|
||||
.collect(Collectors.groupingBy(o -> ((InsnCodeOffset) o).getOffset(), Collectors.toList()))
|
||||
.values().stream()
|
||||
.filter(list -> list.size() > 1)
|
||||
.count();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user