Compare commits

...

63 Commits

Author SHA1 Message Date
Skylot 5640f3b931 fix test 2024-06-02 18:21:21 +01:00
Skylot 02bc27e887 build exclude list in init method to improve performance, add default values migration 2024-06-02 18:21:04 +01:00
bagipro 794e5adb7f Change deobf-min default to 2 2024-05-09 18:22:02 +07:00
bagipro a81cec7701 Improve deobf whitelist 2024-05-08 16:16:08 +07:00
Skylot 09fa35f144 feat: allow to change config and cache dirs with env vars (#2159) 2024-04-27 21:48:42 +01:00
Skylot f2a6a1e942 build: update JDK to 21 for windows artifacts 2024-04-27 17:40:06 +01:00
Andrei Kudryavtsev b85900aa3d feat: move AAB support to separate plugin (PR #2165)
* wip: finished with factories

* wip: bundleconfig.pb

* wip: jadx-aab-input, separate BundleConfig parser

* wip: removed test apks

* wip: proto xml pretty print

* wip: fixed getNamedValues NPE

* minor fixes

* spotless

* enabled zip64 for gui shadow jar

* spotless

* spotless

* reverted manifest identification since signature parsing not working at the moment

* replace static methods with new API methods

---------

Co-authored-by: Skylot <118523+skylot@users.noreply.github.com>
2024-04-26 21:54:54 +01:00
Jan S 37a42d1418 fix(gui): show correct content of binary resources in hex view (#2160) (PR #2166) 2024-04-24 19:29:52 +01:00
Jan S 07dde05337 fix(build): configure launch4j to not change current directory (#2162) (PR #2163) 2024-04-24 18:52:08 +01:00
Nick 8618214c7f docs: improve installation section (PR #2161)
* Update README.md

* Update README.md
2024-04-24 18:43:05 +01:00
Skylot b80f32a36f fix(smali-input): compile one smali file at a time to avoid 64k limit (#2158) 2024-04-23 22:14:59 +01:00
Skylot ce527ed753 fix(build): add missing files in bundle 2024-04-20 21:04:17 +01:00
Skylot f2ea6415c9 fix(cli): don't print stacktrace for incorrect options (#2140) 2024-04-20 18:06:30 +01:00
Skylot bc70f8eabb fix: use correct new line string for simple code writer 2024-04-20 17:37:45 +01:00
Skylot be25cbf8c2 fix: use common parser for manifest, verify app package 2024-04-20 17:37:45 +01:00
Skylot f9c0cad146 chore: update dependencies 2024-04-19 20:14:25 +01:00
Skylot b356ff76e1 fix: improve StringBuilder elimination (#2148) 2024-04-19 20:14:25 +01:00
Skylot ec9244a635 fix(gui): use common code for manifest parsing in debugger 2024-04-19 20:14:25 +01:00
omerfarukkykc a5bd64461d fix(gui): remember selected device in debugger (PR #2153)
* ADBDialog->launchApp() if multiple devices presented should let user select the one they desire.

* compare objects directly instead parsing

---------

Co-authored-by: Ömer Faruk KAYIKCI <omer.kayikci@tubitak.gov.tr>
Co-authored-by: Skylot <118523+skylot@users.noreply.github.com>
2024-04-19 19:02:12 +01:00
dependabot[bot] 54bf79ccc5 build(deps): bump gradle/wrapper-validation-action from 2 to 3 (#2149) 2024-04-15 18:17:48 +00:00
Skylot 6182332eef fix: avoid self-loop for exception handlers (#2147) 2024-04-11 23:07:45 +03:00
Skylot 37b57096ec fix: allow use FieldInfo as switch key (#2147) 2024-04-11 23:07:44 +03:00
Skylot 6aab8fabc9 chore: update dependencies 2024-04-11 23:07:41 +03:00
JustFor 665c1e57d2 fix(gui): update Messages_zh_CN.properties (PR #2146)
Sync new Jadx text.
2024-04-09 22:11:24 +01:00
Skylot 6e8affcbdc feat: add options to JadxArgs to change code new line and indent (#1945, #1948) 2024-04-08 21:51:24 +01:00
Skylot 41d6b0018e fix: add missing " * " on new line for block comments, flip addCodeComment args (#2145) 2024-04-08 21:34:34 +01:00
Skylot dbadbb01fc refactor: rename method collectArgsWithoutLoading into collectArgNodes in MethodNode (#2142) 2024-04-07 23:09:02 +01:00
Skylot 0f52077c5c feat: allow to set style for code comments (#2145) 2024-04-07 23:06:32 +01:00
Skylot ea861829c7 fix: support end block entry for mutli-entry loops (#889) 2024-04-06 22:49:32 +01:00
Skylot c1de235289 fix: in anonymous class checks ignore instance fields not used outside 2024-04-06 22:45:30 +01:00
Skylot 8f969d4e89 chore: update gradle and dependencies 2024-04-03 21:03:48 +01:00
Skylot 0c1f830f94 fix: lambda decoding and code generation (#2139) 2024-04-03 21:03:48 +01:00
Skylot 43c082e4da feat: replace Android resource ids with android.R fields (#2119) 2024-03-31 20:37:33 +01:00
Skylot ecdc4e6757 refactor: move constant collection into separate pass (#2119) 2024-03-30 21:51:02 +00:00
Skylot b865c9c687 refactor: allow store unresolved fields in ConstStorage (#2119) 2024-03-30 20:52:31 +00:00
xnumad 6b4976c593 fix(gui): handle paths where file name is null (#2136)(PR #2137)
* fix: Ignore invalid files

Avoid NullPointerException when using "Open files" or drag-n-drop

* refactor: Replace Stream API chain with loop

IntelliJ

* fix: Ignore invalid files

Avoid NullPointerException when using "Add files"

* fix: Fall back to complete path string

Instead of empty project name

* fix: Render tree

Project tree (sidebar) didn’t load
Toggling "View > Show flatten packages" threw a NPE here

* fix code formatting

---------

Co-authored-by: Skylot <skylot@gmail.com>
2024-03-29 22:30:01 +00:00
Skylot 2807dc5090 fix(script): add example script for resources rename (#2126) 2024-03-20 18:46:44 +00:00
Skylot 463d2b90fa fix: don't apply node positions and prevent eager loading for custom decompile modes (#2116) 2024-03-19 20:23:04 +00:00
Skylot bff00d101f fix(script): add option flags, fix missing script options in help 2024-03-19 20:22:50 +00:00
Skylot 1290ef63a2 fix(build): enable publish to maven for rename-mappings plugin 2024-03-16 21:58:03 +03:00
Skylot 49d2b34d84 chore: update dependencies 2024-03-16 21:58:00 +03:00
CKCat eecdfae73f fix(res): resolve some manifest decode errors (PR #2122)
* The elementSize may be larger than the actual size of the element chunk.

* end namespace chunk size can be any value.

* keep at least a warning.
2024-03-16 18:57:10 +00:00
JustFor 8760b4ddde fix(gui): copy strings without quotes (PR #2121)
* Update AbstractCodeArea.java

In general, we need data, not text in code. But now every time you copy the highlighted text, you copy the highlighted quotes as well. This often results in an extra need to delete the quotation marks around the sides, which is confusing.
Now when copying selected highlighted text, quotes are not copied in.

* Update AbstractCodeArea.java

fix code format

* additional checks, move to common method

---------

Co-authored-by: Skylot <skylot@gmail.com>
2024-03-16 18:55:57 +00:00
Andrei Kudryavtsev 3599b248a4 feat(gui): dragging tab appearance settings (#2120)(PR #2118) 2024-03-08 23:11:58 +03:00
bagipro 2fdd496518 fix(res): add indents for namespace declarations (PR #2114)
Co-authored-by: bagipro <bugi@bugi>
2024-03-01 16:32:47 +00:00
bagipro 278e3c2d47 fix(res): avoid duplicated XML attributes (PR #2112)
Co-authored-by: bagipro <bugi@bugi>
2024-02-27 18:49:09 +00:00
bagipro 881a716b8e fix(res): fixed XML proto parsing for removed debug data (PR #2111)
* Fixed XML proto parsing for removed debug data

* Fixed codestyle check

---------

Co-authored-by: bagipro <bugi@bugi>
2024-02-27 17:34:37 +00:00
Skylot a73c9e90fc fix(dex-input): improve error report message for invalid dex checksum 2024-02-26 19:36:28 +00:00
Skylot 56749b2afb chore: update dependencies 2024-02-25 22:38:00 +03:00
Andrei Kudryavtsev d7ec35791b feat(gui): tabs drag and drop reorder support (#1212) (PR #2109) 2024-02-25 19:36:46 +00:00
Skylot d51362ed50 fix: don't remove exception handlers (#2104) 2024-02-19 20:18:44 +00:00
Skylot 5c0c1daa71 fix(gui): use new RSTA line number formatter API to show source lines 2024-02-16 18:38:56 +00:00
Skylot 603ea3989a chore: update dependencies 2024-02-16 17:41:37 +00:00
Emiel Matthys 018ff98df7 feat(gui): remember save preference decision (PR #2103)
* First version

* Use dropdown

* Spotless

* Language strings and tests

* Comment out translated versions

* Remove more translations

---------

Co-authored-by: Emiel Matthys <emiel.matthys@guardsquare.com>
2024-02-15 18:00:37 +00:00
nitram84 5fbabdefca fix: NPE in unused EcxHandler block removal code (#2086) (PR #2104) 2024-02-15 17:57:30 +00:00
nitram84 13607fc8b6 fix(res): add missing namespace declarations (PR #2102)
* fix(res): add missing namespace declarations

* remove `writer.getIndent() == 0`

---------

Co-authored-by: skylot <118523+skylot@users.noreply.github.com>
2024-02-15 17:56:39 +00:00
DanielFi 0c33d723c8 fix: optimize switch fallthrough (PR #2054)
* cache post dom map between switch cases
* cache post dom map of whole methods
* calculate full post dom tree, fix switch out block detection

---------

Co-authored-by: Skylot <skylot@gmail.com>
2024-02-14 18:31:38 +00:00
Skylot 0143423dc9 fix: use empty line before field in correct place (#2101) 2024-02-12 15:39:41 +00:00
Skylot 21b1452485 chore: update gradle and dependencies 2024-02-12 15:39:40 +00:00
Skylot ecb8abb98e fix: correct rollback if type update failed (#2090) 2024-02-10 16:08:51 +00:00
Skylot a3a4fabd5a fix: store classes access flags in class set 2024-02-07 22:10:31 +03:00
Skylot edf6ce273c fix: clear node flags for custom decompilation mode 2024-02-06 21:54:41 +03:00
dependabot[bot] 1bb956a8b0 build(deps): bump gradle/wrapper-validation-action from 1 to 2 (PR #2099)
Bumps [gradle/wrapper-validation-action](https://github.com/gradle/wrapper-validation-action) from 1 to 2.
- [Release notes](https://github.com/gradle/wrapper-validation-action/releases)
- [Commits](https://github.com/gradle/wrapper-validation-action/compare/v1...v2)

---
updated-dependencies:
- dependency-name: gradle/wrapper-validation-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-05 18:38:02 +00:00
277 changed files with 5832 additions and 2691 deletions
+8 -8
View File
@@ -20,8 +20,8 @@ jobs:
- 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}"
JADX_REV=$(git rev-list --count HEAD)
JADX_VERSION="r${JADX_REV}.${GITHUB_SHA:0:7}"
echo "JADX_VERSION=$JADX_VERSION" >> $GITHUB_ENV
- name: Build with Gradle
@@ -37,7 +37,7 @@ jobs:
# Upload unpacked files for now
path: build/jadx/**/*
if-no-files-found: error
retention-days: 30
retention-days: 14
- name: Save exe artifact
uses: actions/upload-artifact@v4
@@ -45,7 +45,7 @@ jobs:
name: ${{ format('jadx-gui-{0}-no-jre-win.exe', env.JADX_VERSION) }}
path: build/*.exe
if-no-files-found: error
retention-days: 30
retention-days: 14
build-win-bundle:
runs-on: windows-latest
@@ -57,7 +57,7 @@ jobs:
- name: Set up JDK
uses: oracle-actions/setup-java@v1
with:
release: 17
release: 21
- name: Print Java version
shell: bash
@@ -66,8 +66,8 @@ jobs:
- 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}"
JADX_REV=$(git rev-list --count HEAD)
JADX_VERSION="r${JADX_REV}.${GITHUB_SHA:0:7}"
echo "JADX_VERSION=$JADX_VERSION" >> $GITHUB_ENV
- name: Build with Gradle
@@ -81,4 +81,4 @@ jobs:
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
retention-days: 14
@@ -12,4 +12,4 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: gradle/wrapper-validation-action@v1
- uses: gradle/actions/wrapper-validation@v3
+29 -19
View File
@@ -8,16 +8,19 @@
![GitHub release (latest by SemVer)](https://img.shields.io/github/downloads/skylot/jadx/latest/total)
![Latest release](https://img.shields.io/github/release/skylot/jadx.svg)
[![Maven Central](https://img.shields.io/maven-central/v/io.github.skylot/jadx-core)](https://search.maven.org/search?q=g:io.github.skylot%20AND%20jadx)
![Java 11+](https://img.shields.io/badge/Java-11%2B-blue)
[![License](http://img.shields.io/:license-apache-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0.html)
**jadx** - Dex to Java decompiler
Command line and GUI tools for producing Java source code from Android Dex and Apk files
:exclamation::exclamation::exclamation: Please note that in most cases **jadx** can't decompile all 100% of the code, so errors will occur. Check [Troubleshooting guide](https://github.com/skylot/jadx/wiki/Troubleshooting-Q&A#decompilation-issues) for workarounds
> [!WARNING]
> Please note that in most cases **jadx** can't decompile all 100% of the code, so errors will occur.<br />
> Check [Troubleshooting guide](https://github.com/skylot/jadx/wiki/Troubleshooting-Q&A#decompilation-issues) for workarounds.
**Main features:**
- decompile Dalvik bytecode to java classes from APK, dex, aar, aab and zip files
- decompile Dalvik bytecode to Java code from APK, dex, aar, aab and zip files
- decode `AndroidManifest.xml` and other resources from `resources.arsc`
- deobfuscator included
@@ -48,18 +51,22 @@ 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 ![Arch Linux package](https://img.shields.io/archlinux/v/extra/any/jadx?label=)
```bash
sudo pacman -S jadx
```
2. macOS ![homebrew version](https://img.shields.io/homebrew/v/jadx?label=)
```bash
brew install jadx
```
3. [Flathub ![Flathub](https://img.shields.io/flathub/v/com.github.skylot.jadx?label=)](https://flathub.org/apps/details/com.github.skylot.jadx)
```bash
flatpak install flathub com.github.skylot.jadx
```
- Arch Linux
[![Arch Linux package](https://img.shields.io/archlinux/v/extra/any/jadx)](https://archlinux.org/packages/extra/any/jadx/)
[![AUR Version](https://img.shields.io/aur/version/jadx-git)](https://aur.archlinux.org/packages/jadx-git)
```bash
sudo pacman -S jadx
```
- macOS
[![homebrew version](https://img.shields.io/homebrew/v/jadx)](https://formulae.brew.sh/formula/jadx)
```bash
brew install jadx
```
- Flathub
[![Flathub Version](https://img.shields.io/flathub/v/com.github.skylot.jadx)](https://flathub.org/apps/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)
@@ -79,7 +86,7 @@ and also packed to `build/jadx-<version>.zip`
### Usage
```
jadx[-gui] [command] [options] <input files> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc, .aab, .xapk)
jadx[-gui] [command] [options] <input files> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc, .aab, .xapk, .jadx.kts)
commands (use '<command> --help' for command options):
plugins - manage jadx plugins
@@ -121,7 +128,7 @@ options:
--deobf - activate deobfuscation
--deobf-min - min length of name, renamed if shorter, default: 3
--deobf-max - max length of name, renamed if longer, default: 64
--deobf-whitelist - space separated list of classes (full name) and packages (ends with '.*') to exclude from deobfuscation, default: android.support.v4.* android.support.v7.* android.support.v4.os.* android.support.annotation.Px androidx.core.os.* androidx.annotation.Px
--deobf-whitelist - space separated list of classes (full name) and packages (ends with '.*') to exclude from deobfuscation, default: android.support.* android.os.* androidx.core.os.* androidx.annotation.*
--deobf-cfg-file - deobfuscation mappings file used for JADX auto-generated names (in the JOBF file format), default: same dir and name as input file with '.jobf' extension
--deobf-cfg-file-mode - set mode for handling the JADX auto-generated names' deobfuscation map file:
'read' - read if found, don't save (default)
@@ -171,12 +178,15 @@ Plugin options (-P<name>=<value>):
- kotlin-metadata.to-string - rename fields using toString, values: [yes, no], default: yes
- kotlin-metadata.getters - rename simple getters to field names, values: [yes, no], default: yes
4) rename-mappings: various mappings support
- rename-mappings.format - mapping format, values: [auto, TINY, TINY_2, ENIGMA, ENIGMA_DIR, MCP, SRG, TSRG, TSRG2, PROGUARD], default: auto
- rename-mappings.invert - invert mapping, values: [yes, no], default: no
- rename-mappings.format - mapping format, values: [AUTO, TINY_FILE, TINY_2_FILE, ENIGMA_FILE, ENIGMA_DIR, SRG_FILE, XSRG_FILE, CSRG_FILE, TSRG_FILE, TSRG_2_FILE, PROGUARD_FILE], default: AUTO
- rename-mappings.invert - invert mapping on load, values: [yes, no], default: no
Environment variables:
JADX_DISABLE_XML_SECURITY - set to 'true' to disable all security checks for XML files
JADX_DISABLE_ZIP_SECURITY - set to 'true' to disable all security checks for zip files
JADX_ZIP_MAX_ENTRIES_COUNT - maximum allowed number of entries in zip files (default: 100 000)
JADX_CONFIG_DIR - custom config directory, using system by default
JADX_CACHE_DIR - custom cache directory, using system by default
JADX_TMP_DIR - custom temp directory, using system by default
Examples:
@@ -186,7 +196,7 @@ Examples:
jadx --log-level ERROR app.apk
jadx -Pdex-input.verify-checksum=no app.apk
```
These options also worked on jadx-gui running from command line and override options from preferences dialog
These options also work in jadx-gui running from command line and override options from preferences' dialog
### Troubleshooting
Please check wiki page [Troubleshooting Q&A](https://github.com/skylot/jadx/wiki/Troubleshooting-Q&A)
+4
View File
@@ -89,6 +89,10 @@ val copyArtifacts by tasks.registering(Copy::class) {
include("**/*.jar")
rename("jadx-gui-(.*)-all.jar", "jadx-$1-all.jar")
}
from(layout.projectDirectory) {
include("README.md")
include("LICENSE")
}
into(layout.buildDirectory.dir("jadx"))
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
+1 -1
View File
@@ -3,7 +3,7 @@ plugins {
}
dependencies {
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.22")
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.23")
}
repositories {
@@ -11,15 +11,15 @@ group = "io.github.skylot"
version = jadxVersion
dependencies {
implementation("org.slf4j:slf4j-api:2.0.11")
implementation("org.slf4j:slf4j-api:2.0.13")
compileOnly("org.jetbrains:annotations:24.1.0")
testImplementation("ch.qos.logback:logback-classic:1.4.14")
testImplementation("ch.qos.logback:logback-classic:1.5.6")
testImplementation("org.hamcrest:hamcrest-library:2.2")
testImplementation("org.mockito:mockito-core:5.10.0")
testImplementation("org.assertj:assertj-core:3.25.2")
testImplementation("org.mockito:mockito-core:5.11.0")
testImplementation("org.assertj:assertj-core:3.25.3")
testImplementation("org.junit.jupiter:junit-jupiter:5.10.1")
testImplementation("org.junit.jupiter:junit-jupiter:5.10.2")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
testCompileOnly("org.jetbrains:annotations:24.1.0")
Binary file not shown.
+2 -2
View File
@@ -1,7 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=9d926787066a081739e8200858338b4a69e837c3a821a33aca9db09dd4a41026
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
distributionSha256Sum=544c35d6bd849ae8a5ed0bcea39ba677dc40f49df7d1835561582da2009b961d
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Vendored
+10 -10
View File
@@ -43,11 +43,11 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
@@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
+2 -1
View File
@@ -18,9 +18,10 @@ dependencies {
runtimeOnly(project(":jadx-plugins:jadx-kotlin-metadata"))
runtimeOnly(project(":jadx-plugins:jadx-script:jadx-script-plugin"))
runtimeOnly(project(":jadx-plugins:jadx-xapk-input"))
runtimeOnly(project(":jadx-plugins:jadx-aab-input"))
implementation("org.jcommander:jcommander:1.83")
implementation("ch.qos.logback:logback-classic:1.4.14")
implementation("ch.qos.logback:logback-classic:1.5.6")
}
application {
@@ -17,7 +17,6 @@ import com.beust.jcommander.ParameterDescription;
import com.beust.jcommander.ParameterException;
import com.beust.jcommander.Parameterized;
import jadx.api.JadxArgs;
import jadx.api.JadxDecompiler;
import jadx.api.plugins.JadxPluginInfo;
import jadx.api.plugins.options.JadxPluginOptions;
@@ -109,8 +108,11 @@ public class JCommanderWrapper<T> {
out.println(appendPluginOptions(maxNamesLen));
out.println();
out.println("Environment variables:");
out.println(" JADX_DISABLE_XML_SECURITY - set to 'true' to disable all security checks for XML files");
out.println(" JADX_DISABLE_ZIP_SECURITY - set to 'true' to disable all security checks for zip files");
out.println(" JADX_ZIP_MAX_ENTRIES_COUNT - maximum allowed number of entries in zip files (default: 100 000)");
out.println(" JADX_CONFIG_DIR - custom config directory, using system by default");
out.println(" JADX_CACHE_DIR - custom cache directory, using system by default");
out.println(" JADX_TMP_DIR - custom temp directory, using system by default");
out.println();
out.println("Examples:");
@@ -165,7 +167,7 @@ public class JCommanderWrapper<T> {
opt.append("- ").append(description);
}
if (addDefaults) {
String defaultValue = getDefaultValue(args, f, opt);
String defaultValue = getDefaultValue(args, f);
if (defaultValue != null && !description.contains("(default)")) {
opt.append(", default: ").append(defaultValue);
}
@@ -188,7 +190,7 @@ public class JCommanderWrapper<T> {
}
@Nullable
private static String getDefaultValue(Object args, Field f, StringBuilder opt) {
private static String getDefaultValue(Object args, Field f) {
try {
Class<?> fieldType = f.getType();
if (fieldType == int.class) {
@@ -219,7 +221,7 @@ public class JCommanderWrapper<T> {
StringBuilder sb = new StringBuilder();
int k = 1;
// load and init all options plugins to print all options
try (JadxDecompiler decompiler = new JadxDecompiler(new JadxArgs())) {
try (JadxDecompiler decompiler = new JadxDecompiler(argsObj.toJadxArgs())) {
JadxPluginManager pluginManager = decompiler.getPluginManager();
pluginManager.load(new JadxExternalPluginsLoader());
pluginManager.initAll();
@@ -29,12 +29,12 @@ import jadx.api.args.IntegerFormat;
import jadx.api.args.ResourceNameSource;
import jadx.api.args.UserRenamesMappingsMode;
import jadx.core.deobf.conditions.DeobfWhitelist;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.exceptions.JadxArgsValidateException;
import jadx.core.utils.files.FileUtils;
public class JadxCLIArgs {
@Parameter(description = "<input files> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc, .aab, .xapk)")
@Parameter(description = "<input files> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc, .aab, .xapk, .jadx.kts)")
protected List<String> files = new ArrayList<>(1);
@Parameter(names = { "-d", "--output-dir" }, description = "output directory")
@@ -287,14 +287,13 @@ public class JadxCLIArgs {
System.out.println(JadxDecompiler.getVersion());
return false;
}
try {
if (threadsCount <= 0) {
throw new JadxException("Threads count must be positive, got: " + threadsCount);
if (threadsCount <= 0) {
throw new JadxArgsValidateException("Threads count must be positive, got: " + threadsCount);
}
for (String fileName : files) {
if (fileName.startsWith("-")) {
throw new JadxArgsValidateException("Unknown option: " + fileName);
}
} catch (JadxException e) {
System.err.println("ERROR: " + e.getMessage());
jcw.printUsage();
return false;
}
return true;
}
@@ -559,8 +558,8 @@ public class JadxCLIArgs {
for (String s : value.split(",")) {
try {
set.add(RenameEnum.valueOf(s.trim().toUpperCase(Locale.ROOT)));
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException(
} catch (Exception e) {
throw new JadxArgsValidateException(
'\'' + s + "' is unknown for parameter " + paramName
+ ", possible values are " + enumValuesString(RenameEnum.values()));
}
@@ -625,7 +624,7 @@ public class JadxCLIArgs {
try {
return parse.apply(stringAsEnumName(value));
} catch (Exception e) {
throw new IllegalArgumentException(
throw new JadxArgsValidateException(
'\'' + value + "' is unknown, possible values are: " + enumValuesString(values.get()));
}
}
@@ -7,6 +7,7 @@ import com.beust.jcommander.JCommander;
import jadx.cli.commands.CommandPlugins;
import jadx.cli.commands.ICommand;
import jadx.core.utils.exceptions.JadxArgsValidateException;
public class JadxCLICommands {
private static final Map<String, ICommand> COMMANDS_MAP = new TreeMap<>();
@@ -26,7 +27,8 @@ public class JadxCLICommands {
public static boolean process(JCommanderWrapper<?> jcw, JCommander jc, String parsedCommand) {
ICommand command = COMMANDS_MAP.get(parsedCommand);
if (command == null) {
throw new IllegalArgumentException("Unknown command: " + parsedCommand);
throw new JadxArgsValidateException("Unknown command: " + parsedCommand
+ ". Expected one of: " + COMMANDS_MAP.keySet());
}
JCommander subCommander = jc.getCommands().get(parsedCommand);
command.process(jcw, subCommander);
@@ -12,6 +12,7 @@ import jadx.api.JadxDecompiler;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.visitors.SaveCode;
import jadx.core.utils.exceptions.JadxArgsValidateException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.FileUtils;
@@ -33,10 +34,10 @@ public class SingleClassMode {
.findFirst().orElse(null);
}
if (clsForProcess == null) {
throw new JadxRuntimeException("Input class not found: " + singleClass);
throw new JadxArgsValidateException("Input class not found: " + singleClass);
}
if (clsForProcess.contains(AFlag.DONT_GENERATE)) {
throw new JadxRuntimeException("Input class can't be saved by current jadx settings (marked as DONT_GENERATE)");
throw new JadxArgsValidateException("Input class can't be saved by current jadx settings (marked as DONT_GENERATE)");
}
if (clsForProcess.isInner()) {
clsForProcess = clsForProcess.getTopParentClass();
@@ -52,7 +53,7 @@ public class SingleClassMode {
if (size == 1) {
clsForProcess = classes.get(0);
} else {
throw new JadxRuntimeException("Found " + size + " classes, single class output can't be used");
throw new JadxArgsValidateException("Found " + size + " classes, single class output can't be used");
}
}
ICodeInfo codeInfo;
@@ -23,8 +23,9 @@ public class ConvertToClsSet {
private static final Logger LOG = LoggerFactory.getLogger(ConvertToClsSet.class);
public static void usage() {
LOG.info("<output .jcst file> <several input dex or jar files> ");
LOG.info("<android API level (number)> <output .jcst file> <several input dex or jar files> ");
LOG.info("Arguments to update core.jcst: "
+ "<android API level (number)> "
+ "<jadx root>/jadx-core/src/main/resources/clst/core.jcst "
+ "<sdk_root>/platforms/android-<api level>/android.jar"
+ "<sdk_root>/platforms/android-<api level>/optional/android.car.jar "
@@ -32,11 +33,12 @@ public class ConvertToClsSet {
}
public static void main(String[] args) {
if (args.length < 2) {
if (args.length != 5) {
usage();
System.exit(1);
}
List<Path> inputPaths = Stream.of(args).map(Paths::get).collect(Collectors.toList());
int androidApiLevel = Integer.parseInt(args[0]);
List<Path> inputPaths = Stream.of(args).skip(1).map(Paths::get).collect(Collectors.toList());
Path output = inputPaths.remove(0);
JadxArgs jadxArgs = new JadxArgs();
@@ -57,6 +59,7 @@ public class ConvertToClsSet {
decompiler.load();
RootNode root = decompiler.getRoot();
ClsSet set = new ClsSet(root);
set.setAndroidApiLevel(androidApiLevel);
set.loadFrom(root);
set.save(output);
@@ -20,7 +20,7 @@ import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.android.TextResMapFile;
import jadx.core.xmlgen.ResTableParser;
import jadx.core.xmlgen.ResTableBinaryParser;
/**
* Utility class for convert '.arsc' to simple text file with mapping id to resource name
@@ -54,7 +54,7 @@ public class ConvertArscFile {
rewritesCount = 0;
for (Path resFile : inputPaths) {
LOG.info("Processing {}", resFile);
ResTableParser resTableParser = new ResTableParser(root, true);
ResTableBinaryParser resTableParser = new ResTableBinaryParser(root, true);
if (resFile.getFileName().toString().endsWith(".jar")) {
// Load resources.arsc from android.jar
try (ZipFile zip = new ZipFile(resFile.toFile())) {
@@ -7,6 +7,7 @@ import org.junit.jupiter.api.Test;
import jadx.api.JadxArgs.RenameEnum;
import jadx.cli.JadxCLIArgs.RenameConverter;
import jadx.core.utils.exceptions.JadxArgsValidateException;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -38,7 +39,7 @@ public class RenameConverterTest {
@Test
public void wrong() {
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class,
JadxArgsValidateException thrown = assertThrows(JadxArgsValidateException.class,
() -> converter.convert("wrong"),
"Expected convert() to throw, but it didn't");
+6
View File
@@ -0,0 +1,6 @@
## jadx app commons
This module contains common utilities used in jadx apps (cli and gui) and not needed in jadx-code module:
- `JadxCommonFiles` - wrapper for `dev.dirs:directories` lib to get
'config' and 'cache' directories in cross-platform way
- `JadxCommonEnv` - utils for work with environment variables
@@ -0,0 +1,7 @@
plugins {
id("jadx-library")
}
dependencies {
implementation("dev.dirs:directories:26")
}
@@ -0,0 +1,29 @@
package jadx.commons.app;
public class JadxCommonEnv {
public static String get(String varName, String defValue) {
String strValue = System.getenv(varName);
return isNullOrEmpty(strValue) ? defValue : strValue;
}
public static boolean getBool(String varName, boolean defValue) {
String strValue = System.getenv(varName);
if (isNullOrEmpty(strValue)) {
return defValue;
}
return strValue.equalsIgnoreCase("true");
}
public static int getInt(String varName, int defValue) {
String strValue = System.getenv(varName);
if (isNullOrEmpty(strValue)) {
return defValue;
}
return Integer.parseInt(strValue);
}
private static boolean isNullOrEmpty(String value) {
return value == null || value.isEmpty();
}
}
@@ -0,0 +1,73 @@
package jadx.commons.app;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import org.jetbrains.annotations.Nullable;
import dev.dirs.ProjectDirectories;
public class JadxCommonFiles {
private static final Path CONFIG_DIR;
private static final Path CACHE_DIR;
public static Path getConfigDir() {
return CONFIG_DIR;
}
public static Path getCacheDir() {
return CACHE_DIR;
}
static {
DirsLoader loader = new DirsLoader();
loader.init();
CONFIG_DIR = loader.getConfigDir();
CACHE_DIR = loader.getCacheDir();
}
private static final class DirsLoader {
private @Nullable ProjectDirectories dirs;
private Path configDir;
private Path cacheDir;
public void init() {
try {
configDir = loadEnvDir("JADX_CONFIG_DIR");
cacheDir = loadEnvDir("JADX_CACHE_DIR");
} catch (Exception e) {
throw new RuntimeException("Failed to init common directories", e);
}
}
private Path loadEnvDir(String envVar) throws IOException {
String envDir = JadxCommonEnv.get(envVar, null);
String dirStr;
if (envDir != null) {
dirStr = envDir;
} else {
dirStr = loadDirs().configDir;
}
Path path = Path.of(dirStr).toAbsolutePath();
Files.createDirectories(path);
return path;
}
private synchronized ProjectDirectories loadDirs() {
if (dirs == null) {
dirs = ProjectDirectories.from("io.github", "skylot", "jadx");
}
return dirs;
}
public Path getCacheDir() {
return cacheDir;
}
public Path getConfigDir() {
return configDir;
}
}
}
-4
View File
@@ -7,10 +7,6 @@ dependencies {
implementation("com.google.code.gson:gson:2.10.1")
// TODO: move resources decoding to separate plugin module
implementation("com.android.tools.build:aapt2-proto:8.2.2-10154469")
implementation("com.google.protobuf:protobuf-java:3.25.2") // forcing latest version
testImplementation("org.apache.commons:commons-lang3:3.14.0")
testImplementation(project(":jadx-plugins:jadx-dex-input"))
@@ -8,8 +8,6 @@ import jadx.api.metadata.ICodeAnnotation;
import jadx.api.metadata.ICodeNodeRef;
public interface ICodeWriter {
String NL = System.getProperty("line.separator");
String INDENT_STR = " ";
boolean isMetadataSupported();
+24 -1
View File
@@ -42,6 +42,9 @@ public class JadxArgs implements Closeable {
public static final int DEFAULT_THREADS_COUNT = Math.max(1, Runtime.getRuntime().availableProcessors() / 2);
public static final String DEFAULT_NEW_LINE_STR = System.lineSeparator();
public static final String DEFAULT_INDENT_STR = " ";
public static final String DEFAULT_OUT_DIR = "jadx-output";
public static final String DEFAULT_SRC_DIR = "sources";
public static final String DEFAULT_RES_DIR = "resources";
@@ -107,7 +110,7 @@ public class JadxArgs implements Closeable {
/**
* List of classes and packages (ends with '.*') to exclude from deobfuscation
*/
private List<String> deobfuscationWhitelist = DeobfWhitelist.DEFAULT_LIST;
private List<String> deobfuscationWhitelist = new ArrayList<>(DeobfWhitelist.DEFAULT_LIST);
/**
* Nodes alias provider for deobfuscator and rename visitor
@@ -144,6 +147,10 @@ public class JadxArgs implements Closeable {
private ICodeData codeData;
private String codeNewLineStr = DEFAULT_NEW_LINE_STR;
private String codeIndentStr = DEFAULT_INDENT_STR;
private CommentsLevel commentsLevel = CommentsLevel.INFO;
private IntegerFormat integerFormat = IntegerFormat.AUTO;
@@ -622,6 +629,22 @@ public class JadxArgs implements Closeable {
this.codeData = codeData;
}
public String getCodeIndentStr() {
return codeIndentStr;
}
public void setCodeIndentStr(String codeIndentStr) {
this.codeIndentStr = codeIndentStr;
}
public String getCodeNewLineStr() {
return codeNewLineStr;
}
public void setCodeNewLineStr(String codeNewLineStr) {
this.codeNewLineStr = codeNewLineStr;
}
public CommentsLevel getCommentsLevel() {
return commentsLevel;
}
@@ -28,12 +28,6 @@ public class JadxArgsValidator {
if (inputFiles.isEmpty() && jadx.getCustomCodeLoaders().isEmpty()) {
throw new JadxArgsValidateException("Please specify input file");
}
for (File inputFile : inputFiles) {
String fileName = inputFile.getName();
if (fileName.startsWith("--")) {
throw new JadxArgsValidateException("Unknown argument: " + fileName);
}
}
for (File file : inputFiles) {
checkFile(file);
}
@@ -52,7 +52,6 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.FileUtils;
import jadx.core.utils.tasks.TaskExecutor;
import jadx.core.xmlgen.BinaryXMLParser;
import jadx.core.xmlgen.ProtoXMLParser;
import jadx.core.xmlgen.ResourcesSaver;
/**
@@ -94,10 +93,10 @@ public final class JadxDecompiler implements Closeable {
private List<ResourceFile> resources;
private BinaryXMLParser binaryXmlParser;
private ProtoXMLParser protoXmlParser;
private final IDecompileScheduler decompileScheduler = new DecompilerScheduler();
private final JadxEventsImpl events = new JadxEventsImpl();
private final ResourcesLoader resourcesLoader = new ResourcesLoader(this);
private final List<ICodeLoader> customCodeLoaders = new ArrayList<>();
private final List<CustomResourcesLoader> customResourcesLoaders = new ArrayList<>();
@@ -119,12 +118,11 @@ public final class JadxDecompiler implements Closeable {
loadInputFiles();
root = new RootNode(args);
root.init();
root.setDecompilerRef(this);
root.mergePasses(customPasses);
root.loadClasses(loadedInputs);
root.initClassPath();
root.loadResources(getResources());
root.loadResources(resourcesLoader, getResources());
root.runPreDecompileStage();
root.initPasses();
loadFinished();
@@ -170,7 +168,6 @@ public final class JadxDecompiler implements Closeable {
classes = null;
resources = null;
binaryXmlParser = null;
protoXmlParser = null;
events.reset();
}
@@ -430,7 +427,7 @@ public final class JadxDecompiler implements Closeable {
if (root == null) {
return Collections.emptyList();
}
resources = new ResourcesLoader(this).load();
resources = resourcesLoader.load(root);
}
return resources;
}
@@ -476,13 +473,6 @@ public final class JadxDecompiler implements Closeable {
return binaryXmlParser;
}
synchronized ProtoXMLParser getProtoXmlParser() {
if (protoXmlParser == null) {
protoXmlParser = new ProtoXMLParser(root);
}
return protoXmlParser;
}
/**
* Get JavaClass by ClassNode without loading and decompilation
*/
@@ -704,6 +694,10 @@ public final class JadxDecompiler implements Closeable {
customPasses.computeIfAbsent(pass.getPassType(), l -> new ArrayList<>()).add(pass);
}
public ResourcesLoader getResourcesLoader() {
return resourcesLoader;
}
@Override
public String toString() {
return "jadx decompiler " + getVersion();
@@ -62,6 +62,10 @@ public class ResourceFile {
return deobfName != null ? deobfName : name;
}
public void setDeobfName(String resFullName) {
this.deobfName = resFullName;
}
public ResourceType getType() {
return type;
}
@@ -84,7 +88,7 @@ public class ResourceFile {
}
String alias = sb.toString();
if (!alias.equals(name)) {
deobfName = alias;
setDeobfName(alias);
return true;
}
return false;
@@ -17,30 +17,42 @@ import org.slf4j.LoggerFactory;
import jadx.api.ResourceFile.ZipRef;
import jadx.api.impl.SimpleCodeInfo;
import jadx.api.plugins.CustomResourcesLoader;
import jadx.api.plugins.resources.IResContainerFactory;
import jadx.api.plugins.resources.IResTableParserProvider;
import jadx.api.plugins.resources.IResourcesLoader;
import jadx.api.plugins.utils.ZipSecurity;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.Utils;
import jadx.core.utils.android.Res9patchStreamDecoder;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.FileUtils;
import jadx.core.xmlgen.BinaryXMLParser;
import jadx.core.xmlgen.IResTableParser;
import jadx.core.xmlgen.ResContainer;
import jadx.core.xmlgen.ResProtoParser;
import jadx.core.xmlgen.ResTableParser;
import jadx.core.xmlgen.ResTableBinaryParserProvider;
import static jadx.core.utils.files.FileUtils.READ_BUFFER_SIZE;
import static jadx.core.utils.files.FileUtils.copyStream;
// TODO: move to core package
public final class ResourcesLoader {
public final class ResourcesLoader implements IResourcesLoader {
private static final Logger LOG = LoggerFactory.getLogger(ResourcesLoader.class);
private final JadxDecompiler jadxRef;
private final List<IResTableParserProvider> resTableParserProviders = new ArrayList<>();
private final List<IResContainerFactory> resContainerFactories = new ArrayList<>();
private BinaryXMLParser binaryXmlParser;
ResourcesLoader(JadxDecompiler jadxRef) {
this.jadxRef = jadxRef;
this.resTableParserProviders.add(new ResTableBinaryParserProvider());
}
List<ResourceFile> load() {
List<ResourceFile> load(RootNode root) {
init(root);
List<File> inputFiles = jadxRef.getArgs().getInputFiles();
List<ResourceFile> list = new ArrayList<>(inputFiles.size());
for (File file : inputFiles) {
@@ -49,10 +61,37 @@ public final class ResourcesLoader {
return list;
}
private void init(RootNode root) {
for (IResTableParserProvider resTableParserProvider : resTableParserProviders) {
try {
resTableParserProvider.init(root);
} catch (Exception e) {
throw new JadxRuntimeException("Failed to init res table provider: " + resTableParserProvider);
}
}
for (IResContainerFactory resContainerFactory : resContainerFactories) {
try {
resContainerFactory.init(root);
} catch (Exception e) {
throw new JadxRuntimeException("Failed to init res container factory: " + resContainerFactory);
}
}
}
public interface ResourceDecoder<T> {
T decode(long size, InputStream is) throws IOException;
}
@Override
public void addResContainerFactory(IResContainerFactory resContainerFactory) {
resContainerFactories.add(resContainerFactory);
}
@Override
public void addResTableParserProvider(IResTableParserProvider resTableParserProvider) {
resTableParserProviders.add(resTableParserProvider);
}
public static <T> T decodeStream(ResourceFile rf, ResourceDecoder<T> decoder) throws JadxException {
try {
ZipRef zipRef = rf.getZipRef();
@@ -82,7 +121,8 @@ public final class ResourcesLoader {
static ResContainer loadContent(JadxDecompiler jadxRef, ResourceFile rf) {
try {
return decodeStream(rf, (size, is) -> loadContent(jadxRef, rf, is));
ResourcesLoader resLoader = jadxRef.getResourcesLoader();
return decodeStream(rf, (size, is) -> resLoader.loadContent(rf, is));
} catch (JadxException e) {
LOG.error("Decode error", e);
ICodeWriter cw = jadxRef.getRoot().makeCodeWriter();
@@ -92,36 +132,48 @@ public final class ResourcesLoader {
}
}
private static ResContainer loadContent(JadxDecompiler jadxRef, ResourceFile rf,
InputStream inputStream) throws IOException {
RootNode root = jadxRef.getRoot();
switch (rf.getType()) {
case MANIFEST:
case XML: {
ICodeInfo content;
if (root.isProto()) {
content = jadxRef.getProtoXmlParser().parse(inputStream);
} else {
content = jadxRef.getBinaryXmlParser().parse(inputStream);
}
return ResContainer.textResource(rf.getDeobfName(), content);
private ResContainer loadContent(ResourceFile resFile, InputStream inputStream) throws IOException {
for (IResContainerFactory customFactory : resContainerFactories) {
ResContainer resContainer = customFactory.create(resFile, inputStream);
if (resContainer != null) {
return resContainer;
}
}
switch (resFile.getType()) {
case MANIFEST:
case XML:
ICodeInfo content = loadBinaryXmlParser().parse(inputStream);
return ResContainer.textResource(resFile.getDeobfName(), content);
case ARSC:
if (root.isProto()) {
return new ResProtoParser(root).decodeFiles(inputStream);
} else {
return new ResTableParser(root).decodeFiles(inputStream);
}
return decodeTable(resFile, inputStream).decodeFiles();
case IMG:
return decodeImage(rf, inputStream);
return decodeImage(resFile, inputStream);
default:
return ResContainer.resourceFileLink(rf);
return ResContainer.resourceFileLink(resFile);
}
}
public IResTableParser decodeTable(ResourceFile resFile, InputStream is) throws IOException {
if (resFile.getType() != ResourceType.ARSC) {
throw new IllegalArgumentException("Unexpected resource type for decode: " + resFile.getType() + ", expect '.pb'/'.arsc'");
}
IResTableParser parser = null;
for (IResTableParserProvider provider : resTableParserProviders) {
parser = provider.getParser(resFile);
if (parser != null) {
break;
}
}
if (parser == null) {
throw new JadxRuntimeException("Unknown type of resource file: " + resFile.getOriginalName());
}
parser.decode(is);
return parser;
}
private static ResContainer decodeImage(ResourceFile rf, InputStream inputStream) {
String name = rf.getOriginalName();
if (name.endsWith(".9.png")) {
@@ -184,4 +236,11 @@ public final class ResourcesLoader {
copyStream(is, baos);
return new SimpleCodeInfo(baos.toString("UTF-8"));
}
private synchronized BinaryXMLParser loadBinaryXmlParser() {
if (binaryXmlParser == null) {
binaryXmlParser = new BinaryXMLParser(jadxRef.getRoot());
}
return binaryXmlParser;
}
}
@@ -0,0 +1,69 @@
package jadx.api.data;
public enum CommentStyle {
/**
* <pre>
* // comment
* </pre>
*/
LINE("// ", "// ", ""),
// @formatter:off
/**
* <pre>
* /*
* * comment
* *&#47;
* </pre>
*/
// @formatter:on
BLOCK("/*\n * ", " * ", "\n */"),
/**
* <pre>
* /* comment *&#47;
* </pre>
*/
BLOCK_CONDENSED("/* ", " * ", " */"),
// @formatter:off
/**
* <pre>
* /**
* * comment
* *&#47;
* </pre>
*/
// @formatter:on
JAVADOC("/**\n * ", " * ", "\n */"),
/**
* <pre>
* /** comment *&#47;
* </pre>
*/
JAVADOC_CONDENSED("/** ", " * ", " */");
private final String start;
private final String onNewLine;
private final String end;
CommentStyle(String start, String onNewLine, String end) {
this.start = start;
this.onNewLine = onNewLine;
this.end = end;
}
public String getStart() {
return start;
}
public String getOnNewLine() {
return onNewLine;
}
public String getEnd() {
return end;
}
}
@@ -10,4 +10,6 @@ public interface ICodeComment extends Comparable<ICodeComment> {
IJavaCodeRef getCodeRef();
String getComment();
CommentStyle getStyle();
}
@@ -3,6 +3,7 @@ package jadx.api.data.impl;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import jadx.api.data.CommentStyle;
import jadx.api.data.ICodeComment;
import jadx.api.data.IJavaCodeRef;
import jadx.api.data.IJavaNodeRef;
@@ -13,15 +14,25 @@ public class JadxCodeComment implements ICodeComment {
@Nullable
private IJavaCodeRef codeRef;
private String comment;
private CommentStyle style = CommentStyle.LINE;
public JadxCodeComment(IJavaNodeRef nodeRef, String comment) {
this(nodeRef, null, comment);
}
public JadxCodeComment(IJavaNodeRef nodeRef, String comment, CommentStyle style) {
this(nodeRef, null, comment, style);
}
public JadxCodeComment(IJavaNodeRef nodeRef, @Nullable IJavaCodeRef codeRef, String comment) {
this(nodeRef, codeRef, comment, CommentStyle.LINE);
}
public JadxCodeComment(IJavaNodeRef nodeRef, @Nullable IJavaCodeRef codeRef, String comment, CommentStyle style) {
this.nodeRef = nodeRef;
this.codeRef = codeRef;
this.comment = comment;
this.style = style;
}
public JadxCodeComment() {
@@ -56,6 +67,15 @@ public class JadxCodeComment implements ICodeComment {
this.comment = comment;
}
@Override
public CommentStyle getStyle() {
return style;
}
public void setStyle(CommentStyle style) {
this.style = style;
}
@Override
public int compareTo(@NotNull ICodeComment other) {
int cmpNodeRef = this.getNodeRef().compareTo(other.getNodeRef());
@@ -73,6 +93,7 @@ public class JadxCodeComment implements ICodeComment {
return "JadxCodeComment{" + nodeRef
+ ", ref=" + codeRef
+ ", comment='" + comment + '\''
+ ", style=" + style
+ '}';
}
}
@@ -21,9 +21,6 @@ public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter
private Map<Integer, ICodeAnnotation> annotations = Collections.emptyMap();
private Map<Integer, Integer> lineMap = Collections.emptyMap();
public AnnotatedCodeWriter() {
}
public AnnotatedCodeWriter(JadxArgs args) {
super(args);
}
@@ -35,9 +32,9 @@ public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter
@Override
public AnnotatedCodeWriter addMultiLine(String str) {
if (str.contains(NL)) {
buf.append(str.replace(NL, NL + indentStr));
line += StringUtils.countMatches(str, NL);
if (str.contains(newLineStr)) {
buf.append(str.replace(newLineStr, newLineStr + indentStr));
line += StringUtils.countMatches(str, newLineStr);
offset = 0;
} else {
buf.append(str);
@@ -84,7 +81,7 @@ public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter
@Override
protected void addLine() {
buf.append(NL);
buf.append(newLineStr);
line++;
offset = 0;
}
@@ -154,7 +151,6 @@ public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter
@Override
public ICodeInfo finish() {
processDefinitionAnnotations();
validateAnnotations();
String code = buf.toString();
buf = null;
@@ -166,18 +162,6 @@ public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter
return annotations;
}
private void processDefinitionAnnotations() {
if (!annotations.isEmpty()) {
annotations.forEach((k, v) -> {
if (v instanceof NodeDeclareRef) {
NodeDeclareRef declareRef = (NodeDeclareRef) v;
declareRef.setDefPos(k);
declareRef.getNode().setDefPosition(k);
}
});
}
}
private void validateAnnotations() {
if (annotations.isEmpty()) {
return;
@@ -14,38 +14,39 @@ import jadx.api.metadata.ICodeNodeRef;
import jadx.core.utils.Utils;
/**
* CodeWriter implementation without meta information support (only strings builder)
* CodeWriter implementation without meta information support
*/
public class SimpleCodeWriter implements ICodeWriter {
private static final Logger LOG = LoggerFactory.getLogger(SimpleCodeWriter.class);
private static final String[] INDENT_CACHE = {
"",
INDENT_STR,
INDENT_STR + INDENT_STR,
INDENT_STR + INDENT_STR + INDENT_STR,
INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR,
INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR,
};
protected StringBuilder buf = new StringBuilder();
protected String indentStr = "";
protected int indent = 0;
private final boolean insertLineNumbers;
public SimpleCodeWriter() {
this.insertLineNumbers = false;
}
protected final boolean insertLineNumbers;
protected final String singleIndentStr;
protected final String newLineStr;
public SimpleCodeWriter(JadxArgs args) {
this.insertLineNumbers = args.isInsertDebugLines();
this.singleIndentStr = args.getCodeIndentStr();
this.newLineStr = args.getCodeNewLineStr();
if (insertLineNumbers) {
incIndent(3);
add(indentStr);
}
}
/**
* Constructor with JadxArgs should be used.
*/
@Deprecated
public SimpleCodeWriter() {
this.insertLineNumbers = false;
this.singleIndentStr = JadxArgs.DEFAULT_INDENT_STR;
this.newLineStr = JadxArgs.DEFAULT_NEW_LINE_STR;
}
@Override
public boolean isMetadataSupported() {
return false;
@@ -96,8 +97,8 @@ public class SimpleCodeWriter implements ICodeWriter {
@Override
public SimpleCodeWriter addMultiLine(String str) {
if (str.contains(NL)) {
buf.append(str.replace(NL, NL + indentStr));
if (str.contains(newLineStr)) {
buf.append(str.replace(newLineStr, newLineStr + indentStr));
} else {
buf.append(str);
}
@@ -130,12 +131,12 @@ public class SimpleCodeWriter implements ICodeWriter {
@Override
public SimpleCodeWriter addIndent() {
add(INDENT_STR);
add(singleIndentStr);
return this;
}
protected void addLine() {
buf.append(NL);
buf.append(newLineStr);
}
protected SimpleCodeWriter addLineIndent() {
@@ -144,12 +145,7 @@ public class SimpleCodeWriter implements ICodeWriter {
}
private void updateIndent() {
int curIndent = indent;
if (curIndent < INDENT_CACHE.length) {
this.indentStr = INDENT_CACHE[curIndent];
} else {
this.indentStr = Utils.strRepeat(INDENT_STR, curIndent);
}
this.indentStr = Utils.strRepeat(singleIndentStr, indent);
}
@Override
@@ -219,17 +215,17 @@ public class SimpleCodeWriter implements ICodeWriter {
@Override
public ICodeInfo finish() {
removeFirstEmptyLine();
String code = buf.toString();
String code = getStringWithoutFirstEmptyLine();
buf = null;
return new SimpleCodeInfo(code);
}
protected void removeFirstEmptyLine() {
int len = NL.length();
if (buf.length() > len && buf.substring(0, len).equals(NL)) {
buf.delete(0, len);
private String getStringWithoutFirstEmptyLine() {
int len = newLineStr.length();
if (buf.length() > len && buf.substring(0, len).equals(newLineStr)) {
return buf.substring(len);
}
return buf.toString();
}
@Override
@@ -12,6 +12,7 @@ import jadx.api.plugins.gui.JadxGuiContext;
import jadx.api.plugins.input.JadxCodeInput;
import jadx.api.plugins.options.JadxPluginOptions;
import jadx.api.plugins.pass.JadxPass;
import jadx.api.plugins.resources.IResourcesLoader;
public interface JadxPluginContext {
@@ -32,6 +33,11 @@ public interface JadxPluginContext {
*/
void registerInputsHashSupplier(Supplier<String> supplier);
/**
* Customize resource loading
*/
IResourcesLoader getResourcesLoader();
/**
* Access to jadx-gui specific methods
*/
@@ -12,12 +12,12 @@ public enum OptionFlag {
HIDE_IN_GUI,
/**
* Do not show this option in jadx-gui (useful if option is configured with custom ui)
* Option will be read-only in jadx-gui (can be used for calculated properties)
*/
DISABLE_IN_GUI,
/**
* Add this flag only if option do not affect generated code.
* Add this flag only if the option does not affect generated code.
* If added, option value change will not cause code cache reset.
*/
NOT_CHANGING_CODE,
@@ -64,6 +64,14 @@ public abstract class BasePluginOptionsBuilder implements JadxPluginOptions {
.parser(v -> v));
}
public OptionBuilder<Integer> intOption(String name) {
return addOption(
new OptionData<Integer>(name)
.type(OptionType.NUMBER)
.formatter(Object::toString)
.parser(Integer::parseInt));
}
public <E extends Enum<?>> OptionBuilder<E> enumOption(String name, E[] values, Function<String, E> valueOf) {
return addOption(
new OptionData<E>(name)
@@ -0,0 +1,33 @@
package jadx.api.plugins.resources;
import java.io.IOException;
import java.io.InputStream;
import org.jetbrains.annotations.Nullable;
import jadx.api.ResourceFile;
import jadx.core.dex.nodes.RootNode;
import jadx.core.xmlgen.ResContainer;
/**
* Factory for {@link ResContainer}. Can be used in plugins via
* {@code context.getResourcesLoader().addResContainerFactory()} to implement content parsing in
* files with
* different formats.
*/
public interface IResContainerFactory {
/**
* Optional init method
*/
default void init(RootNode root) {
}
/**
* Checks if resource file is of expected format and tries to parse its content.
*
* @return {@link ResContainer} if file is of expected format, {@code null} otherwise.
*/
@Nullable
ResContainer create(ResourceFile resFile, InputStream inputStream) throws IOException;
}
@@ -0,0 +1,30 @@
package jadx.api.plugins.resources;
import org.jetbrains.annotations.Nullable;
import jadx.api.ResourceFile;
import jadx.core.dex.nodes.RootNode;
import jadx.core.xmlgen.IResTableParser;
/**
* Provides the resource table parser instance for specific resource table file format. Can be used
* in plugins via {@code context.getResourcesLoader().addResTableParserProvider()} to parse
* resources from tables
* in different formats.
*/
public interface IResTableParserProvider {
/**
* Optional init method
*/
default void init(RootNode root) {
}
/**
* Checks a file format and provides the instance if the format is expected.
*
* @return {@link IResTableParser} if resource table is of expected format, {@code null} otherwise.
*/
@Nullable
IResTableParser getParser(ResourceFile resFile);
}
@@ -0,0 +1,8 @@
package jadx.api.plugins.resources;
public interface IResourcesLoader {
void addResContainerFactory(IResContainerFactory resContainerFactory);
void addResTableParserProvider(IResTableParserProvider resTableParserProvider);
}
@@ -1,7 +1,5 @@
package jadx.api.utils;
import jadx.api.ICodeWriter;
public class CodeUtils {
public static String getLineForPos(String code, int pos) {
@@ -11,18 +9,32 @@ public class CodeUtils {
}
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();
int start = getNewLinePosBefore(code, pos);
return start == -1 ? 0 : start + 1;
}
public static int getLineEndForPos(String code, int pos) {
int end = code.indexOf(ICodeWriter.NL, pos);
int end = getNewLinePosAfter(code, pos);
return end == -1 ? code.length() : end;
}
public static int getLineNumForPos(String code, int pos) {
String newLine = ICodeWriter.NL;
public static int getNewLinePosAfter(String code, int startPos) {
int pos = code.indexOf('\n', startPos);
if (pos != -1) {
// check for '\r\n'
int prev = pos - 1;
if (code.charAt(prev) == '\r') {
return prev;
}
}
return pos;
}
public static int getNewLinePosBefore(String code, int startPos) {
return code.lastIndexOf('\n', startPos);
}
public static int getLineNumForPos(String code, int pos, String newLine) {
int newLineLen = newLine.length();
int line = 1;
int prev = 0;
@@ -13,6 +13,7 @@ import org.slf4j.LoggerFactory;
import jadx.api.CommentsLevel;
import jadx.api.JadxArgs;
import jadx.core.deobf.DeobfuscatorVisitor;
import jadx.core.deobf.InitRenameProviders;
import jadx.core.deobf.SaveDeobfMapping;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.visitors.AnonymousClassVisitor;
@@ -54,6 +55,8 @@ import jadx.core.dex.visitors.debuginfo.DebugInfoApplyVisitor;
import jadx.core.dex.visitors.debuginfo.DebugInfoAttachVisitor;
import jadx.core.dex.visitors.finaly.MarkFinallyVisitor;
import jadx.core.dex.visitors.kotlin.ProcessKotlinInternals;
import jadx.core.dex.visitors.prepare.AddAndroidConstants;
import jadx.core.dex.visitors.prepare.CollectConstValues;
import jadx.core.dex.visitors.regions.CheckRegions;
import jadx.core.dex.visitors.regions.CleanRegions;
import jadx.core.dex.visitors.regions.IfRegionVisitor;
@@ -67,6 +70,7 @@ import jadx.core.dex.visitors.rename.SourceFileRename;
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
import jadx.core.dex.visitors.ssa.SSATransform;
import jadx.core.dex.visitors.typeinference.FinishTypeInference;
import jadx.core.dex.visitors.typeinference.FixTypesVisitor;
import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor;
import jadx.core.dex.visitors.usage.UsageInfoVisitor;
import jadx.core.utils.exceptions.JadxRuntimeException;
@@ -95,8 +99,11 @@ public class Jadx {
List<IDexTreeVisitor> passes = new ArrayList<>();
passes.add(new SignatureProcessor());
passes.add(new OverrideMethodVisitor());
passes.add(new AddAndroidConstants());
passes.add(new CollectConstValues());
// rename and deobfuscation
passes.add(new InitRenameProviders());
passes.add(new DeobfuscatorVisitor());
passes.add(new SourceFileRename());
passes.add(new RenameVisitor());
@@ -141,7 +148,9 @@ public class Jadx {
if (args.isDebugInfo()) {
passes.add(new DebugInfoApplyVisitor());
}
passes.add(new FixTypesVisitor());
passes.add(new FinishTypeInference());
if (args.getUseKotlinMethodsForVarNames() != JadxArgs.UseKotlinMethodsForVarNames.DISABLE) {
passes.add(new ProcessKotlinInternals());
}
@@ -216,6 +225,7 @@ public class Jadx {
if (args.isDebugInfo()) {
passes.add(new DebugInfoApplyVisitor());
}
passes.add(new FixTypesVisitor());
passes.add(new FinishTypeInference());
passes.add(new CodeRenameVisitor());
passes.add(new DeboxingVisitor());
@@ -44,14 +44,17 @@ public class ClsSet {
private static final String CLST_PATH = "/clst/" + CLST_FILENAME;
private static final String JADX_CLS_SET_HEADER = "jadx-cst";
private static final int VERSION = 4;
private static final int VERSION = 5;
private static final String STRING_CHARSET = "US-ASCII";
private static final ArgType[] EMPTY_ARGTYPE_ARRAY = new ArgType[0];
private static final ArgType[] OBJECT_ARGTYPE_ARRAY = new ArgType[] { ArgType.OBJECT };
private final RootNode root;
private int androidApiLevel;
public ClsSet(RootNode root) {
this.root = root;
}
@@ -79,7 +82,8 @@ public class ClsSet {
if (LOG.isDebugEnabled()) {
long time = System.currentTimeMillis() - startTime;
int methodsCount = Stream.of(classes).mapToInt(clspClass -> clspClass.getMethodsMap().size()).sum();
LOG.debug("Clst file loaded in {}ms, classes: {}, methods: {}", time, classes.length, methodsCount);
LOG.debug("Clst file loaded in {}ms, android api: {}, classes: {}, methods: {}",
time, androidApiLevel, classes.length, methodsCount);
}
}
@@ -93,7 +97,7 @@ public class ClsSet {
cls.load();
ClspClassSource source = getClspClassSource(cls);
ClspClass nClass = new ClspClass(clsType, k, source);
ClspClass nClass = new ClspClass(clsType, k, cls.getAccessFlags().rawValue(), source);
if (names.put(clsRawName, nClass) != null) {
throw new JadxRuntimeException("Duplicate class: " + clsRawName);
}
@@ -151,7 +155,11 @@ public class ClsSet {
// cls is java.lang.Object
return EMPTY_ARGTYPE_ARRAY;
}
ArgType[] parents = new ArgType[1 + cls.getInterfaces().size()];
int interfacesCount = cls.getInterfaces().size();
if (interfacesCount == 0 && superClass == ArgType.OBJECT) {
return OBJECT_ARGTYPE_ARRAY;
}
ArgType[] parents = new ArgType[1 + interfacesCount];
parents[0] = superClass;
int k = 1;
for (ArgType iface : cls.getInterfaces()) {
@@ -193,10 +201,12 @@ public class ClsSet {
DataOutputStream out = new DataOutputStream(output);
out.writeBytes(JADX_CLS_SET_HEADER);
out.writeByte(VERSION);
out.writeInt(androidApiLevel);
Map<String, ClspClass> names = new HashMap<>(classes.length);
out.writeInt(classes.length);
for (ClspClass cls : classes) {
out.writeInt(cls.getAccFlags());
writeUnsignedByte(out, cls.getSource().ordinal());
String clsName = cls.getName();
writeString(out, clsName);
@@ -243,6 +253,10 @@ public class ClsSet {
out.writeByte(-1);
return;
}
if (arr == OBJECT_ARGTYPE_ARRAY) {
out.writeByte(-2);
return;
}
int size = arr.length;
out.writeByte(size);
if (size != 0) {
@@ -294,22 +308,22 @@ public class ClsSet {
try (DataInputStream in = new DataInputStream(new BufferedInputStream(input))) {
byte[] header = new byte[JADX_CLS_SET_HEADER.length()];
int readHeaderLength = in.read(header);
int version = in.readByte();
if (readHeaderLength != JADX_CLS_SET_HEADER.length()
|| !JADX_CLS_SET_HEADER.equals(new String(header, STRING_CHARSET))
|| version != VERSION) {
|| !JADX_CLS_SET_HEADER.equals(new String(header, STRING_CHARSET))) {
throw new DecodeException("Wrong jadx class set header");
}
int version = in.readByte();
if (version != VERSION) {
throw new DecodeException("Wrong jadx class set version, got: " + version + ", expect: " + VERSION);
}
androidApiLevel = in.readInt();
int clsCount = in.readInt();
classes = new ClspClass[clsCount];
ClspClassSource[] clspClassSources = ClspClassSource.values();
for (int i = 0; i < clsCount; i++) {
int source = readUnsignedByte(in);
if (source < 0 || source > clspClassSources.length) {
throw new DecodeException("Wrong jadx source identifier");
}
int accFlags = in.readInt();
ClspClassSource clsSource = readClsSource(in);
String name = readString(in);
classes[i] = new ClspClass(ArgType.object(name), i, clspClassSources[source]);
classes[i] = new ClspClass(ArgType.object(name), i, accFlags, clsSource);
}
for (int i = 0; i < clsCount; i++) {
ClspClass nClass = classes[i];
@@ -321,6 +335,15 @@ public class ClsSet {
}
}
private static ClspClassSource readClsSource(DataInputStream in) throws IOException, DecodeException {
int source = readUnsignedByte(in);
ClspClassSource[] clspClassSources = ClspClassSource.values();
if (source < 0 || source > clspClassSources.length) {
throw new DecodeException("Wrong jadx source identifier: " + source);
}
return clspClassSources[source];
}
private List<ClspMethod> readClsMethods(DataInputStream in, ClassInfo clsInfo) throws IOException {
int mCount = in.readShort();
List<ClspMethod> methods = new ArrayList<>(mCount);
@@ -366,17 +389,20 @@ public class ClsSet {
@Nullable
private ArgType[] readArgTypesArray(DataInputStream in) throws IOException {
int count = in.readByte();
if (count == -1) {
return null;
switch (count) {
case -1:
return null;
case -2:
return OBJECT_ARGTYPE_ARRAY;
case 0:
return EMPTY_ARGTYPE_ARRAY;
default:
ArgType[] arr = new ArgType[count];
for (int i = 0; i < count; i++) {
arr[i] = readArgType(in);
}
return arr;
}
if (count == 0) {
return EMPTY_ARGTYPE_ARRAY;
}
ArgType[] arr = new ArgType[count];
for (int i = 0; i < count; i++) {
arr[i] = readArgType(in);
}
return arr;
}
private ArgType readArgType(DataInputStream in) throws IOException {
@@ -384,9 +410,6 @@ public class ClsSet {
if (ordinal == -1) {
return null;
}
if (ordinal >= TypeEnum.values().length) {
throw new JadxRuntimeException("Incorrect ordinal for type enum: " + ordinal);
}
switch (TypeEnum.values()[ordinal]) {
case WILDCARD:
ArgType.WildcardBound bound = ArgType.WildcardBound.getByNum(in.readByte());
@@ -414,7 +437,7 @@ public class ClsSet {
return classes[in.readInt()].getClsType();
case ARRAY:
return ArgType.array(readArgType(in));
return ArgType.array(Objects.requireNonNull(readArgType(in)));
case PRIMITIVE:
char shortName = (char) in.readByte();
@@ -474,4 +497,12 @@ public class ClsSet {
nameMap.put(cls.getName(), cls);
}
}
public int getAndroidApiLevel() {
return androidApiLevel;
}
public void setAndroidApiLevel(int androidApiLevel) {
this.androidApiLevel = androidApiLevel;
}
}
@@ -7,6 +7,9 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.intellij.lang.annotations.MagicConstant;
import jadx.api.plugins.input.data.AccessFlags;
import jadx.core.dex.instructions.args.ArgType;
/**
@@ -16,21 +19,17 @@ public class ClspClass {
private final ArgType clsType;
private final int id;
private final int accFlags;
private ArgType[] parents;
private Map<String, ClspMethod> methodsMap = Collections.emptyMap();
private List<ArgType> typeParameters = Collections.emptyList();
private ClspClassSource source;
public ClspClass(ArgType clsType, int id) {
this.clsType = clsType;
this.id = id;
this.source = ClspClassSource.APP;
}
public ClspClass(ArgType clsType, int id, ClspClassSource source) {
public ClspClass(ArgType clsType, int id, int accFlags, ClspClassSource source) {
this.clsType = clsType;
this.id = id;
this.accFlags = accFlags;
this.source = source;
}
@@ -46,6 +45,18 @@ public class ClspClass {
return id;
}
public int getAccFlags() {
return accFlags;
}
public boolean isInterface() {
return AccessFlags.hasFlag(accFlags, AccessFlags.INTERFACE);
}
public boolean hasAccFlag(@MagicConstant(flagsFromClass = AccessFlags.class) int flags) {
return AccessFlags.hasFlag(accFlags, flags);
}
public ArgType[] getParents() {
return parents;
}
@@ -13,6 +13,7 @@ import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.Consts;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.ClassNode;
@@ -106,7 +107,7 @@ public class ClspGraph {
private void addClass(ClassNode cls) {
ArgType clsType = cls.getClassInfo().getType();
String rawName = clsType.getObject();
ClspClass clspClass = new ClspClass(clsType, -1);
ClspClass clspClass = new ClspClass(clsType, -1, cls.getAccessFlags().rawValue(), ClspClassSource.APP);
clspClass.setParents(ClsSet.makeParentsArray(cls));
nameMap.put(rawName, clspClass);
}
@@ -174,6 +175,8 @@ public class ClspGraph {
return result == null ? Collections.emptySet() : result;
}
private static final Set<String> OBJECT_SINGLE_SET = Collections.singleton(Consts.CLASS_OBJECT);
private void fillSuperTypesCache() {
Map<String, Set<String>> map = new HashMap<>(nameMap.size());
Set<String> tmpSet = new HashSet<>();
@@ -182,10 +185,25 @@ public class ClspGraph {
tmpSet.clear();
addSuperTypes(cls, tmpSet);
Set<String> result;
if (tmpSet.isEmpty()) {
result = Collections.emptySet();
} else {
result = new HashSet<>(tmpSet);
int size = tmpSet.size();
switch (size) {
case 0: {
result = Collections.emptySet();
break;
}
case 1: {
String supCls = tmpSet.iterator().next();
if (supCls.equals(Consts.CLASS_OBJECT)) {
result = OBJECT_SINGLE_SET;
} else {
result = Collections.singleton(supCls);
}
break;
}
default: {
result = new HashSet<>(tmpSet);
break;
}
}
map.put(cls.getName(), result);
}
@@ -26,6 +26,7 @@ import jadx.api.plugins.input.data.annotations.EncodedType;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.core.Consts;
import jadx.core.codegen.utils.CodeGenUtils;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.FieldInitInsnAttr;
@@ -45,7 +46,6 @@ import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.CodeGenUtils;
import jadx.core.utils.EncodedValueUtils;
import jadx.core.utils.Utils;
import jadx.core.utils.android.AndroidResourcesUtils;
@@ -421,18 +421,22 @@ public class ClassGen {
if (f.contains(AFlag.DONT_GENERATE)) {
return;
}
if (f.contains(JadxAttrType.ANNOTATION_LIST)
|| f.contains(AType.JADX_COMMENTS)
|| f.contains(AType.CODE_COMMENTS)
|| f.getFieldInfo().hasAlias()) {
code.newLine();
}
if (Consts.DEBUG_USAGE) {
addFieldUsageInfo(code, f);
}
if (f.getFieldInfo().hasAlias()) {
CodeGenUtils.addRenamedComment(code, f, f.getName());
}
CodeGenUtils.addComments(code, f);
annotationGen.addForField(code, f);
boolean addInfoComments = f.checkCommentsLevel(CommentsLevel.INFO);
if (f.getFieldInfo().hasAlias() && addInfoComments) {
code.newLine();
CodeGenUtils.addRenamedComment(code, f, f.getName());
}
code.startLine(f.getAccessFlags().makeString(addInfoComments));
code.startLine(f.getAccessFlags().makeString(f.checkCommentsLevel(CommentsLevel.INFO)));
useType(code, f.getType());
code.add(' ');
code.attachDefinition(f);
@@ -15,6 +15,7 @@ import jadx.api.metadata.annotations.InsnCodeOffset;
import jadx.api.metadata.annotations.VarNode;
import jadx.api.plugins.input.data.MethodHandleType;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.core.codegen.utils.CodeGenUtils;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.FieldInitInsnAttr;
@@ -60,7 +61,6 @@ import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.CodeGenUtils;
import jadx.core.utils.RegionUtils;
import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.JadxRuntimeException;
@@ -936,7 +936,7 @@ public class InsnGen {
makeInlinedLambdaMethod(code, customNode, callMth);
}
private void makeRefLambda(ICodeWriter code, InvokeCustomNode customNode) {
private void makeRefLambda(ICodeWriter code, InvokeCustomNode customNode) throws CodegenException {
InsnNode callInsn = customNode.getCallInsn();
if (callInsn instanceof ConstructorInsn) {
MethodInfo callMth = ((ConstructorInsn) callInsn).getCallMth();
@@ -950,7 +950,7 @@ public class InsnGen {
if (customNode.getHandleType() == MethodHandleType.INVOKE_STATIC) {
useClass(code, callMth.getDeclClass());
} else {
code.add("this");
addArg(code, customNode.getArg(0));
}
code.add("::").add(callMth.getAlias());
}
@@ -22,6 +22,7 @@ import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.api.plugins.input.data.attributes.types.AnnotationMethodParamsAttr;
import jadx.core.Consts;
import jadx.core.Jadx;
import jadx.core.codegen.utils.CodeGenUtils;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.JadxError;
@@ -43,7 +44,6 @@ import jadx.core.dex.trycatch.CatchAttr;
import jadx.core.dex.trycatch.ExceptionHandler;
import jadx.core.dex.visitors.DepthTraversal;
import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.utils.CodeGenUtils;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.JadxOverflowException;
@@ -5,6 +5,7 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -12,14 +13,18 @@ import jadx.api.CommentsLevel;
import jadx.api.ICodeWriter;
import jadx.api.metadata.annotations.InsnCodeOffset;
import jadx.api.metadata.annotations.VarNode;
import jadx.api.plugins.input.data.AccessFlags;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.core.clsp.ClspClass;
import jadx.core.codegen.utils.CodeGenUtils;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
import jadx.core.dex.attributes.nodes.ForceReturnAttr;
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.SwitchInsn;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.CodeVar;
@@ -44,7 +49,6 @@ import jadx.core.dex.regions.loops.LoopRegion;
import jadx.core.dex.regions.loops.LoopType;
import jadx.core.dex.trycatch.ExceptionHandler;
import jadx.core.utils.BlockUtils;
import jadx.core.utils.CodeGenUtils;
import jadx.core.utils.RegionUtils;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.CodegenException;
@@ -268,19 +272,10 @@ public class RegionGen extends InsnGen {
private void addCaseKey(ICodeWriter code, InsnArg arg, Object k) throws CodegenException {
if (k instanceof FieldNode) {
FieldNode fn = (FieldNode) k;
if (fn.getParentClass().isEnum()) {
code.add(fn.getAlias());
} else {
staticField(code, fn.getFieldInfo());
if (mth.checkCommentsLevel(CommentsLevel.INFO)) {
// print original value, sometimes replaced with incorrect field
EncodedValue constVal = fn.get(JadxAttrType.CONSTANT_VALUE);
if (constVal != null && constVal.getValue() != null) {
code.add(" /* ").add(constVal.getValue().toString()).add(" */");
}
}
}
FieldNode fld = (FieldNode) k;
useField(code, fld.getFieldInfo(), fld);
} else if (k instanceof FieldInfo) {
useField(code, (FieldInfo) k, null);
} else if (k instanceof Integer) {
code.add(TypeGen.literalToString((Integer) k, arg.getType(), mth, fallback));
} else {
@@ -288,6 +283,28 @@ public class RegionGen extends InsnGen {
}
}
private void useField(ICodeWriter code, FieldInfo fldInfo, @Nullable FieldNode fld) throws CodegenException {
boolean isEnum;
if (fld != null) {
isEnum = fld.getParentClass().isEnum();
} else {
ClspClass clsDetails = root.getClsp().getClsDetails(fldInfo.getDeclClass().getType());
isEnum = clsDetails != null && clsDetails.hasAccFlag(AccessFlags.ENUM);
}
if (isEnum) {
code.add(fldInfo.getAlias());
return;
}
staticField(code, fldInfo);
if (fld != null && mth.checkCommentsLevel(CommentsLevel.INFO)) {
// print original value, sometimes replaced with incorrect field
EncodedValue constVal = fld.get(JadxAttrType.CONSTANT_VALUE);
if (constVal != null && constVal.getValue() != null) {
code.add(" /* ").add(constVal.getValue().toString()).add(" */");
}
}
}
public void makeTryCatch(TryCatchRegion region, ICodeWriter code) throws CodegenException {
code.startLine("try {");
@@ -56,7 +56,7 @@ public class SimpleModeHelper {
if (!block.contains(AFlag.EXC_BOTTOM_SPLITTER)) {
startLabel.set(block.getId());
}
if (prev.getSuccessors().size() == 1 && !mth.isPreExitBlocks(prev)) {
if (prev.getSuccessors().size() == 1 && !mth.isPreExitBlock(prev)) {
endGoto.set(prev.getId());
}
}
@@ -68,7 +68,7 @@ public class SimpleModeHelper {
if (block.contains(AType.EXC_HANDLER)) {
startLabel.set(block.getId());
}
if (nextBlock == null && !mth.isPreExitBlocks(block)) {
if (nextBlock == null && !mth.isPreExitBlock(block)) {
endGoto.set(block.getId());
}
prev = block;
@@ -145,7 +145,7 @@ public class SimpleModeHelper {
// DFS sort blocks to reduce goto count
private List<BlockNode> getSortedBlocks() {
List<BlockNode> list = new ArrayList<>(mth.getBasicBlocks().size());
BlockUtils.dfsVisit(mth, list::add);
BlockUtils.visitDFS(mth, list::add);
return list;
}
}
@@ -24,6 +24,7 @@ import jadx.core.codegen.json.cls.JsonClass;
import jadx.core.codegen.json.cls.JsonCodeLine;
import jadx.core.codegen.json.cls.JsonField;
import jadx.core.codegen.json.cls.JsonMethod;
import jadx.core.codegen.utils.CodeGenUtils;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.instructions.args.ArgType;
@@ -31,7 +32,6 @@ import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.CodeGenUtils;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
@@ -86,7 +86,7 @@ public class JsonCodeGen {
jsonCls.setInterfaces(Utils.collectionMap(cls.getInterfaces(), clsType -> getTypeAlias(classGen, clsType)));
}
ICodeWriter cw = new SimpleCodeWriter();
ICodeWriter cw = new SimpleCodeWriter(args);
CodeGenUtils.addErrorsAndComments(cw, cls);
classGen.addClassDeclaration(cw);
jsonCls.setDeclaration(cw.getCodeStr());
@@ -130,7 +130,7 @@ public class JsonCodeGen {
jsonField.setAlias(field.getAlias());
}
ICodeWriter cw = new SimpleCodeWriter();
ICodeWriter cw = new SimpleCodeWriter(args);
classGen.addField(cw, field);
jsonField.setDeclaration(cw.getCodeStr());
jsonField.setAccessFlags(field.getAccessFlags().rawValue());
@@ -154,7 +154,7 @@ public class JsonCodeGen {
jsonMth.setArguments(Utils.collectionMap(mth.getMethodInfo().getArgumentsTypes(), clsType -> getTypeAlias(classGen, clsType)));
MethodGen mthGen = new MethodGen(classGen, mth);
ICodeWriter cw = new AnnotatedCodeWriter();
ICodeWriter cw = new AnnotatedCodeWriter(args);
mthGen.addDefinition(cw);
jsonMth.setDeclaration(cw.getCodeStr());
jsonMth.setAccessFlags(mth.getAccessFlags().rawValue());
@@ -181,7 +181,7 @@ public class JsonCodeGen {
return Collections.emptyList();
}
String[] lines = codeStr.split(ICodeWriter.NL);
String[] lines = codeStr.split(args.getCodeNewLineStr());
Map<Integer, Integer> lineMapping = code.getCodeMetadata().getLineMapping();
ICodeMetadata metadata = code.getCodeMetadata();
long mthCodeOffset = mth.getMethodCodeOffset() + 16;
@@ -189,7 +189,7 @@ public class JsonCodeGen {
int linesCount = lines.length;
List<JsonCodeLine> codeLines = new ArrayList<>(linesCount);
int lineStartPos = 0;
int newLineLen = ICodeWriter.NL.length();
int newLineLen = args.getCodeNewLineStr().length();
for (int i = 0; i < linesCount; i++) {
String codeLine = lines[i];
int line = i + 2;
@@ -208,7 +208,7 @@ public class JsonCodeGen {
}
private String getTypeAlias(ClassGen classGen, ArgType clsType) {
ICodeWriter code = new SimpleCodeWriter();
ICodeWriter code = new SimpleCodeWriter(args);
classGen.useType(code, clsType);
return code.getCodeStr();
}
@@ -0,0 +1,32 @@
package jadx.core.codegen.utils;
import jadx.api.data.CommentStyle;
import jadx.api.data.ICodeComment;
public class CodeComment {
private final String comment;
private final CommentStyle style;
public CodeComment(String comment, CommentStyle style) {
this.comment = comment;
this.style = style;
}
public CodeComment(ICodeComment comment) {
this(comment.getComment(), comment.getStyle());
}
public String getComment() {
return comment;
}
public CommentStyle getStyle() {
return style;
}
@Override
public String toString() {
return "CodeComment{" + style + ": '" + comment + "'}";
}
}
@@ -1,12 +1,13 @@
package jadx.core.utils;
package jadx.core.codegen.utils;
import java.util.List;
import java.util.regex.Pattern;
import org.jetbrains.annotations.Nullable;
import jadx.api.CommentsLevel;
import jadx.api.ICodeWriter;
import jadx.api.metadata.ICodeAnnotation;
import jadx.api.data.CommentStyle;
import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.api.plugins.input.data.attributes.types.SourceFileAttr;
import jadx.core.dex.attributes.AType;
@@ -20,6 +21,7 @@ import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.ICodeNode;
import jadx.core.utils.Utils;
public class CodeGenUtils {
@@ -72,48 +74,36 @@ public class CodeGenUtils {
if (node == null) {
return;
}
List<String> comments = node.getAll(AType.CODE_COMMENTS);
if (comments.isEmpty()) {
return;
boolean startNewLine = node instanceof ICodeNode; // add on same line for instructions
for (CodeComment comment : node.getAll(AType.CODE_COMMENTS)) {
addCodeComment(code, comment, startNewLine);
}
if (node instanceof ICodeNode) {
// for classes, fields and methods add one line before node declaration
}
private static void addCodeComment(ICodeWriter code, CodeComment comment, boolean startNewLine) {
if (startNewLine) {
code.startLine();
} else {
code.add(' ');
}
if (comments.size() == 1) {
String comment = comments.get(0);
if (!comment.contains("\n")) {
code.add("// ").add(comment);
return;
}
}
addMultiLineComment(code, comments);
CommentStyle style = comment.getStyle();
appendMultiLineString(code, "", style.getStart());
appendMultiLineString(code, style.getOnNewLine(), comment.getComment());
appendMultiLineString(code, "", style.getEnd());
}
private static void addMultiLineComment(ICodeWriter code, List<String> comments) {
boolean first = true;
String indent = "";
ICodeAnnotation lineAnn = null;
for (String comment : comments) {
for (String line : comment.split("\n")) {
if (first) {
first = false;
StringBuilder buf = code.getRawBuf();
int startLinePos = buf.lastIndexOf(ICodeWriter.NL) + 1;
indent = Utils.strRepeat(" ", buf.length() - startLinePos);
if (code.isMetadataSupported()) {
lineAnn = code.getRawAnnotations().get(startLinePos);
}
} else {
code.newLine().add(indent);
if (lineAnn != null) {
code.attachLineAnnotation(lineAnn);
}
}
code.add("// ").add(line);
}
private static final Pattern NEW_LINE_PATTERN = Pattern.compile("\\R");
private static void appendMultiLineString(ICodeWriter code, String onNewLine, String str) {
String[] lines = NEW_LINE_PATTERN.split(str);
int linesCount = lines.length;
if (linesCount == 0) {
return;
}
code.add(lines[0]);
for (int i = 1; i < linesCount; i++) {
code.startLine(onNewLine);
code.add(lines[i]);
}
}
@@ -124,8 +114,7 @@ public class CodeGenUtils {
code.startLine("/* renamed from: ").add(origName);
RenameReasonAttr renameReasonAttr = node.get(AType.RENAME_REASON);
if (renameReasonAttr != null) {
code.add(" reason: ");
code.add(renameReasonAttr.getDescription());
code.add(", reason: ").add(renameReasonAttr.getDescription());
}
code.add(" */");
}
@@ -0,0 +1,20 @@
package jadx.core.deobf;
import jadx.api.JadxArgs;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.AbstractVisitor;
import jadx.core.utils.exceptions.JadxException;
public class InitRenameProviders extends AbstractVisitor {
@Override
public void init(RootNode root) throws JadxException {
JadxArgs args = root.getArgs();
if (args.isDeobfuscationOn() || !args.getRenameFlags().isEmpty()) {
args.getAliasProvider().init(root);
}
if (args.isDeobfuscationOn()) {
args.getRenameCondition().init(root);
}
}
}
@@ -5,39 +5,81 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.PackageNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.Utils;
public class DeobfWhitelist extends AbstractDeobfCondition {
private static final Logger LOG = LoggerFactory.getLogger(DeobfWhitelist.class);
public static final List<String> DEFAULT_LIST = Arrays.asList(
"android.support.v4.*",
"android.support.v7.*",
"android.support.v4.os.*",
"android.support.annotation.Px",
"android.support.*",
"android.os.*",
"androidx.core.os.*",
"androidx.annotation.Px");
"androidx.annotation.*");
public static final String DEFAULT_STR = Utils.listToString(DEFAULT_LIST, " ");
private final Set<String> packages = new HashSet<>();
private final Set<String> classes = new HashSet<>();
private final Set<ClassNode> classes = new HashSet<>();
private boolean reportMissingItems = false;
@Override
public void init(RootNode root) {
packages.clear();
classes.clear();
for (String whitelistItem : root.getArgs().getDeobfuscationWhitelist()) {
if (!whitelistItem.isEmpty()) {
if (whitelistItem.endsWith(".*")) {
packages.add(whitelistItem.substring(0, whitelistItem.length() - 2));
} else {
classes.add(whitelistItem);
}
List<String> excludeList = root.getArgs().getDeobfuscationWhitelist();
reportMissingItems = !excludeList.equals(DEFAULT_LIST);
for (String name : excludeList) {
if (name.isEmpty()) {
continue;
}
if (name.endsWith(".*")) {
excludePackage(root, name.substring(0, name.length() - 2));
} else {
excludeClass(root, name);
}
}
LOG.debug("Excluded from deobfuscation: {} packages, {} classes", packages.size(), classes.size());
}
private void excludeClass(RootNode root, String clsFullName) {
ClassNode cls = root.resolveClass(clsFullName);
if (cls == null) {
if (reportMissingItems) {
LOG.info("Can't exclude from deobfuscation: class '{}' not found", clsFullName);
}
return;
}
excludeClsNode(cls);
}
private void excludeClsNode(ClassNode cls) {
classes.add(cls);
cls.addInfoComment("Class excluded from deobfuscation");
}
private void excludePackage(RootNode root, String fullPkgName) {
PackageNode pkg = root.resolvePackage(fullPkgName);
if (pkg == null) {
if (reportMissingItems) {
LOG.info("Can't exclude from deobfuscation: package '{}' not found", fullPkgName);
}
return;
}
excludePkgNode(pkg);
}
private void excludePkgNode(PackageNode pkg) {
packages.add(pkg.getFullName());
pkg.getClasses().forEach(this::excludeClsNode);
pkg.getSubPackages().forEach(this::excludePkgNode);
}
@Override
@@ -50,9 +92,19 @@ public class DeobfWhitelist extends AbstractDeobfCondition {
@Override
public Action check(ClassNode cls) {
if (classes.contains(cls.getClassInfo().getFullName())) {
if (classes.contains(cls)) {
return Action.FORBID_RENAME;
}
return Action.NO_ACTION;
}
@Override
public Action check(FieldNode fld) {
return check(fld.getParentClass());
}
@Override
public Action check(MethodNode mth) {
return check(mth.getParentClass());
}
}
@@ -106,4 +106,5 @@ public enum AFlag {
DONT_UNLOAD_CLASS, // don't unload class after code generation (only for tests and debug!)
RESOLVE_JAVA_JSR,
COMPUTE_POST_DOM,
}
@@ -2,6 +2,7 @@ package jadx.core.dex.attributes;
import jadx.api.plugins.input.data.attributes.IJadxAttrType;
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
import jadx.core.codegen.utils.CodeComment;
import jadx.core.dex.attributes.nodes.AnonymousClassAttr;
import jadx.core.dex.attributes.nodes.ClassTypeVarsAttr;
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
@@ -44,7 +45,7 @@ import jadx.core.dex.trycatch.TryCatchBlockAttr;
public final class AType<T extends IJadxAttribute> implements IJadxAttrType<T> {
// class, method, field, insn
public static final AType<AttrList<String>> CODE_COMMENTS = new AType<>();
public static final AType<AttrList<CodeComment>> CODE_COMMENTS = new AType<>();
// class, method, field
public static final AType<RenameReasonAttr> RENAME_REASON = new AType<>();
@@ -139,14 +139,12 @@ public abstract class AttrNode implements IAttributeNode {
storage = EMPTY_ATTR_STORAGE;
}
/**
* Remove all attribute
*/
public void unloadAttributes() {
if (storage == EMPTY_ATTR_STORAGE) {
return;
}
storage.unloadAttributes();
storage.clearFlags();
unloadIfEmpty();
}
@@ -102,6 +102,10 @@ public class AttributeStorage {
flags.remove(flag);
}
public void clearFlags() {
flags.clear();
}
public <T extends IJadxAttribute> void remove(IJadxAttrType<T> type) {
if (!attributes.isEmpty()) {
writeAttributes(map -> map.remove(type));
@@ -4,7 +4,6 @@ import java.util.Objects;
import org.jetbrains.annotations.NotNull;
import jadx.api.ICodeWriter;
import jadx.core.utils.Utils;
public class JadxError implements Comparable<JadxError> {
@@ -55,7 +54,7 @@ public class JadxError implements Comparable<JadxError> {
str.append(cause.getClass());
str.append(':');
str.append(cause.getMessage());
str.append(ICodeWriter.NL);
str.append('\n');
str.append(Utils.getStackTrace(cause));
}
return str.toString();
@@ -2,7 +2,6 @@ package jadx.core.dex.attributes.nodes;
import java.util.List;
import jadx.api.ICodeWriter;
import jadx.api.plugins.input.data.ILocalVar;
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
import jadx.core.dex.attributes.AType;
@@ -26,6 +25,6 @@ public class LocalVarsDebugInfoAttr implements IJadxAttribute {
@Override
public String toString() {
return "Debug Info:" + ICodeWriter.NL + " " + Utils.listToString(localVars, ICodeWriter.NL + " ");
return "Debug Info:\n " + Utils.listToString(localVars, "\n ");
}
}
@@ -1,7 +1,8 @@
package jadx.core.dex.attributes.nodes;
import jadx.api.CommentsLevel;
import jadx.api.ICodeWriter;
import jadx.api.data.CommentStyle;
import jadx.core.codegen.utils.CodeComment;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.nodes.ICodeNode;
@@ -25,7 +26,11 @@ public abstract class NotificationAttrNode extends LineAttrNode implements ICode
}
public void addCodeComment(String comment) {
addAttr(AType.CODE_COMMENTS, comment);
addAttr(AType.CODE_COMMENTS, new CodeComment(comment, CommentStyle.LINE));
}
public void addCodeComment(String comment, CommentStyle style) {
addAttr(AType.CODE_COMMENTS, new CodeComment(comment, style));
}
public void addWarnComment(String warn) {
@@ -33,7 +38,7 @@ public abstract class NotificationAttrNode extends LineAttrNode implements ICode
}
public void addWarnComment(String warn, Throwable exc) {
String commentStr = warn + ICodeWriter.NL + Utils.getStackTrace(exc);
String commentStr = warn + root().getArgs().getCodeNewLineStr() + Utils.getStackTrace(exc);
JadxCommentsAttr.add(this, CommentsLevel.WARN, commentStr);
}
@@ -3,7 +3,6 @@ package jadx.core.dex.attributes.nodes;
import java.util.ArrayList;
import java.util.List;
import jadx.api.ICodeWriter;
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.instructions.PhiInsn;
@@ -33,7 +32,7 @@ public class PhiListAttr implements IJadxAttribute {
}
}
for (PhiInsn phiInsn : list) {
sb.append(ICodeWriter.NL).append(" ").append(phiInsn);
sb.append('\n').append(" ").append(phiInsn);
}
return sb.toString();
}
@@ -2,47 +2,44 @@ package jadx.core.dex.info;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.jetbrains.annotations.Nullable;
import jadx.api.JadxArgs;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.PrimitiveType;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.IFieldInfoRef;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.prepare.CollectConstValues;
public class ConstStorage {
private static final class ValueStorage {
private final Map<Object, FieldNode> values = new ConcurrentHashMap<>();
private final Map<Object, IFieldInfoRef> values = new ConcurrentHashMap<>();
private final Set<Object> duplicates = new HashSet<>();
public Map<Object, FieldNode> getValues() {
public Map<Object, IFieldInfoRef> getValues() {
return values;
}
public FieldNode get(Object key) {
public IFieldInfoRef get(Object key) {
return values.get(key);
}
/**
* @return true if this value is duplicated
*/
public boolean put(Object value, FieldNode fld) {
public boolean put(Object value, IFieldInfoRef fld) {
if (duplicates.contains(value)) {
values.remove(value);
return true;
}
FieldNode prev = values.put(value, fld);
IFieldInfoRef prev = values.put(value, fld);
if (prev != null) {
values.remove(value);
duplicates.add(value);
@@ -56,14 +53,13 @@ public class ConstStorage {
}
void removeForCls(ClassNode cls) {
Iterator<Entry<Object, FieldNode>> it = values.entrySet().iterator();
while (it.hasNext()) {
Entry<Object, FieldNode> entry = it.next();
FieldNode field = entry.getValue();
if (field.getParentClass().equals(cls)) {
it.remove();
values.entrySet().removeIf(entry -> {
IFieldInfoRef field = entry.getValue();
if (field instanceof FieldNode) {
return ((FieldNode) field).getParentClass().equals(cls);
}
}
return false;
});
}
}
@@ -77,27 +73,24 @@ public class ConstStorage {
this.replaceEnabled = args.isReplaceConsts();
}
public void processConstFields(ClassNode cls, List<FieldNode> staticFields) {
if (!replaceEnabled || staticFields.isEmpty()) {
return;
}
for (FieldNode f : staticFields) {
Object value = getFieldConstValue(f);
if (value != null) {
addConstField(cls, f, value, f.getAccessFlags().isPublic());
}
public void addConstField(FieldNode fld, Object value, boolean isPublic) {
if (isPublic) {
addGlobalConstField(fld, value);
} else {
getClsValues(fld.getParentClass()).put(value, fld);
}
}
public void addGlobalConstField(IFieldInfoRef fld, Object value) {
globalValues.put(value, fld);
}
/**
* Use method from CollectConstValues class
*/
@Deprecated
public static @Nullable Object getFieldConstValue(FieldNode fld) {
AccessInfo accFlags = fld.getAccessFlags();
if (accFlags.isStatic() && accFlags.isFinal()) {
EncodedValue constVal = fld.get(JadxAttrType.CONSTANT_VALUE);
if (constVal != null) {
return constVal.getValue();
}
}
return null;
return CollectConstValues.getFieldConstValue(fld);
}
public void removeForClass(ClassNode cls) {
@@ -105,20 +98,11 @@ public class ConstStorage {
globalValues.removeForCls(cls);
}
private void addConstField(ClassNode cls, FieldNode fld, Object value, boolean isPublic) {
if (isPublic) {
globalValues.put(value, fld);
} else {
getClsValues(cls).put(value, fld);
}
}
private ValueStorage getClsValues(ClassNode cls) {
return classes.computeIfAbsent(cls, c -> new ValueStorage());
}
@Nullable
public FieldNode getConstField(ClassNode cls, Object value, boolean searchGlobal) {
public @Nullable IFieldInfoRef getConstField(ClassNode cls, Object value, boolean searchGlobal) {
if (!replaceEnabled) {
return null;
}
@@ -137,7 +121,7 @@ public class ConstStorage {
while (current != null) {
ValueStorage classValues = classes.get(current);
if (classValues != null) {
FieldNode field = classValues.get(value);
IFieldInfoRef field = classValues.get(value);
if (field != null) {
if (foundInGlobal) {
return null;
@@ -182,8 +166,7 @@ public class ConstStorage {
return null;
}
@Nullable
public FieldNode getConstFieldByLiteralArg(ClassNode cls, LiteralArg arg) {
public @Nullable IFieldInfoRef getConstFieldByLiteralArg(ClassNode cls, LiteralArg arg) {
if (!replaceEnabled) {
return null;
}
@@ -225,7 +208,7 @@ public class ConstStorage {
return resourcesNames;
}
public Map<Object, FieldNode> getGlobalConstFields() {
public Map<Object, IFieldInfoRef> getGlobalConstFields() {
return globalValues.getValues();
}
@@ -5,9 +5,10 @@ import java.util.Objects;
import jadx.api.plugins.input.data.IFieldRef;
import jadx.core.codegen.TypeGen;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.IFieldInfoRef;
import jadx.core.dex.nodes.RootNode;
public final class FieldInfo {
public final class FieldInfo implements IFieldInfoRef {
private final ClassInfo declClass;
private final String name;
@@ -76,6 +77,11 @@ public final class FieldInfo {
return name.equals(other.name) && type.equals(other.type);
}
@Override
public FieldInfo getFieldInfo() {
return this;
}
@Override
public boolean equals(Object o) {
if (this == o) {
@@ -2,6 +2,7 @@ package jadx.core.dex.instructions;
import java.util.Objects;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.Utils;
@@ -23,6 +24,10 @@ public class IndexInsnNode extends InsnNode {
this.index = index;
}
public ArgType getIndexAsType() {
return (ArgType) index;
}
@Override
public IndexInsnNode copy() {
return copyCommonParams(new IndexInsnNode(insnType, index, getArgsCount()));
@@ -521,6 +521,7 @@ public class InsnDecoder {
if (payload != null) {
swInsn.attachSwitchData(new SwitchData((ISwitchPayload) payload), insn.getTarget());
}
method.add(AFlag.COMPUTE_POST_DOM);
return swInsn;
}
@@ -103,6 +103,6 @@ public class InvokeNode extends BaseInvokeNode {
@Override
public String toString() {
return baseString() + " type: " + type + " call: " + mth + attributesString();
return baseString() + " " + type + " call: " + mth + attributesString();
}
}
@@ -5,7 +5,6 @@ import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import jadx.api.ICodeWriter;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.InsnNode;
@@ -111,20 +110,20 @@ public class SwitchInsn extends TargetInsnNode {
int[] keys = switchData.getKeys();
if (targetBlocks != null) {
for (int i = 0; i < size; i++) {
sb.append(ICodeWriter.NL);
sb.append('\n');
sb.append(" case ").append(keys[i]).append(": goto ").append(targetBlocks[i]);
}
if (def != -1) {
sb.append(ICodeWriter.NL).append(" default: goto ").append(defTargetBlock);
sb.append('\n').append(" default: goto ").append(defTargetBlock);
}
} else {
int[] targets = switchData.getTargets();
for (int i = 0; i < size; i++) {
sb.append(ICodeWriter.NL);
sb.append('\n');
sb.append(" case ").append(keys[i]).append(": goto ").append(InsnUtils.formatOffset(targets[i]));
}
if (def != -1) {
sb.append(ICodeWriter.NL);
sb.append('\n');
sb.append(" default: goto ").append(InsnUtils.formatOffset(def));
}
}
@@ -16,8 +16,8 @@ import jadx.core.utils.InsnRemover;
import jadx.core.utils.exceptions.JadxRuntimeException;
/**
* Instruction argument,
* argument can be register, literal or instruction
* Instruction argument.
* Can be: register, literal, instruction or name
*/
public abstract class InsnArg extends Typed {
@@ -132,6 +132,10 @@ public abstract class InsnArg extends Typed {
}
InsnArg arg = wrapInsnIntoArg(insn);
InsnArg oldArg = parent.getArg(i);
if (arg.getType() == ArgType.UNKNOWN) {
// restore arg type if wrapped insn missing result
arg.setType(oldArg.getType());
}
parent.setArg(i, arg);
InsnRemover.unbindArgUsage(mth, oldArg);
if (unbind) {
@@ -209,7 +213,20 @@ public abstract class InsnArg extends Typed {
}
public boolean isZeroLiteral() {
return isLiteral() && (((LiteralArg) this)).getLiteral() == 0;
return false;
}
public boolean isZeroConst() {
if (isZeroLiteral()) {
return true;
}
if (isInsnWrap()) {
InsnNode wrapInsn = ((InsnWrapArg) this).getWrapInsn();
if (wrapInsn.getType() == InsnType.CONST) {
return wrapInsn.getArg(0).isZeroLiteral();
}
}
return false;
}
public boolean isFalse() {
@@ -265,6 +282,9 @@ public abstract class InsnArg extends Typed {
}
public boolean isSameVar(RegisterArg arg) {
if (arg == null) {
return false;
}
if (isRegister()) {
return ((RegisterArg) this).sameRegAndSVar(arg);
}
@@ -280,4 +300,8 @@ public abstract class InsnArg extends Typed {
public InsnArg duplicate() {
return this;
}
public String toShortString() {
return this.toString();
}
}
@@ -1,7 +1,5 @@
package jadx.core.dex.instructions.args;
import java.util.Objects;
import org.jetbrains.annotations.NotNull;
import jadx.core.dex.instructions.ConstStringNode;
@@ -75,10 +73,18 @@ public final class InsnWrapArg extends InsnArg {
}
@Override
public String toString() {
if (wrappedInsn.getType() == InsnType.CONST_STR && Objects.equals(type, ArgType.STRING)) {
public String toShortString() {
if (wrappedInsn.getType() == InsnType.CONST_STR) {
return "(\"" + ((ConstStringNode) wrappedInsn).getString() + "\")";
}
return "(wrap: " + type + " : " + wrappedInsn + ')';
return "(wrap:" + type + ":" + wrappedInsn.getType() + ')';
}
@Override
public String toString() {
if (wrappedInsn.getType() == InsnType.CONST_STR) {
return "(\"" + ((ConstStringNode) wrappedInsn).getString() + "\")";
}
return "(wrap:" + type + ":" + wrappedInsn + ')';
}
}
@@ -58,6 +58,11 @@ public final class LiteralArg extends InsnArg {
return true;
}
@Override
public boolean isZeroLiteral() {
return literal == 0;
}
public boolean isInteger() {
switch (type.getPrimitiveType()) {
case INT:
@@ -125,6 +130,11 @@ public final class LiteralArg extends InsnArg {
return literal == that.literal && getType().equals(that.getType());
}
@Override
public String toShortString() {
return Long.toString(literal);
}
@Override
public String toString() {
try {
@@ -48,6 +48,11 @@ public final class NamedArg extends InsnArg implements Named {
return name.equals(((NamedArg) o).name);
}
@Override
public String toShortString() {
return name;
}
@Override
public String toString() {
return '(' + name + ' ' + type + ')';
@@ -215,6 +215,16 @@ public class RegisterArg extends InsnArg implements Named {
&& Objects.equals(sVar, other.getSVar());
}
@Override
public String toShortString() {
StringBuilder sb = new StringBuilder();
sb.append("r").append(regNum);
if (sVar != null) {
sb.append('v').append(sVar.getVersion());
}
return sb.toString();
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
@@ -2,6 +2,7 @@ package jadx.core.dex.instructions.args;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -23,9 +24,12 @@ import jadx.core.dex.visitors.typeinference.TypeInfo;
import jadx.core.utils.StringUtils;
import jadx.core.utils.exceptions.JadxRuntimeException;
public class SSAVar {
public class SSAVar implements Comparable<SSAVar> {
private static final Logger LOG = LoggerFactory.getLogger(SSAVar.class);
private static final Comparator<SSAVar> SSA_VAR_COMPARATOR =
Comparator.comparingInt(SSAVar::getRegNum).thenComparingInt(SSAVar::getVersion);
private final int regNum;
private final int version;
@@ -256,34 +260,6 @@ public class SSAVar {
return codeVar != null;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof SSAVar)) {
return false;
}
SSAVar ssaVar = (SSAVar) o;
return regNum == ssaVar.regNum && version == ssaVar.version;
}
@Override
public int hashCode() {
return 31 * regNum + version;
}
public String toShortString() {
return "r" + regNum + 'v' + version;
}
@Override
public String toString() {
return toShortString()
+ (StringUtils.notEmpty(getName()) ? " '" + getName() + "' " : "")
+ ' ' + typeInfo.getType();
}
public String getDetailedVarInfo(MethodNode mth) {
Set<ArgType> types = new HashSet<>();
Set<String> names = Collections.emptySet();
@@ -323,4 +299,37 @@ public class SSAVar {
}
return sb.toString();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof SSAVar)) {
return false;
}
SSAVar ssaVar = (SSAVar) o;
return regNum == ssaVar.regNum && version == ssaVar.version;
}
@Override
public int hashCode() {
return 31 * regNum + version;
}
@Override
public int compareTo(@NotNull SSAVar o) {
return SSA_VAR_COMPARATOR.compare(this, o);
}
public String toShortString() {
return "r" + regNum + 'v' + version;
}
@Override
public String toString() {
return toShortString()
+ (StringUtils.notEmpty(getName()) ? " '" + getName() + "' " : "")
+ ' ' + typeInfo.getType();
}
}
@@ -46,6 +46,11 @@ public final class BlockNode extends AttrNode implements IBlock, Comparable<Bloc
*/
private BitSet doms = EmptyBitSet.EMPTY;
/**
* Post dominators, excluding self
*/
private BitSet postDoms = EmptyBitSet.EMPTY;
/**
* Dominance frontier
*/
@@ -56,6 +61,11 @@ public final class BlockNode extends AttrNode implements IBlock, Comparable<Bloc
*/
private BlockNode idom;
/**
* Immediate post dominator
*/
private BlockNode iPostDom;
/**
* Blocks on which dominates this block
*/
@@ -165,6 +175,14 @@ public final class BlockNode extends AttrNode implements IBlock, Comparable<Bloc
this.doms = doms;
}
public BitSet getPostDoms() {
return postDoms;
}
public void setPostDoms(BitSet postDoms) {
this.postDoms = postDoms;
}
public BitSet getDomFrontier() {
return domFrontier;
}
@@ -184,6 +202,14 @@ public final class BlockNode extends AttrNode implements IBlock, Comparable<Bloc
this.idom = idom;
}
public BlockNode getIPostDom() {
return iPostDom;
}
public void setIPostDom(BlockNode iPostDom) {
this.iPostDom = iPostDom;
}
public List<BlockNode> getDominatesOn() {
return dominatesOn;
}
@@ -233,6 +259,6 @@ public final class BlockNode extends AttrNode implements IBlock, Comparable<Bloc
@Override
public String toString() {
return "B:" + cid + ':' + InsnUtils.formatOffset(startOffset);
return "B:" + id + ':' + InsnUtils.formatOffset(startOffset);
}
}
@@ -9,7 +9,6 @@ import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -19,10 +18,12 @@ import org.slf4j.LoggerFactory;
import jadx.api.DecompilationMode;
import jadx.api.ICodeCache;
import jadx.api.ICodeInfo;
import jadx.api.ICodeWriter;
import jadx.api.JadxArgs;
import jadx.api.JavaClass;
import jadx.api.impl.SimpleCodeInfo;
import jadx.api.impl.SimpleCodeWriter;
import jadx.api.metadata.ICodeAnnotation;
import jadx.api.metadata.annotations.NodeDeclareRef;
import jadx.api.plugins.input.data.IClassData;
import jadx.api.plugins.input.data.IFieldData;
import jadx.api.plugins.input.data.IMethodData;
@@ -246,19 +247,15 @@ public class ClassNode extends NotificationAttrNode
if (fields.isEmpty()) {
return;
}
List<FieldNode> staticFields = fields.stream().filter(FieldNode::isStatic).collect(Collectors.toList());
for (FieldNode f : staticFields) {
if (f.getAccessFlags().isFinal() && f.get(JadxAttrType.CONSTANT_VALUE) == null) {
// incorrect initialization will be removed if assign found in constructor
f.addAttr(EncodedValue.NULL);
// bytecode can omit field initialization to 0 (of any type)
// add explicit init to all static final fields
// incorrect initializations will be removed if assign found in class init
for (FieldNode fld : fields) {
AccessInfo accFlags = fld.getAccessFlags();
if (accFlags.isStatic() && accFlags.isFinal() && fld.get(JadxAttrType.CONSTANT_VALUE) == null) {
fld.addAttr(EncodedValue.NULL);
}
}
try {
// process const fields
root().getConstValues().processConstFields(this, staticFields);
} catch (Exception e) {
this.addWarnComment("Failed to load initial values for static fields", e);
}
}
private boolean checkSourceFilenameAttr() {
@@ -312,6 +309,8 @@ public class ClassNode extends NotificationAttrNode
return decompile(true);
}
private static final Object DECOMPILE_WITH_MODE_SYNC = new Object();
/**
* WARNING: Slow operation! Use with caution!
*/
@@ -320,15 +319,18 @@ public class ClassNode extends NotificationAttrNode
if (mode == baseMode) {
return decompile(true);
}
JadxArgs args = root.getArgs();
try {
unload();
args.setDecompilationMode(mode);
ProcessClass process = new ProcessClass(args);
process.initPasses(root);
return process.generateCode(this);
} finally {
args.setDecompilationMode(baseMode);
synchronized (DECOMPILE_WITH_MODE_SYNC) {
JadxArgs args = root.getArgs();
try {
unload();
args.setDecompilationMode(mode);
ProcessClass process = new ProcessClass(args);
process.initPasses(root);
return process.generateCode(this);
} finally {
args.setDecompilationMode(baseMode);
unload();
}
}
}
@@ -383,22 +385,46 @@ public class ClassNode extends NotificationAttrNode
return code;
}
}
ICodeInfo codeInfo;
try {
if (Consts.DEBUG) {
LOG.debug("Decompiling class: {}", this);
}
codeInfo = root.getProcessClasses().generateCode(this);
} catch (Throwable e) {
addError("Code generation failed", e);
codeInfo = new SimpleCodeInfo(Utils.getStackTrace(e));
}
ICodeInfo codeInfo = generateClassCode();
if (codeInfo != ICodeInfo.EMPTY) {
codeCache.add(clsRawName, codeInfo);
}
return codeInfo;
}
private ICodeInfo generateClassCode() {
try {
if (Consts.DEBUG) {
LOG.debug("Decompiling class: {}", this);
}
ICodeInfo codeInfo = root.getProcessClasses().generateCode(this);
processDefinitionAnnotations(codeInfo);
return codeInfo;
} catch (Throwable e) {
addError("Code generation failed", e);
return new SimpleCodeInfo(Utils.getStackTrace(e));
}
}
/**
* Save node definition positions found in code
*/
private static void processDefinitionAnnotations(ICodeInfo codeInfo) {
Map<Integer, ICodeAnnotation> annotations = codeInfo.getCodeMetadata().getAsMap();
if (annotations.isEmpty()) {
return;
}
for (Map.Entry<Integer, ICodeAnnotation> entry : annotations.entrySet()) {
ICodeAnnotation ann = entry.getValue();
if (ann.getAnnType() == AnnType.DECLARATION) {
NodeDeclareRef declareRef = (NodeDeclareRef) ann;
int pos = entry.getKey();
declareRef.setDefPos(pos);
declareRef.getNode().setDefPosition(pos);
}
}
}
@Nullable
public ICodeInfo getCodeFromCache() {
ICodeCache codeCache = root().getCodeCache();
@@ -432,7 +458,7 @@ public class ClassNode extends NotificationAttrNode
}
methods.forEach(MethodNode::unload);
innerClasses.forEach(ClassNode::unload);
fields.forEach(FieldNode::unloadAttributes);
fields.forEach(FieldNode::unload);
unloadAttributes();
setState(NOT_LOADED);
this.loadStage = LoadStage.NONE;
@@ -482,17 +508,15 @@ public class ClassNode extends NotificationAttrNode
fields.add(fld);
}
public FieldNode getConstField(Object obj) {
public @Nullable IFieldInfoRef getConstField(Object obj) {
return getConstField(obj, true);
}
@Nullable
public FieldNode getConstField(Object obj, boolean searchGlobal) {
public @Nullable IFieldInfoRef getConstField(Object obj, boolean searchGlobal) {
return root().getConstValues().getConstField(this, obj, searchGlobal);
}
@Nullable
public FieldNode getConstFieldByLiteralArg(LiteralArg arg) {
public @Nullable IFieldInfoRef getConstFieldByLiteralArg(LiteralArg arg) {
return root().getConstValues().getConstFieldByLiteralArg(this, arg);
}
@@ -807,33 +831,29 @@ public class ClassNode extends NotificationAttrNode
public String getDisassembledCode() {
if (smali == null) {
StringBuilder sb = new StringBuilder();
getDisassembledCode(sb);
sb.append(ICodeWriter.NL);
SimpleCodeWriter code = new SimpleCodeWriter(root.getArgs());
getDisassembledCode(code);
Set<ClassNode> allInlinedClasses = new LinkedHashSet<>();
getInnerAndInlinedClassesRecursive(allInlinedClasses);
for (ClassNode innerClass : allInlinedClasses) {
innerClass.getDisassembledCode(sb);
sb.append(ICodeWriter.NL);
innerClass.getDisassembledCode(code);
}
smali = sb.toString();
smali = code.finish().getCodeStr();
}
return smali;
}
protected void getDisassembledCode(StringBuilder sb) {
protected void getDisassembledCode(SimpleCodeWriter code) {
if (clsData == null) {
sb.append(String.format("###### Class %s is created by jadx", getFullName()));
code.startLine(String.format("###### Class %s is created by jadx", getFullName()));
return;
}
sb.append(String.format("###### Class %s (%s)", getFullName(), getRawName()));
sb.append(ICodeWriter.NL);
code.startLine(String.format("###### Class %s (%s)", getFullName(), getRawName()));
try {
sb.append(clsData.getDisassembledCode());
code.startLine(clsData.getDisassembledCode());
} catch (Throwable e) {
sb.append("Failed to disassemble class:");
sb.append(ICodeWriter.NL);
sb.append(Utils.getStackTrace(e));
code.startLine("Failed to disassemble class:");
code.startLine(Utils.getStackTrace(e));
}
}
@@ -12,7 +12,7 @@ import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.utils.ListUtils;
public class FieldNode extends NotificationAttrNode implements ICodeNode {
public class FieldNode extends NotificationAttrNode implements ICodeNode, IFieldInfoRef {
private final ClassNode parentClass;
private final FieldInfo fieldInfo;
@@ -38,10 +38,15 @@ public class FieldNode extends NotificationAttrNode implements ICodeNode {
this.accFlags = new AccessInfo(accessFlags, AFType.FIELD);
}
public void unload() {
unloadAttributes();
}
public void updateType(ArgType type) {
this.type = type;
}
@Override
public FieldInfo getFieldInfo() {
return fieldInfo;
}
@@ -0,0 +1,10 @@
package jadx.core.dex.nodes;
import jadx.core.dex.info.FieldInfo;
/**
* Common interface for FieldInfo and FieldNode
*/
public interface IFieldInfoRef {
FieldInfo getFieldInfo();
}
@@ -10,7 +10,6 @@ import java.util.function.Function;
import org.jetbrains.annotations.Nullable;
import jadx.api.ICodeWriter;
import jadx.api.plugins.input.insns.InsnData;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
@@ -563,9 +562,9 @@ public class InsnNode extends LineAttrNode {
return false;
}
// wrap args
String separator = ICodeWriter.NL + " ";
String separator = "\n ";
sb.append(separator).append(Utils.listToString(arguments, separator));
sb.append(ICodeWriter.NL);
sb.append('\n');
return true;
}
@@ -253,7 +253,7 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
return mthInfo.getReturnType().equals(ArgType.VOID);
}
public List<VarNode> collectArgsWithoutLoading() {
public List<VarNode> collectArgNodes() {
ICodeInfo codeInfo = getTopParentClass().getCode();
int mthDefPos = getDefPosition();
int lineEndPos = CodeUtils.getLineEndForPos(codeInfo.getCodeStr(), mthDefPos);
@@ -360,10 +360,13 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
public void setBasicBlocks(List<BlockNode> blocks) {
this.blocks = blocks;
int i = 0;
for (BlockNode block : blocks) {
block.setId(i);
i++;
updateBlockIds(blocks);
}
public void updateBlockIds(List<BlockNode> blocks) {
int count = blocks.size();
for (int i = 0; i < count; i++) {
blocks.get(i).setId(i);
}
}
@@ -391,7 +394,7 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
return exitBlock.getPredecessors();
}
public boolean isPreExitBlocks(BlockNode block) {
public boolean isPreExitBlock(BlockNode block) {
List<BlockNode> successors = block.getSuccessors();
if (successors.size() == 1) {
return successors.get(0).equals(exitBlock);
@@ -54,9 +54,8 @@ import jadx.core.utils.StringUtils;
import jadx.core.utils.Utils;
import jadx.core.utils.android.AndroidResourcesUtils;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.xmlgen.IResParser;
import jadx.core.xmlgen.IResTableParser;
import jadx.core.xmlgen.ManifestAttributes;
import jadx.core.xmlgen.ResDecoder;
import jadx.core.xmlgen.ResourceStorage;
import jadx.core.xmlgen.entry.ResourceEntry;
import jadx.core.xmlgen.entry.ValuesParser;
@@ -93,7 +92,6 @@ public class RootNode {
private String appPackage;
@Nullable
private ClassNode appResClass;
private boolean isProto;
/**
* Optional decompiler reference
@@ -109,16 +107,6 @@ public class RootNode {
this.typeUpdate = new TypeUpdate(this);
this.methodUtils = new MethodUtils(this);
this.typeUtils = new TypeUtils(this);
this.isProto = args.getInputFiles().size() > 0 && args.getInputFiles().get(0).getName().toLowerCase().endsWith(".aab");
}
public void init() {
if (args.isDeobfuscationOn() || !args.getRenameFlags().isEmpty()) {
args.getAliasProvider().init(this);
}
if (args.isDeobfuscationOn()) {
args.getRenameCondition().init(this);
}
}
public void loadClasses(List<ICodeLoader> loadedInputs) {
@@ -203,25 +191,25 @@ public class RootNode {
rawClsMap.put(clsNode.getRawName(), clsNode);
}
public void loadResources(List<ResourceFile> resources) {
public void loadResources(ResourcesLoader resLoader, List<ResourceFile> resources) {
ResourceFile arsc = getResourceFile(resources);
if (arsc == null) {
LOG.debug("'.arsc' file not found");
LOG.debug("'resources.arsc' or 'resources.pb' file not found");
return;
}
try {
IResParser parser = ResourcesLoader.decodeStream(arsc, (size, is) -> ResDecoder.decode(this, arsc, is));
IResTableParser parser = ResourcesLoader.decodeStream(arsc, (size, is) -> resLoader.decodeTable(arsc, is));
if (parser != null) {
processResources(parser.getResStorage());
updateObfuscatedFiles(parser, resources);
updateManifestAttribMap(parser);
}
} catch (Exception e) {
LOG.error("Failed to parse '.arsc' file", e);
LOG.error("Failed to parse 'resources.pb'/'.arsc' file", e);
}
}
private void updateManifestAttribMap(IResParser parser) {
private void updateManifestAttribMap(IResTableParser parser) {
ManifestAttributes manifestAttributes = ManifestAttributes.getInstance();
manifestAttributes.updateAttributes(parser);
}
@@ -257,7 +245,7 @@ public class RootNode {
}
}
private void updateObfuscatedFiles(IResParser parser, List<ResourceFile> resources) {
private void updateObfuscatedFiles(IResTableParser parser, List<ResourceFile> resources) {
if (args.isSkipResources()) {
return;
}
@@ -715,10 +703,6 @@ public class RootNode {
return attributes;
}
public boolean isProto() {
return isProto;
}
public GradleInfoStorage getGradleInfoStorage() {
return gradleInfoStorage;
}
@@ -15,7 +15,12 @@ import jadx.core.utils.exceptions.CodegenException;
public final class SwitchRegion extends AbstractRegion implements IBranchRegion {
public static final Object DEFAULT_CASE_KEY = new Object();
public static final Object DEFAULT_CASE_KEY = new Object() {
@Override
public String toString() {
return "default";
}
};
private final BlockNode header;
@@ -91,7 +96,7 @@ public final class SwitchRegion extends AbstractRegion implements IBranchRegion
for (CaseInfo caseInfo : cases) {
List<String> keyStrings = Utils.collectionMap(caseInfo.getKeys(),
k -> k == DEFAULT_CASE_KEY ? "default" : k.toString());
sb.append(ICodeWriter.NL).append(" case ")
sb.append("\n case ")
.append(Utils.listToString(keyStrings))
.append(" -> ").append(caseInfo.getContainer());
}
@@ -14,6 +14,7 @@ import jadx.api.data.ICodeComment;
import jadx.api.data.ICodeData;
import jadx.api.data.IJavaCodeRef;
import jadx.api.data.IJavaNodeRef;
import jadx.core.codegen.utils.CodeComment;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttributeNode;
import jadx.core.dex.nodes.ClassNode;
@@ -58,7 +59,7 @@ public class AttachCommentsVisitor extends AbstractVisitor {
IJavaNodeRef nodeRef = comment.getNodeRef();
switch (nodeRef.getType()) {
case CLASS:
addComment(cls, comment.getComment());
addComment(cls, comment);
break;
case FIELD:
@@ -66,7 +67,7 @@ public class AttachCommentsVisitor extends AbstractVisitor {
if (fieldNode == null) {
LOG.warn("Field reference not found: {}", nodeRef);
} else {
addComment(fieldNode, comment.getComment());
addComment(fieldNode, comment);
}
break;
@@ -77,7 +78,7 @@ public class AttachCommentsVisitor extends AbstractVisitor {
} else {
IJavaCodeRef codeRef = comment.getCodeRef();
if (codeRef == null) {
addComment(methodNode, comment.getComment());
addComment(methodNode, comment);
} else {
processCustomAttach(methodNode, codeRef, comment);
}
@@ -101,7 +102,7 @@ public class AttachCommentsVisitor extends AbstractVisitor {
switch (attachType) {
case INSN: {
InsnNode insn = getInsnByOffset(mth, codeRef.getIndex());
addComment(insn, comment.getComment());
addComment(insn, comment);
break;
}
default:
@@ -109,11 +110,11 @@ public class AttachCommentsVisitor extends AbstractVisitor {
}
}
private static void addComment(@Nullable IAttributeNode node, String comment) {
private static void addComment(@Nullable IAttributeNode node, ICodeComment comment) {
if (node == null) {
return;
}
node.addAttr(AType.CODE_COMMENTS, comment);
node.addAttr(AType.CODE_COMMENTS, new CodeComment(comment));
}
private List<ICodeComment> getCommentsData(ClassNode cls) {
@@ -1,7 +1,6 @@
package jadx.core.dex.visitors;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.jetbrains.annotations.Nullable;
@@ -16,16 +15,12 @@ import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.trycatch.CatchAttr;
import jadx.core.dex.trycatch.ExcHandlerAttr;
import jadx.core.dex.trycatch.ExceptionHandler;
import jadx.core.dex.visitors.typeinference.TypeCompare;
import jadx.core.dex.visitors.typeinference.TypeCompareEnum;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import static jadx.core.dex.visitors.ProcessInstructionsVisitor.getNextInsnOffset;
@@ -122,7 +117,6 @@ public class AttachTryCatchVisitor extends AbstractVisitor {
if (allHandlerOffset >= 0) {
Utils.addToList(list, createHandler(mth, insnByOffset, allHandlerOffset, null));
}
checkAndFilterHandlers(mth, list);
return list;
}
@@ -149,45 +143,6 @@ public class AttachTryCatchVisitor extends AbstractVisitor {
return handler;
}
private static void checkAndFilterHandlers(MethodNode mth, List<ExceptionHandler> list) {
if (list.size() <= 1) {
return;
}
// Remove shadowed handlers (with same or narrow type compared to previous)
TypeCompare typeCompare = mth.root().getTypeCompare();
Iterator<ExceptionHandler> it = list.iterator();
ArgType maxType = null;
while (it.hasNext()) {
ExceptionHandler handler = it.next();
ArgType maxCatch = maxCatchFromHandler(handler, typeCompare);
if (maxType == null) {
maxType = maxCatch;
} else {
TypeCompareEnum result = typeCompare.compareObjects(maxType, maxCatch);
if (result.isWiderOrEqual()) {
if (Consts.DEBUG_EXC_HANDLERS) {
LOG.debug("Removed shadowed catch handler: {}, from list: {}", handler, list);
}
it.remove();
}
}
}
}
private static ArgType maxCatchFromHandler(ExceptionHandler handler, TypeCompare typeCompare) {
List<ClassInfo> catchTypes = handler.getCatchTypes();
if (catchTypes.isEmpty()) {
return ArgType.THROWABLE;
}
if (catchTypes.size() == 1) {
return catchTypes.get(0).getType();
}
return catchTypes.stream()
.map(ClassInfo::getType)
.max(typeCompare.getComparator())
.orElseThrow(() -> new JadxRuntimeException("Failed to get max type from catch list: " + catchTypes));
}
private static InsnNode insertNOP(InsnNode[] insnByOffset, int offset) {
InsnNode nop = new InsnNode(InsnType.NOP, 0);
nop.setOffset(offset);
@@ -18,7 +18,7 @@ import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.instructions.mods.ConstructorInsn;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.IFieldInfoRef;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.visitors.finaly.MarkFinallyVisitor;
@@ -26,6 +26,7 @@ import jadx.core.dex.visitors.ssa.SSATransform;
import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor;
import jadx.core.utils.InsnRemover;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.exceptions.JadxRuntimeException;
@JadxVisitor(
name = "Constants Inline",
@@ -73,8 +74,7 @@ public class ConstInlineVisitor extends AbstractVisitor {
if (!constArg.isLiteral()) {
return;
}
long lit = ((LiteralArg) constArg).getLiteral();
if (lit == 0 && forbidNullInlines(sVar)) {
if (constArg.isZeroLiteral() && forbidNullInlines(sVar)) {
// all usages forbids inlining
return;
}
@@ -82,7 +82,7 @@ public class ConstInlineVisitor extends AbstractVisitor {
}
case CONST_STR: {
String s = ((ConstStringNode) insn).getString();
FieldNode f = mth.getParentClass().getConstField(s);
IFieldInfoRef f = mth.getParentClass().getConstField(s);
if (f == null) {
InsnNode copy = insn.copyWithoutResult();
constArg = InsnArg.wrapArg(copy);
@@ -90,7 +90,7 @@ public class ConstInlineVisitor extends AbstractVisitor {
InsnNode constGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0);
constArg = InsnArg.wrapArg(constGet);
constArg.setType(ArgType.STRING);
onSuccess = () -> f.addUseIn(mth);
onSuccess = () -> ModVisitor.addFieldUsage(f, mth);
}
break;
}
@@ -134,20 +134,15 @@ public class ConstInlineVisitor extends AbstractVisitor {
}
private static boolean forbidNullArgInline(InsnNode insn, RegisterArg useArg) {
switch (insn.getType()) {
case MOVE:
case CAST:
case CHECK_CAST:
// result is null, chain checks
return forbidNullInlines(insn.getResult().getSVar());
default:
if (!canUseNull(insn, useArg)) {
useArg.add(AFlag.DONT_INLINE_CONST);
return true;
}
return false;
if (insn.getType() == InsnType.MOVE) {
// result is null, chain checks
return forbidNullInlines(insn.getResult().getSVar());
}
if (!canUseNull(insn, useArg)) {
useArg.add(AFlag.DONT_INLINE_CONST);
return true;
}
return false;
}
private static boolean canUseNull(InsnNode insn, RegisterArg useArg) {
@@ -256,7 +251,7 @@ public class ConstInlineVisitor extends AbstractVisitor {
return false;
}
// arg replaced, made some optimizations
FieldNode fieldNode = null;
IFieldInfoRef fieldNode = null;
ArgType litArgType = litArg.getType();
if (litArgType.isTypeKnown()) {
fieldNode = mth.getParentClass().getConstFieldByLiteralArg(litArg);
@@ -266,12 +261,10 @@ public class ConstInlineVisitor extends AbstractVisitor {
if (fieldNode != null) {
IndexInsnNode sgetInsn = new IndexInsnNode(InsnType.SGET, fieldNode.getFieldInfo(), 0);
if (litArg.wrapInstruction(mth, sgetInsn) != null) {
fieldNode.addUseIn(mth);
ModVisitor.addFieldUsage(fieldNode, mth);
}
} else {
if (needExplicitCast(useInsn, litArg)) {
litArg.add(AFlag.EXPLICIT_PRIMITIVE_TYPE);
}
addExplicitCast(useInsn, litArg);
}
} else {
if (!useInsn.replaceArg(arg, constArg.duplicate())) {
@@ -282,18 +275,33 @@ public class ConstInlineVisitor extends AbstractVisitor {
return true;
}
private static boolean needExplicitCast(InsnNode insn, LiteralArg arg) {
private static void addExplicitCast(InsnNode insn, LiteralArg arg) {
if (insn instanceof BaseInvokeNode) {
BaseInvokeNode callInsn = (BaseInvokeNode) insn;
MethodInfo callMth = callInsn.getCallMth();
int offset = callInsn.getFirstArgOffset();
int argIndex = insn.getArgIndex(arg);
ArgType argType = callMth.getArgumentsTypes().get(argIndex - offset);
if (argType.isPrimitive()) {
arg.setType(argType);
return argType.equals(ArgType.BYTE);
if (callInsn.getInstanceArg() == arg) {
// instance arg is null, force cast
if (!arg.isZeroLiteral()) {
throw new JadxRuntimeException("Unexpected instance arg in invoke");
}
ArgType castType = callMth.getDeclClass().getType();
InsnNode castInsn = new IndexInsnNode(InsnType.CAST, castType, 1);
castInsn.addArg(arg);
castInsn.add(AFlag.EXPLICIT_CAST);
InsnArg wrapCast = InsnArg.wrapArg(castInsn);
wrapCast.setType(castType);
insn.replaceArg(arg, wrapCast);
} else {
int offset = callInsn.getFirstArgOffset();
int argIndex = insn.getArgIndex(arg);
ArgType argType = callMth.getArgumentsTypes().get(argIndex - offset);
if (argType.isPrimitive()) {
arg.setType(argType);
if (argType.equals(ArgType.BYTE)) {
arg.add(AFlag.EXPLICIT_PRIMITIVE_TYPE);
}
}
}
}
return false;
}
}
@@ -199,7 +199,7 @@ public class DotGraphVisitor extends AbstractVisitor {
dot.add("color=red,");
}
dot.add("label=\"{");
dot.add(String.valueOf(block.getCId())).add("\\:\\ ");
dot.add(String.valueOf(block.getId())).add("\\:\\ ");
dot.add(InsnUtils.formatOffset(block.getStartOffset()));
if (!attrs.isEmpty()) {
dot.add('|').add(attrs);
@@ -208,6 +208,8 @@ public class DotGraphVisitor extends AbstractVisitor {
dot.add('|');
dot.startLine("doms: ").add(escape(block.getDoms()));
dot.startLine("\\lidom: ").add(escape(block.getIDom()));
dot.startLine("\\lpost-doms: ").add(escape(block.getPostDoms()));
dot.startLine("\\lpost-idom: ").add(escape(block.getIPostDom()));
dot.startLine("\\ldom-f: ").add(escape(block.getDomFrontier()));
dot.startLine("\\ldoms-on: ").add(escape(Utils.listToString(block.getDominatesOn())));
dot.startLine("\\l");
@@ -230,10 +232,10 @@ public class DotGraphVisitor extends AbstractVisitor {
if (PRINT_DOMINATORS) {
for (BlockNode c : block.getDominatesOn()) {
conn.startLine(block.getCId() + " -> " + c.getCId() + "[color=green];");
conn.startLine(block.getId() + " -> " + c.getId() + "[color=green];");
}
for (BlockNode dom : BlockUtils.bitSetToBlocks(mth, block.getDomFrontier())) {
conn.startLine("f_" + block.getCId() + " -> f_" + dom.getCId() + "[color=blue];");
conn.startLine("f_" + block.getId() + " -> f_" + dom.getId() + "[color=blue];");
}
}
}
@@ -273,7 +275,7 @@ public class DotGraphVisitor extends AbstractVisitor {
private String makeName(IContainer c) {
String name;
if (c instanceof BlockNode) {
name = "Node_" + ((BlockNode) c).getCId();
name = "Node_" + ((BlockNode) c).getId();
} else if (c instanceof IBlock) {
name = "Node_" + c.getClass().getSimpleName() + '_' + c.hashCode();
} else {
@@ -317,8 +319,7 @@ public class DotGraphVisitor extends AbstractVisitor {
.replace("\"", "\\\"")
.replace("-", "\\-")
.replace("|", "\\|")
.replace(ICodeWriter.NL, NL)
.replace("\n", NL);
.replaceAll("\\R", NL);
}
}
}
@@ -99,7 +99,7 @@ public class MarkMethodsForInline extends AbstractVisitor {
&& retInsn.getArg(0).isSameVar(firstInsn.getResult())
&& firstInsn.getArg(0).isSameVar(mthRegs.get(0));
case SGET:
return mthRegs.size() == 0
return mthRegs.isEmpty()
&& retInsn.getArg(0).isSameVar(firstInsn.getResult());
case IPUT:
@@ -113,7 +113,7 @@ public class MarkMethodsForInline extends AbstractVisitor {
&& firstInsn.getArg(0).isSameVar(mthRegs.get(0));
case INVOKE:
return mthRegs.size() >= 1
return !mthRegs.isEmpty()
&& firstInsn.getArg(0).isSameVar(mthRegs.get(0))
&& retInsn.getArg(0).isSameVar(firstInsn.getResult());
default:
@@ -5,7 +5,9 @@ import java.util.List;
import java.util.Map;
import java.util.function.Function;
import jadx.api.ICodeWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.Consts;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.info.MethodInfo;
@@ -28,6 +30,7 @@ import jadx.core.dex.visitors.methods.MutableMethodDetails;
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
import jadx.core.dex.visitors.typeinference.TypeCompare;
import jadx.core.dex.visitors.typeinference.TypeCompareEnum;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
@@ -43,6 +46,8 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
}
)
public class MethodInvokeVisitor extends AbstractVisitor {
private static final Logger LOG = LoggerFactory.getLogger(MethodInvokeVisitor.class);
private RootNode root;
@Override
@@ -116,7 +121,8 @@ public class MethodInvokeVisitor extends AbstractVisitor {
int argsOffset = invokeInsn.getFirstArgOffset();
List<ArgType> compilerVarTypes = collectCompilerVarTypes(invokeInsn, argsOffset);
List<ArgType> castTypes = searchCastTypes(parentMth, effectiveMthDetails, effectiveOverloadMethods, compilerVarTypes);
applyArgsCast(invokeInsn, argsOffset, compilerVarTypes, castTypes);
List<ArgType> resultCastTypes = expandTypes(parentMth, effectiveMthDetails, castTypes);
applyArgsCast(invokeInsn, argsOffset, compilerVarTypes, resultCastTypes);
}
/**
@@ -180,7 +186,13 @@ public class MethodInvokeVisitor extends AbstractVisitor {
if (arg.isLiteral() && compilerType.isPrimitive() && castType.isPrimitive()) {
arg.setType(castType);
arg.add(AFlag.EXPLICIT_PRIMITIVE_TYPE);
} else if (InsnUtils.isWrapped(arg, InsnType.CHECK_CAST)) {
IndexInsnNode wrapInsn = ((IndexInsnNode) ((InsnWrapArg) arg).getWrapInsn());
wrapInsn.updateIndex(castType);
} else {
if (Consts.DEBUG_TYPE_INFERENCE) {
LOG.info("Insert cast for invoke insn arg: {}, insn: {}", arg, invokeInsn);
}
InsnNode castInsn = new IndexInsnNode(InsnType.CAST, castType, 1);
castInsn.addArg(arg);
castInsn.add(AFlag.EXPLICIT_CAST);
@@ -243,7 +255,7 @@ public class MethodInvokeVisitor extends AbstractVisitor {
private List<ArgType> searchCastTypes(MethodNode parentMth, IMethodDetails mthDetails, List<IMethodDetails> overloadedMethods,
List<ArgType> compilerVarTypes) {
// try compile types
// try compiler types
if (isOverloadResolved(mthDetails, overloadedMethods, compilerVarTypes)) {
return compilerVarTypes;
}
@@ -277,10 +289,10 @@ public class MethodInvokeVisitor extends AbstractVisitor {
if (Consts.DEBUG_OVERLOADED_CASTS) {
// TODO: try to minimize casts count
parentMth.addDebugComment("Failed to find minimal casts for resolve overloaded methods, cast all args instead"
+ ICodeWriter.NL + " method: " + mthDetails
+ ICodeWriter.NL + " arg types: " + compilerVarTypes
+ ICodeWriter.NL + " candidates:"
+ ICodeWriter.NL + " " + Utils.listToString(overloadedMethods, ICodeWriter.NL + " "));
+ "\n method: " + mthDetails
+ "\n arg types: " + compilerVarTypes
+ "\n candidates:"
+ "\n " + Utils.listToString(overloadedMethods, "\n "));
}
// not resolved -> cast all args
return mthDetails.getArgTypes();
@@ -300,6 +312,27 @@ public class MethodInvokeVisitor extends AbstractVisitor {
return changed;
}
/**
* Use generified types if available
*/
private List<ArgType> expandTypes(MethodNode parentMth, IMethodDetails methodDetails, List<ArgType> castTypes) {
TypeCompare typeCompare = parentMth.root().getTypeCompare();
List<ArgType> mthArgTypes = methodDetails.getArgTypes();
int argsCount = castTypes.size();
List<ArgType> list = new ArrayList<>(argsCount);
for (int i = 0; i < argsCount; i++) {
ArgType mthType = mthArgTypes.get(i);
ArgType castType = castTypes.get(i);
TypeCompareEnum result = typeCompare.compareTypes(mthType, castType);
if (result == TypeCompareEnum.NARROW_BY_GENERIC) {
list.add(mthType);
} else {
list.add(castType);
}
}
return list;
}
private boolean isOverloadResolved(IMethodDetails expectedMthDetails, List<IMethodDetails> overloadedMethods, List<ArgType> castTypes) {
if (overloadedMethods.isEmpty()) {
return false;
@@ -387,12 +420,22 @@ public class MethodInvokeVisitor extends AbstractVisitor {
}
if (arg instanceof InsnWrapArg) {
InsnWrapArg wrapArg = (InsnWrapArg) arg;
InsnNode wrapInsn = wrapArg.getWrapInsn();
if (wrapInsn.getResult() != null) {
return wrapInsn.getResult().getType();
}
return arg.getType();
return getInsnCompilerType(arg, wrapArg.getWrapInsn());
}
throw new JadxRuntimeException("Unknown var type for: " + arg);
}
private static ArgType getInsnCompilerType(InsnArg arg, InsnNode insn) {
switch (insn.getType()) {
case CAST:
case CHECK_CAST:
return ((IndexInsnNode) insn).getIndexAsType();
default:
if (insn.getResult() != null) {
return insn.getResult().getType();
}
return arg.getType();
}
}
}
@@ -44,6 +44,7 @@ import jadx.core.dex.instructions.mods.TernaryInsn;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.IFieldInfoRef;
import jadx.core.dex.nodes.IMethodDetails;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
@@ -239,10 +240,10 @@ public class ModVisitor extends AbstractVisitor {
int[] keys = insn.getKeys();
int len = keys.length;
for (int k = 0; k < len; k++) {
FieldNode f = parentClass.getConstField(keys[k]);
IFieldInfoRef f = parentClass.getConstField(keys[k]);
if (f != null) {
insn.modifyKey(k, f);
f.addUseIn(mth);
addFieldUsage(f, mth);
}
}
}
@@ -313,7 +314,7 @@ public class ModVisitor extends AbstractVisitor {
}
return new EncodedValue(EncodedType.ENCODED_ARRAY, listVal);
}
FieldNode constField = parentCls.getConstField(encodedValue.getValue());
IFieldInfoRef constField = parentCls.getConstField(encodedValue.getValue());
if (constField != null) {
return new EncodedValue(EncodedType.ENCODED_FIELD, constField.getFieldInfo());
}
@@ -321,7 +322,7 @@ public class ModVisitor extends AbstractVisitor {
}
private static void replaceConst(MethodNode mth, ClassNode parentClass, BlockNode block, int i, InsnNode insn) {
FieldNode f;
IFieldInfoRef f;
if (insn.getType() == InsnType.CONST_STR) {
String s = ((ConstStringNode) insn).getString();
f = parentClass.getConstField(s);
@@ -335,7 +336,7 @@ public class ModVisitor extends AbstractVisitor {
InsnNode inode = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0);
inode.setResult(insn.getResult());
replaceInsn(mth, block, i, inode);
f.addUseIn(mth);
addFieldUsage(f, mth);
}
}
@@ -345,11 +346,11 @@ public class ModVisitor extends AbstractVisitor {
}
InsnArg litArg = arithNode.getArg(1);
if (litArg.isLiteral()) {
FieldNode f = parentClass.getConstFieldByLiteralArg((LiteralArg) litArg);
IFieldInfoRef f = parentClass.getConstFieldByLiteralArg((LiteralArg) litArg);
if (f != null) {
InsnNode fGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0);
if (arithNode.replaceArg(litArg, InsnArg.wrapArg(fGet))) {
f.addUseIn(mth);
addFieldUsage(f, mth);
}
}
}
@@ -389,6 +390,11 @@ public class ModVisitor extends AbstractVisitor {
private static void removeCheckCast(MethodNode mth, BlockNode block, int i, IndexInsnNode insn) {
InsnArg castArg = insn.getArg(0);
if (castArg.isZeroLiteral()) {
// always keep cast for 'null'
insn.add(AFlag.EXPLICIT_CAST);
return;
}
ArgType castType = (ArgType) insn.getIndex();
if (!ArgType.isCastNeeded(mth.root(), castArg.getType(), castType)) {
RegisterArg result = insn.getResult();
@@ -561,11 +567,11 @@ public class ModVisitor extends AbstractVisitor {
InsnNode filledArr = new FilledNewArrayNode(elType, list.size());
filledArr.setResult(newArrayNode.getResult().duplicate());
for (LiteralArg arg : list) {
FieldNode f = mth.getParentClass().getConstFieldByLiteralArg(arg);
IFieldInfoRef f = mth.getParentClass().getConstFieldByLiteralArg(arg);
if (f != null) {
InsnNode fGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0);
filledArr.addArg(InsnArg.wrapArg(fGet));
f.addUseIn(mth);
addFieldUsage(f, mth);
} else {
filledArr.addArg(arg.duplicate());
}
@@ -602,4 +608,10 @@ public class ModVisitor extends AbstractVisitor {
}
block.copyAttributeFrom(insn, AType.CODE_COMMENTS); // save comment
}
public static void addFieldUsage(IFieldInfoRef fieldData, MethodNode mth) {
if (fieldData instanceof FieldNode) {
((FieldNode) fieldData).addUseIn(mth);
}
}
}
@@ -25,7 +25,9 @@ import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.ArithNode;
import jadx.core.dex.instructions.ArithOp;
import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.InvokeNode;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.InsnWrapArg;
@@ -82,6 +84,7 @@ public class PrepareForCodeGen extends AbstractVisitor {
removeParenthesis(block);
modifyArith(block);
checkConstUsage(block);
addNullCasts(mth, block);
}
moveConstructorInConstructor(mth);
collectFieldsUsageInAnnotations(mth, mth);
@@ -379,4 +382,27 @@ public class PrepareForCodeGen extends AbstractVisitor {
break;
}
}
private void addNullCasts(MethodNode mth, BlockNode block) {
for (InsnNode insn : block.getInstructions()) {
switch (insn.getType()) {
case INVOKE:
verifyNullCast(mth, ((InvokeNode) insn).getInstanceArg());
break;
case ARRAY_LENGTH:
verifyNullCast(mth, insn.getArg(0));
break;
}
}
}
private void verifyNullCast(MethodNode mth, InsnArg arg) {
if (arg != null && arg.isZeroConst()) {
ArgType castType = arg.getType();
IndexInsnNode castInsn = new IndexInsnNode(InsnType.CAST, castType, 1);
castInsn.addArg(InsnArg.lit(0, castType));
arg.wrapInstruction(mth, castInsn);
}
}
}
@@ -43,9 +43,7 @@ public class ProcessAnonymous extends AbstractVisitor {
if (!inlineAnonymousClasses) {
return;
}
for (ClassNode cls : root.getClasses()) {
markAnonymousClass(cls);
}
root.getClasses().forEach(ProcessAnonymous::processClass);
mergeAnonymousDeps(root);
}
@@ -59,10 +57,18 @@ public class ProcessAnonymous extends AbstractVisitor {
}
private void visitClassAndInners(ClassNode cls) {
markAnonymousClass(cls);
processClass(cls);
cls.getInnerClasses().forEach(this::visitClassAndInners);
}
private static void processClass(ClassNode cls) {
try {
markAnonymousClass(cls);
} catch (Throwable e) {
cls.addError("Anonymous visitor error", e);
}
}
private static void markAnonymousClass(ClassNode cls) {
if (!canBeAnonymous(cls)) {
return;
@@ -273,6 +279,10 @@ public class ProcessAnonymous extends AbstractVisitor {
if (!ctrUseMth.getMethodInfo().isClassInit()) {
return false;
}
if (cls.getUseInMth().isEmpty()) {
// no outside usage, inline not needed
return false;
}
FieldNode instFld = ListUtils.filterOnlyOne(cls.getFields(),
f -> f.getAccessFlags().containsFlags(AccessFlags.PUBLIC, AccessFlags.STATIC, AccessFlags.FINAL)
&& f.getFieldInfo().getType().equals(cls.getClassInfo().getType()));
@@ -16,7 +16,7 @@ import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.IFieldInfoRef;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
@@ -167,11 +167,11 @@ public class ReplaceNewArray extends AbstractVisitor {
private static InsnArg replaceConstInArg(MethodNode mth, InsnArg valueArg) {
if (valueArg.isLiteral()) {
FieldNode f = mth.getParentClass().getConstFieldByLiteralArg((LiteralArg) valueArg);
IFieldInfoRef f = mth.getParentClass().getConstFieldByLiteralArg((LiteralArg) valueArg);
if (f != null) {
InsnNode fGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0);
InsnArg arg = InsnArg.wrapArg(fGet);
f.addUseIn(mth);
ModVisitor.addFieldUsage(f, mth);
return arg;
}
}
@@ -402,7 +402,8 @@ public class SimplifyVisitor extends AbstractVisitor {
}
}
if (!stringArgFound) {
mth.addDebugComment("TODO: convert one arg to string using `String.valueOf()`, args: " + args);
String argStr = Utils.listToString(args, InsnArg::toShortString);
mth.addDebugComment("TODO: convert one arg to string using `String.valueOf()`, args: " + argStr);
return null;
}
@@ -625,7 +626,9 @@ public class SimplifyVisitor extends AbstractVisitor {
for (int i = 1; i < argsCount; i++) {
concat.addArg(wrap.getArg(i));
}
return ArithNode.oneArgOp(ArithOp.ADD, fArg, InsnArg.wrapArg(concat));
InsnArg concatArg = InsnArg.wrapArg(concat);
concatArg.setType(ArgType.STRING);
return ArithNode.oneArgOp(ArithOp.ADD, fArg, concatArg);
} catch (Exception e) {
LOG.debug("Can't convert field arith insn: {}, mth: {}", insn, mth, e);
}
@@ -68,7 +68,7 @@ public class BlockExceptionHandler {
BlockProcessor.removeMarkedBlocks(mth);
BlockSet sorted = new BlockSet(mth);
BlockUtils.dfsVisit(mth, sorted::set);
BlockUtils.visitDFS(mth, sorted::set);
removeUnusedExcHandlers(mth, tryBlocks, sorted);
return true;
}
@@ -331,6 +331,17 @@ public class BlockExceptionHandler {
return false;
}
BlockNode bottom = searchBottomBlock(mth, blocks);
BlockNode splitReturn;
if (bottom != null && bottom.isReturnBlock()) {
if (Consts.DEBUG_EXC_HANDLERS) {
LOG.debug("TryCatch #{} bottom block ({}) is return, split", tryCatchBlock.id(), bottom);
}
splitReturn = bottom;
bottom = BlockSplitter.blockSplitTop(mth, bottom);
bottom.add(AFlag.SYNTHETIC);
} else {
splitReturn = null;
}
if (Consts.DEBUG_EXC_HANDLERS) {
LOG.debug("TryCatch #{} split: top {}, bottom: {}", tryCatchBlock.id(), top, bottom);
}
@@ -349,6 +360,18 @@ public class BlockExceptionHandler {
bottomSplitterBlock.add(AFlag.EXC_BOTTOM_SPLITTER);
bottomSplitterBlock.add(AFlag.SYNTHETIC);
BlockSplitter.connect(bottom, bottomSplitterBlock);
if (splitReturn != null) {
// redirect handler to return block instead synthetic split block to avoid self-loop
BlockSet bottomPreds = BlockSet.from(mth, bottom.getPredecessors());
for (ExceptionHandler handler : tryCatchBlock.getHandlers()) {
if (bottomPreds.intersects(handler.getBlocks())) {
BlockNode lastBlock = bottomPreds.intersect(handler.getBlocks()).getOne();
if (lastBlock != null) {
BlockSplitter.replaceConnection(lastBlock, bottom, splitReturn);
}
}
}
}
}
if (Consts.DEBUG_EXC_HANDLERS) {
@@ -600,7 +623,7 @@ public class BlockExceptionHandler {
for (ExceptionHandler eh : mth.getExceptionHandlers()) {
boolean notProcessed = true;
BlockNode handlerBlock = eh.getHandlerBlock();
if (blocks.get(handlerBlock)) {
if (handlerBlock == null || blocks.get(handlerBlock)) {
continue;
}
for (TryCatchBlockAttr tcb : tryBlocks) {
@@ -70,6 +70,8 @@ public class BlockProcessor extends AbstractVisitor {
registerLoops(mth);
processNestedLoops(mth);
PostDominatorTree.compute(mth);
updateCleanSuccessors(mth);
if (!mth.contains(AFlag.DISABLE_BLOCKS_LOCK)) {
mth.finishBasicBlocks();
@@ -3,8 +3,7 @@ package jadx.core.dex.visitors.blocks;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import java.util.function.Function;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.MethodNode;
@@ -23,14 +22,14 @@ public class DominatorTree {
public static void compute(MethodNode mth) {
List<BlockNode> sorted = sortBlocks(mth);
BlockNode[] doms = build(sorted);
BlockNode[] doms = build(sorted, BlockNode::getPredecessors);
apply(sorted, doms);
}
private static List<BlockNode> sortBlocks(MethodNode mth) {
int blocksCount = mth.getBasicBlocks().size();
List<BlockNode> sorted = new ArrayList<>(blocksCount);
BlockUtils.dfsVisit(mth, sorted::add);
BlockUtils.visitDFS(mth, sorted::add);
if (sorted.size() != blocksCount) {
throw new JadxRuntimeException("Found unreachable blocks");
}
@@ -38,8 +37,7 @@ public class DominatorTree {
return sorted;
}
@NotNull
private static BlockNode[] build(List<BlockNode> sorted) {
static BlockNode[] build(List<BlockNode> sorted, Function<BlockNode, List<BlockNode>> predFunc) {
int blocksCount = sorted.size();
BlockNode[] doms = new BlockNode[blocksCount];
doms[0] = sorted.get(0);
@@ -48,7 +46,7 @@ public class DominatorTree {
changed = false;
for (int blockId = 1; blockId < blocksCount; blockId++) {
BlockNode b = sorted.get(blockId);
List<BlockNode> preds = b.getPredecessors();
List<BlockNode> preds = predFunc.apply(b);
int pickedPred = -1;
BlockNode newIDom = null;
for (BlockNode pred : preds) {
@@ -60,7 +58,7 @@ public class DominatorTree {
}
}
if (newIDom == null) {
throw new JadxRuntimeException("No predecessors for block: " + b);
throw new JadxRuntimeException("No immediate dominator for block: " + b);
}
for (BlockNode predBlock : preds) {
int predId = predBlock.getId();
@@ -110,7 +108,7 @@ public class DominatorTree {
}
}
private static BitSet collectDoms(BlockNode[] doms, BlockNode idom) {
static BitSet collectDoms(BlockNode[] doms, BlockNode idom) {
BitSet domBS = new BitSet(doms.length);
BlockNode nextIDom = idom;
while (true) {
@@ -22,8 +22,7 @@ public class FixMultiEntryLoops {
}
List<SpecialEdgeAttr> specialEdges = mth.getAll(AType.SPECIAL_EDGE);
List<SpecialEdgeAttr> multiEntryLoops = specialEdges.stream()
.filter(e -> e.getType() == SpecialEdgeType.BACK_EDGE)
.filter(e -> !isSingleEntryLoop(e))
.filter(e -> e.getType() == SpecialEdgeType.BACK_EDGE && !isSingleEntryLoop(e))
.collect(Collectors.toList());
if (multiEntryLoops.isEmpty()) {
return false;
@@ -42,12 +41,21 @@ public class FixMultiEntryLoops {
}
private static boolean fixLoop(MethodNode mth, SpecialEdgeAttr backEdge, List<SpecialEdgeAttr> crossEdges) {
if (isHeaderSuccessorEntry(mth, backEdge, crossEdges)) {
return true;
}
if (isEndBlockEntry(mth, backEdge, crossEdges)) {
return true;
}
mth.addWarnComment("Unsupported multi-entry loop pattern (" + backEdge + "). Please report as a decompilation issue!!!");
return false;
}
private static boolean isHeaderSuccessorEntry(MethodNode mth, SpecialEdgeAttr backEdge, List<SpecialEdgeAttr> crossEdges) {
BlockNode header = backEdge.getEnd();
BlockNode headerIDom = header.getIDom();
SpecialEdgeAttr subEntry = ListUtils.filterOnlyOne(crossEdges, e -> e.getStart() == headerIDom);
if (subEntry == null || !isSupportedPattern(header, subEntry)) {
// TODO: for now only sub entry in header successor is supported
mth.addWarnComment("Unsupported multi-entry loop pattern (" + backEdge + "). Please submit an issue!!!");
if (subEntry == null || !ListUtils.isSingleElement(header.getSuccessors(), subEntry.getEnd())) {
return false;
}
BlockNode loopEnd = backEdge.getStart();
@@ -55,12 +63,28 @@ public class FixMultiEntryLoops {
BlockNode copyHeader = BlockSplitter.insertBlockBetween(mth, loopEnd, header);
BlockSplitter.copyBlockData(header, copyHeader);
BlockSplitter.replaceConnection(copyHeader, header, subEntryBlock);
mth.addDebugComment("Duplicate block to fix multi-entry loop: " + backEdge);
mth.addDebugComment("Duplicate block (" + header + ") to fix multi-entry loop: " + backEdge);
return true;
}
private static boolean isSupportedPattern(BlockNode header, SpecialEdgeAttr subEntry) {
return ListUtils.isSingleElement(header.getSuccessors(), subEntry.getEnd());
private static boolean isEndBlockEntry(MethodNode mth, SpecialEdgeAttr backEdge, List<SpecialEdgeAttr> crossEdges) {
BlockNode loopEnd = backEdge.getStart();
SpecialEdgeAttr subEntry = ListUtils.filterOnlyOne(crossEdges, e -> e.getEnd() == loopEnd);
if (subEntry == null) {
return false;
}
dupPath(mth, subEntry.getStart(), loopEnd, backEdge.getEnd());
mth.addDebugComment("Duplicate block (" + loopEnd + ") to fix multi-entry loop: " + backEdge);
return true;
}
/**
* Duplicate 'center' block on path from 'start' to 'end'
*/
private static void dupPath(MethodNode mth, BlockNode start, BlockNode center, BlockNode end) {
BlockNode copyCenter = BlockSplitter.insertBlockBetween(mth, start, end);
BlockSplitter.copyBlockData(center, copyCenter);
BlockSplitter.removeConnection(start, center);
}
private static boolean isSingleEntryLoop(SpecialEdgeAttr e) {
@@ -75,21 +99,18 @@ public class FixMultiEntryLoops {
}
private static void detectSpecialEdges(MethodNode mth) {
List<BlockNode> blocks = mth.getBasicBlocks();
BlockColor[] colors = new BlockColor[blocks.size()];
BlockColor[] colors = new BlockColor[mth.getBasicBlocks().size()];
Arrays.fill(colors, BlockColor.WHITE);
colorDFS(mth, blocks, colors, mth.getEnterBlock().getId());
colorDFS(mth, colors, mth.getEnterBlock());
}
// TODO: transform to non-recursive form
private static void colorDFS(MethodNode mth, List<BlockNode> blocks, BlockColor[] colors, int cur) {
colors[cur] = BlockColor.GRAY;
BlockNode block = blocks.get(cur);
private static void colorDFS(MethodNode mth, BlockColor[] colors, BlockNode block) {
colors[block.getId()] = BlockColor.GRAY;
for (BlockNode v : block.getSuccessors()) {
int vId = v.getId();
switch (colors[vId]) {
switch (colors[v.getId()]) {
case WHITE:
colorDFS(mth, blocks, colors, vId);
colorDFS(mth, colors, v);
break;
case GRAY:
mth.addAttr(AType.SPECIAL_EDGE, new SpecialEdgeAttr(SpecialEdgeType.BACK_EDGE, block, v));
@@ -99,6 +120,6 @@ public class FixMultiEntryLoops {
break;
}
}
colors[cur] = BlockColor.BLACK;
colors[block.getId()] = BlockColor.BLACK;
}
}
@@ -0,0 +1,72 @@
package jadx.core.dex.visitors.blocks;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.BlockUtils;
import jadx.core.utils.EmptyBitSet;
public class PostDominatorTree {
public static void compute(MethodNode mth) {
if (!mth.contains(AFlag.COMPUTE_POST_DOM)) {
return;
}
try {
int mthBlocksCount = mth.getBasicBlocks().size();
List<BlockNode> sorted = new ArrayList<>(mthBlocksCount);
BlockUtils.visitReverseDFS(mth, sorted::add);
// temporary set block ids to match reverse sorted order
// save old ids for later remapping
int blocksCount = sorted.size();
int[] ids = new int[mthBlocksCount];
for (int i = 0; i < blocksCount; i++) {
ids[i] = sorted.get(i).getId();
}
mth.updateBlockIds(sorted);
BlockNode[] postDoms = DominatorTree.build(sorted, BlockNode::getSuccessors);
BlockNode firstBlock = sorted.get(0);
firstBlock.setPostDoms(EmptyBitSet.EMPTY);
firstBlock.setIPostDom(null);
for (int i = 1; i < blocksCount; i++) {
BlockNode block = sorted.get(i);
BlockNode iPostDom = postDoms[i];
block.setIPostDom(iPostDom);
BitSet postDomBS = DominatorTree.collectDoms(postDoms, iPostDom);
block.setPostDoms(postDomBS);
}
for (int i = 1; i < blocksCount; i++) {
BlockNode block = sorted.get(i);
BitSet bs = new BitSet(blocksCount);
block.getPostDoms().stream().forEach(n -> bs.set(ids[n]));
bs.clear(ids[i]);
block.setPostDoms(bs);
}
// check for missing blocks in 'sorted' list
// can be caused by infinite loops
int blocksDelta = mthBlocksCount - blocksCount;
if (blocksDelta != 0) {
int insnsCount = 0;
for (BlockNode block : mth.getBasicBlocks()) {
if (block.getPostDoms() == null) {
block.setPostDoms(EmptyBitSet.EMPTY);
block.setIPostDom(null);
insnsCount += block.getInstructions().size();
}
}
mth.addInfoComment("Infinite loop detected, blocks: " + blocksDelta + ", insns: " + insnsCount);
}
} catch (Throwable e) {
// show error as a warning because this info not always used
mth.addWarnComment("Failed to build post-dominance tree", e);
} finally {
// revert block ids change
mth.updateBlockIds(mth.getBasicBlocks());
}
}
}
@@ -89,7 +89,7 @@ public class DebugInfoApplyVisitor extends AbstractVisitor {
return;
}
OptionalInt max = ssaVar.getUseList().stream().mapToInt(DebugInfoApplyVisitor::getInsnOffsetByArg).max();
if (!max.isPresent()) {
if (max.isEmpty()) {
return;
}
int startOffset = getInsnOffsetByArg(ssaVar.getAssign());
@@ -0,0 +1,45 @@
package jadx.core.dex.visitors.prepare;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.ConstStorage;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.AbstractVisitor;
import jadx.core.dex.visitors.JadxVisitor;
import jadx.core.utils.android.AndroidResourcesMap;
import jadx.core.utils.exceptions.JadxException;
// TODO: move this pass to separate "Android plugin"
@JadxVisitor(
name = "AddAndroidConstants",
desc = "Insert Android constants from resource mapping file",
runBefore = {
CollectConstValues.class
}
)
public class AddAndroidConstants extends AbstractVisitor {
private static final String R_CLS = "android.R";
private static final String R_INNER_CLS = R_CLS + '$';
@Override
public void init(RootNode root) throws JadxException {
if (!root.getArgs().isReplaceConsts()) {
return;
}
if (root.resolveClass(R_CLS) != null) {
// Android R class already loaded
return;
}
ConstStorage constStorage = root.getConstValues();
AndroidResourcesMap.getMap().forEach((resId, path) -> {
int sep = path.indexOf('/');
String clsName = R_INNER_CLS + path.substring(0, sep);
String resName = path.substring(sep + 1);
ClassInfo cls = ClassInfo.fromName(root, clsName);
FieldInfo field = FieldInfo.from(root, cls, resName, ArgType.INT);
constStorage.addGlobalConstField(field, resId);
});
}
}

Some files were not shown because too many files have changed in this diff Show More