Compare commits

...

83 Commits

Author SHA1 Message Date
Skylot 87e0e5bf16 fix: correct inline/merge with overriden bridge method (#1580) 2022-07-20 14:49:37 +01:00
Skylot e4c2d6cf6e fix(gui): use editor font for usage label 2022-07-20 14:36:51 +01:00
Skylot fb0bdb5112 fix(gui): allow to use empty name to reset rename 2022-07-20 14:35:53 +01:00
Skylot f4b3645435 fix: ignore anonymous classes in enclosing node search (#1580) 2022-07-19 19:25:17 +01:00
Skylot c27f2badf7 fix(gui): resolve payload offset for switch insns in debug smali code (#1575) 2022-07-18 18:50:48 +01:00
Skylot 1a877d6535 fix: resolve possible decompilation double execution 2022-07-16 22:29:59 +03:00
Skylot 5ada9331b6 chore: update dependencies 2022-07-14 14:33:04 +01:00
Skylot a0f4ccb7a4 fix: update deps and fix proto resource loading (AAB) (#1129) 2022-07-14 14:33:04 +01:00
Skylot 5b5524a7dd fix: handle parent of inlined/moved classes (#1578) 2022-07-14 14:40:13 +03:00
Jan S 3cc464c9c9 fix: IndexOutOfBoundsException in JumpManager (#1576) (PR #1577) 2022-07-13 17:24:20 +01:00
Skylot 51555667cf fix: add more checks before remove or rename enum methods (#1572) 2022-07-07 16:55:32 +01:00
Skylot e01ea7010f fix: save classes with code generation error into cache (#1568) 2022-07-03 19:32:41 +01:00
Skylot 77732c83c9 fix(gui): ignore/limit waiting of canceled search task (#1568) 2022-07-01 17:57:59 +01:00
Skylot a67fc83949 fix: better dominators algorithms 2022-07-01 17:26:54 +01:00
Skylot 3d920725aa fix: check synthetic methods before remove/inline (#1560) 2022-06-29 19:19:54 +01:00
Skylot 2f2fbea558 fix(gui): check user renames (#1557) 2022-06-29 16:21:49 +01:00
Skylot e7a86a2960 fix(gui): forbid rename method args in fallback mode (#1558) 2022-06-29 15:25:23 +01:00
Skylot b282d97ffe fix(gui): set current dir directly in file chooser constructor (#1553) 2022-06-28 16:57:57 +01:00
Skylot e4ca52a95f chore: update dependencies 2022-06-28 16:23:31 +01:00
Skylot d972d9ec74 fix(gui): ignore errors on code area dispose (#1545) 2022-06-28 16:20:31 +01:00
Jan S 0721a6b050 fix(gui): QuarkReport data validation added and other minor improvements (PR #1556)
* QuarkReport: data validation added and other minor improvements
* checkStyle
2022-06-25 22:24:53 +03:00
zhongqingsong 762ee6550e fix(gui):complete Chinese translation (PR #1549)
1、Complete Chinese translation
2、Previous word polish
2022-06-25 22:15:22 +03:00
Skylot 18070eb7a6 fix(gui): allow to select file on mapping export 2022-06-20 14:19:59 +01:00
Skylot 8486891728 fix(gui): resolve various minor issues 2022-06-20 13:17:50 +01:00
Skylot 4679172d4f fix(gui): correct set highlighted text in search (#1507) 2022-06-20 13:16:42 +01:00
Skylot 92a6c333d8 fix(gui): force jadx new version check by default 2022-06-20 12:55:15 +01:00
Skylot 358adbdd65 feat(gui): allow to disable jump on double click (#1540) 2022-06-19 17:19:08 +01:00
Skylot 65f7c80222 feat(gui): add reload and live reload actions (#1537) 2022-06-18 20:20:11 +01:00
Skylot d2e6bb236e fix: use wide move for long/double store/load java opcodes 2022-06-16 16:26:14 +01:00
Skylot eaeb114258 fix: check class name collisions (#1526) 2022-06-15 18:43:33 +01:00
Skylot 1533b7fe6e fix: keep types on duplicate cast remove (#1527) 2022-06-12 21:55:12 +01:00
Julian Burner a2cd8e1ead feat(gui): support export to deobfuscation mapping file formats (#1491)(PR #1505)
* Add option to export mappings as Tiny v2 file

* Comply with JADX's import order conventions

* Only use Java 8 features

* Only use Java 8 features (2)

* Export comments to mappings file

* Method args test (doesn't work)

* Make method arg mapping exports work now

* Use `getTopParentClass()` instead of `getParentClass()`

See https://github.com/skylot/jadx/pull/1505#issuecomment-1145064865

* Remove unneeded method load call

* Small code cleanup; initial (broken) support for method vars

* Fixes regarding inner classes

* Add option to export mappings as Enigma directory

* Add option to export mappings as Enigma file/directory

Temporarily move to my mapping-io fork until this PR gets merged: https://github.com/FabricMC/mapping-io/pull/19

* Fix method vars' lv-indices

* Use correct offset value for method var mappings

* Also supply lvt-index for method var mappings

* Clarify why we're using local mapping-io fork; comment out Fabric Maven for now

* Remove unnecessary `public` modifier

* Make an `if` condition less complicated

* Move mapping export code into jadx-gui (for now)

* Make mapping export async; make export menu only clickable when everything is loaded

* Fix export mappings menu field declaration position
2022-06-11 20:19:08 +01:00
Christian Jones 4edb512121 fix(cli): allow decoding resource-only APKs (#1517)(PR #1530)
* Process resource-only inputs
* Fix error, add testing
2022-06-11 15:40:39 +01:00
Skylot 702b88228c fix(gui): resolve popup menu action run (#1514, #1529) 2022-06-11 15:08:28 +01:00
Skylot 14fd88b2f8 fix(gui): add volatile and update sync for decompiler field in wrapper (#1518) 2022-06-08 21:06:57 +01:00
Skylot 20657e8bb5 doc(cli): improve plugins section formatting 2022-06-06 19:55:58 +01:00
Skylot 93d3194e3b doc: remove incorrect tokei badge 2022-06-06 19:54:15 +01:00
Skylot 39331d9120 fix: remove deprecated --deobf-rewrite-cfg (#1513) 2022-06-06 19:53:47 +01:00
Skylot b4fa6644bc doc: add more badges 2022-06-05 16:06:19 +01:00
Skylot 0b2e2ed034 fix: improve class search for super call (#1512) 2022-06-05 14:49:34 +01:00
Skylot 81231206f3 fix(gui): reset disk cache on new jadx version 2022-06-04 23:26:25 +01:00
Skylot 49d0e76272 fix: support all-catch in multi-catch (#1510) 2022-06-04 23:25:52 +01:00
CmP-lt 0809993b37 fix(gui): improve restoration of windows saved state (PR #1511)
* Fix restoration of windows saved state
* Don't skip restoration of window saved bounds when they intersect with screen bounds.
* Restore saved bounds of main window regardless of it's saved extended state (fixes divider location of main split pane being restored incorrectly when saved state of main window is maximized).
* Add handling for out-of-screen(s) window bounds
2022-06-04 17:41:00 +01:00
Skylot 0c3afcc24c fix(gui): try to prevent jadx node leaks in UI objects 2022-06-03 16:15:14 +01:00
Skylot d6c851eed4 test: fix method code extract 2022-06-02 19:33:16 +01:00
Skylot dcf4a7c4e3 fix(gui): try to resolve some causes of memory leak 2022-06-01 19:48:51 +01:00
Skylot 9ba07b986b fix(gui): reduce usage of nullable decompiler field in jadx wrapper (#1506) 2022-06-01 16:36:30 +01:00
Skylot e6b6b93cbb fix: improve blocks tree compare for finally extract (#1501) 2022-05-31 20:57:34 +01:00
Skylot fcd58ae76f fix: remap class names for store in disk cache (#1503) 2022-05-30 18:16:05 +01:00
Skylot df380dea27 chore: update dependencies 2022-05-30 20:06:23 +03:00
CKCat 9d88592391 fix(res): ignore version in AndroidManifest.xml (#1502)(PR #1504) 2022-05-30 14:10:10 +01:00
dependabot[bot] c906c11b0f build(deps): bump github/codeql-action from 1 to 2 (PR #1500)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 1 to 2.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v1...v2)

---
updated-dependencies:
- dependency-name: github/codeql-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>
2022-05-28 18:04:08 +01:00
Skylot 4fbc56cdb0 build: add unstable win-with-jre bundle 2022-05-28 17:46:48 +01:00
Skylot 98c0416b20 fix(gui): correct close and reopen for decompiler and cache 2022-05-28 16:41:37 +03:00
ZachQin fa41874e30 feat(gui): add parameters logging in Frida code snippet (#1497)(PR #1498) 2022-05-28 14:39:02 +01:00
Skylot 2aa6c99c90 fix: skip dex files with parsing errors (#1495) 2022-05-28 13:27:29 +01:00
Skylot 5f60c0f1bb build: fix google-java-format for all java versions 2022-05-27 22:51:52 +01:00
Skylot cb741db623 fix: improve usage search, refactor java nodes creation (#1489) 2022-05-27 17:56:08 +01:00
Skylot 1df217c4a0 fix: save cache dir for reuse on project save/reopen 2022-05-27 16:50:13 +01:00
Skylot 81f209ba9e fix: check if directory exists before delete (#1493) 2022-05-26 19:23:59 +03:00
zhongqingsong 34a31aa7df fix(gui): complete Chinese Translation (PR #1492)
1. Complete translation of Chinese
2. Polish up part of the translation
3. Restore all text for subsequent translation
2022-05-24 19:09:11 +01:00
Skylot 5099e02c9b fix(gui): correct merge for plugin options from command line (#1490) 2022-05-23 19:44:49 +01:00
Skylot f364b39b29 fix(gui): save full type info in metadata (#1487) 2022-05-22 16:18:38 +01:00
Skylot 4cd4746f9a fix(gui): save variable name to show in tooltip (#1487) 2022-05-22 15:34:09 +01:00
Skylot 6448f0e32b fix: use variable length encoding instead short for offsets (can overflow) (#1489) 2022-05-22 14:23:15 +01:00
Skylot e07332d49a fix(gui): resolve cast exception for variable reference (#1489) 2022-05-21 21:33:58 +01:00
Skylot bd8a44c4c9 fix(gui): correct handle of selected file in save dialog 2022-05-21 21:31:47 +01:00
Skylot 21e94d8d5c fix(gui): use alias for types in tooltips (#1487) 2022-05-20 22:09:39 +01:00
Skylot 7b1c7b967a fix: use alias for variable names (#1487) 2022-05-20 22:09:39 +01:00
Skylot e4b19ab560 fix(gui): add missing Use debug info option 2022-05-20 22:09:39 +01:00
Skylot 49137c9751 fix(cli): don't ignore critical errors (#1150)
Thrown java.lang.Error was ignored and not logged.
2022-05-19 23:12:19 +01:00
skylot 0606c90f22 feat(gui): disk code cache and search rewrite (PR #1483)
* feat: implement disk code cache
* feat: rewrite code metadata handling, remove code index
* feat: rewrite search
* fix: code cleanup and fixes for previous commits
* feat: run code search in parallel
* fix: reset code strings cache on low memory, code cleanup
* fix: include input files timestamp into code hash
2022-05-18 15:19:31 +01:00
Jan S 65ade379a6 fix(gui): escape class- method and field names in frida code snippet (PR #1480) 2022-05-10 19:43:15 +01:00
Skylot a06df187c9 fix(gui): ask for project file path on exit (#1474) 2022-05-08 14:55:25 +03:00
Jan S e784c7f7df fix(gui): editor theme loading and error/fallback handling improved (#1476)(PR #1478)
* fix(gui): editor theme loading and error/fallback handling improved
* Update jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java

Co-authored-by: skylot <118523+skylot@users.noreply.github.com>
2022-05-08 12:53:48 +01:00
Skylot a717392379 fix: workaround to prevent incorrect order after move inline (#1472) 2022-04-29 15:37:07 +01:00
Skylot a71b3a71d8 fix: better code styling for if-else blocks (#1455) 2022-04-26 20:18:06 +01:00
Skylot 3366bf3dec chore: update dependencies 2022-04-26 20:48:18 +03:00
Jan S a505534197 fix(gui): fix IndexOutOfBoundsException when switching between tabs via mouse wheel (#1456)(PR #1469) 2022-04-26 17:27:39 +01:00
Nelson Gregório 357706b070 feat: allow to include/exclude dependencies when saving with class filter (#1466)(PR #1467)
* feat: Add option to include/exclude dependencies when saving
* fix save skip for class depencencies

Co-authored-by: Skylot <skylot@gmail.com>
2022-04-26 17:18:51 +01:00
Skylot e02434d135 fix(gui): confirm directory loading on file open (#1462) 2022-04-25 14:32:00 +03:00
Jan S 4586015fc0 fix(gui): resolve NPE on project save (#1463)(PR #1464)
* fix(gui): NullPointerException on project save

* chore(gui): ensure MainWindow.project is never null
* ensure `files` in `ProjectData` in not null

Co-authored-by: Skylot <skylot@gmail.com>
2022-04-24 10:32:25 +01:00
Skylot 1832f2aee3 feat: allow to load custom input (#1457) 2022-04-21 13:21:13 +01:00
293 changed files with 9537 additions and 4316 deletions
+7
View File
@@ -0,0 +1,7 @@
version: 2
updates:
# Set update schedule for GitHub Actions
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
+86
View File
@@ -0,0 +1,86 @@
name: Build Artifacts
on:
push:
branches: [ master, build-test ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up JDK
uses: actions/setup-java@v3
with:
distribution: 'adopt'
java-version: 8
- name: Set jadx version
run: |
JADX_LAST_TAG=$(git describe --abbrev=0 --tags)
JADX_VERSION="${JADX_LAST_TAG:1}.$GITHUB_RUN_NUMBER-${GITHUB_SHA:0:8}"
echo "JADX_VERSION=$JADX_VERSION" >> $GITHUB_ENV
- uses: burrunan/gradle-cache-action@v1
name: Build with Gradle
env:
TERM: dumb
with:
arguments: clean dist copyExe
- name: Save bundle artifact
uses: actions/upload-artifact@v3
with:
name: ${{ format('jadx-{0}', env.JADX_VERSION) }}
# Waiting fix for https://github.com/actions/upload-artifact/issues/39 to upload zip file
# Upload unpacked files for now
path: build/jadx/**/*
if-no-files-found: error
retention-days: 30
- name: Save exe artifact
uses: actions/upload-artifact@v3
with:
name: ${{ format('jadx-gui-{0}-no-jre-win.exe', env.JADX_VERSION) }}
path: build/*.exe
if-no-files-found: error
retention-days: 30
build-win-bundle:
runs-on: windows-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up JDK
uses: oracle-actions/setup-java@v1 # set latest java version by default
- name: Print Java version
shell: bash
run: java -version
- name: Set jadx version
shell: bash
run: |
JADX_LAST_TAG=$(git describe --abbrev=0 --tags)
JADX_VERSION="${JADX_LAST_TAG:1}.$GITHUB_RUN_NUMBER-${GITHUB_SHA:0:8}"
echo "JADX_VERSION=$JADX_VERSION" >> $GITHUB_ENV
- uses: gradle/gradle-build-action@v2
name: Build with Gradle
env:
TERM: dumb
with:
arguments: clean dist -PbundleJRE=true
- name: Save exe bundle artifact
uses: actions/upload-artifact@v3
with:
name: ${{ format('jadx-gui-{0}-with-jre-win', env.JADX_VERSION) }}
path: jadx-gui/build/*-with-jre-win/*
if-no-files-found: error
retention-days: 30
+28
View File
@@ -0,0 +1,28 @@
name: Build Test
on:
push:
branches: [ master, build-test ]
pull_request:
branches: [ master ]
jobs:
tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up JDK
uses: actions/setup-java@v3
with:
distribution: 'adopt'
java-version: 8
- uses: burrunan/gradle-cache-action@v1
name: Build with Gradle
env:
TERM: dumb
with:
arguments: clean build dist copyExe --warning-mode=all
-52
View File
@@ -1,52 +0,0 @@
name: Build
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Set up JDK
uses: actions/setup-java@v1
with:
java-version: 8
- name: Set jadx version
run: |
JADX_LAST_TAG=$(git describe --abbrev=0 --tags)
JADX_VERSION="${JADX_LAST_TAG:1}.$GITHUB_RUN_NUMBER-${GITHUB_SHA:0:8}"
echo "JADX_VERSION=$JADX_VERSION" >> $GITHUB_ENV
- uses: burrunan/gradle-cache-action@v1
name: Build with Gradle
env:
TERM: dumb
TEST_INPUT_PLUGIN: dx
with:
arguments: clean build dist copyExe --warning-mode=all
- name: Save bundle artifact
if: success() && github.event_name == 'push'
uses: actions/upload-artifact@v2
with:
name: ${{ format('jadx-{0}', env.JADX_VERSION) }}
# Waiting fix for https://github.com/actions/upload-artifact/issues/39 to upload zip file
# Upload unpacked files for now
path: build/jadx/**/*
if-no-files-found: error
- name: Save exe artifact
if: success() && github.event_name == 'push'
uses: actions/upload-artifact@v2
with:
name: ${{ format('jadx-gui-{0}-no-jre-win.exe', env.JADX_VERSION) }}
path: build/*.exe
if-no-files-found: error
+2 -2
View File
@@ -28,7 +28,7 @@ jobs:
uses: actions/checkout@v2
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
uses: github/codeql-action/init@v2
with:
queries: +security-extended
languages: ${{ matrix.language }}
@@ -38,4 +38,4 @@ jobs:
./gradlew clean build -x checkstyleTest -x checkstyleMain -x test -x ':jadx-core:testClasses'
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
uses: github/codeql-action/analyze@v2
+2 -1
View File
@@ -35,4 +35,5 @@ jadx-output/
*.orig
quark.json
cliff.toml
cliff.toml
jadx-gui/src/main/resources/logback.xml
+20 -14
View File
@@ -3,8 +3,10 @@
## JADX
[![Build status](https://github.com/skylot/jadx/workflows/Build/badge.svg)](https://github.com/skylot/jadx/actions?query=workflow%3ABuild)
[![Alerts from lgtm.com](https://img.shields.io/lgtm/alerts/g/skylot/jadx.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/skylot/jadx/alerts/)
[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)
![GitHub contributors](https://img.shields.io/github/contributors/skylot/jadx)
![GitHub all releases](https://img.shields.io/github/downloads/skylot/jadx/total)
![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)
[![License](http://img.shields.io/:license-apache-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0.html)
@@ -33,8 +35,9 @@ See these features in action here: [jadx-gui features overview](https://github.c
<img src="https://user-images.githubusercontent.com/118523/142730720-839f017e-38db-423e-b53f-39f5f0a0316f.png" width="700"/>
### Download
- release from [github: ![Latest release](https://img.shields.io/github/release/skylot/jadx.svg)](https://github.com/skylot/jadx/releases/latest)
- latest [unstable build](https://nightly.link/skylot/jadx/workflows/build/master)
- release
from [github: ![Latest release](https://img.shields.io/github/release/skylot/jadx.svg)](https://github.com/skylot/jadx/releases/latest)
- latest [unstable build ![GitHub commits since tagged version (branch)](https://img.shields.io/github/commits-since/skylot/jadx/latest/master)](https://nightly.link/skylot/jadx/workflows/build-artifacts/master)
After download unpack zip file go to `bin` directory and run:
- `jadx` - command line version
@@ -45,14 +48,18 @@ On Windows run `.bat` files with double-click\
For Windows, you can download it from [oracle.com](https://www.oracle.com/java/technologies/downloads/#jdk17-windows) (select x64 Installer).
### Install
1. Arch linux
1. Arch linux ![Arch Linux package](https://img.shields.io/archlinux/v/community/any/jadx?label=)
```bash
sudo pacman -S jadx
sudo pacman -S jadx
```
2. macOS
2. macOS ![homebrew version](https://img.shields.io/homebrew/v/jadx?label=)
```bash
brew install jadx
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
```
### Use jadx as a library
You can use jadx in your java projects, check details on [wiki page](https://github.com/skylot/jadx/wiki/Use-jadx-as-a-library)
@@ -107,7 +114,6 @@ options:
'read-or-save' - read if found, save otherwise (don't overwrite)
'overwrite' - don't read, always save
'ignore' - don't read and don't save
--deobf-rewrite-cfg - set '--deobf-cfg-file-mode' to 'overwrite' (deprecated)
--deobf-use-sourcename - use source file name as class name alias
--deobf-parse-kotlin-metadata - parse kotlin metadata to class and package names
--use-kotlin-methods-for-var-names - use kotlin intrinsic methods to rename variables, values: disable, apply, apply-and-hide, default: apply
@@ -130,11 +136,11 @@ options:
-h, --help - print this help
Plugin options (-P<name>=<value>):
1) dex-input (Load .dex and .apk files)
-Pdex-input.verify-checksum - Verify dex file checksum before load, values: [yes, no], default: yes
2) java-convert (Convert .jar and .class files to dex)
-Pjava-convert.mode - Convert mode, values: [dx, d8, both], default: both
-Pjava-convert.d8-desugar - Use desugar in d8, values: [yes, no], default: no
1) dex-input: Load .dex and .apk files
- dex-input.verify-checksum - verify dex file checksum before load, values: [yes, no], default: yes
2) java-convert: Convert .class, .jar and .aar files to dex
- java-convert.mode - convert mode, values: [dx, d8, both], default: both
- java-convert.d8-desugar - use desugar in d8, values: [yes, no], default: no
Examples:
jadx -d out classes.dex
-33
View File
@@ -1,33 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop">
<id>com.github.skylot.jadx</id>
<metadata_license>CC0-1.0</metadata_license>
<project_license>Apache-2.0</project_license>
<name>JADX</name>
<summary>Dex to Java decompiler</summary>
<description>
<p>Command line and GUI tools for producing Java source code from Android Dex and Apk files</p>
<ul>
<li>decompile Dalvik bytecode to java classes from APK, dex, aar, aab and zip files</li>
<li>decode AndroidManifest.xml and other resources from resources.arsc</li>
<li>deobfuscator included</li>
<li>view decompiled code with highlighted syntax</li>
<li>jump to declaration</li>
<li>find usage</li>
<li>full text search</li>
<li>smali debugger</li>
</ul>
</description>
<screenshots>
<screenshot type="default">
<image>https://user-images.githubusercontent.com/118523/142730720-839f017e-38db-423e-b53f-39f5f0a0316f.png</image>
</screenshot>
</screenshots>
<content_rating type="oars-1.1" />
<launchable type="desktop-id">com.github.skylot.jadx.desktop</launchable>
<url type="homepage">https://github.com/skylot/jadx</url>
<url type="bugtracker">https://github.com/skylot/jadx/issues</url>
<releases>
<release version="1.3.4" date="2022-03-20" />
</releases>
</component>
+9 -11
View File
@@ -1,6 +1,6 @@
plugins {
id 'com.github.ben-manes.versions' version '0.42.0'
id 'com.diffplug.spotless' version '6.4.2'
id 'com.diffplug.spotless' version '6.8.0'
}
ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev"
@@ -14,7 +14,6 @@ allprojects {
version = jadxVersion
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
compileJava {
options.encoding = "UTF-8"
@@ -32,8 +31,8 @@ allprojects {
testImplementation 'ch.qos.logback:logback-classic:1.2.11'
testImplementation 'org.hamcrest:hamcrest-library:2.2'
testImplementation 'org.mockito:mockito-core:4.4.0'
testImplementation 'org.assertj:assertj-core:3.22.0'
testImplementation 'org.mockito:mockito-core:4.6.1'
testImplementation 'org.assertj:assertj-core:3.23.1'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2'
@@ -50,6 +49,11 @@ allprojects {
mavenLocal()
mavenCentral()
google()
// Commented out for now since we're using a local mapping-io fork atm.
// maven {
// name 'FabricMC'
// url 'https://maven.fabricmc.net/'
// }
}
}
@@ -64,13 +68,7 @@ spotless {
importOrderFile 'config/code-formatter/eclipse.importorder'
eclipse().configFile 'config/code-formatter/eclipse.xml'
if (JavaVersion.current() < JavaVersion.VERSION_16) {
removeUnusedImports()
} else {
// google-format on Java 16+ issue: https://github.com/diffplug/spotless/issues/834
println('Warning! Unused imports remove is disabled for Java 16+'
+ ' (use workaround from https://github.com/diffplug/spotless/tree/main/plugin-gradle#google-java-format)')
}
removeUnusedImports()
lineEndings(com.diffplug.spotless.LineEnding.UNIX)
encoding("UTF-8")
+9
View File
@@ -1,2 +1,11 @@
org.gradle.warning.mode=all
org.gradle.parallel=true
# Flags for google-java-format (optimize imports by spotless) for Java >= 16.
# Java < 9 will ignore unsupported flags (thanks to -XX:+IgnoreUnrecognizedVMOptions)
org.gradle.jvmargs=-XX:+IgnoreUnrecognizedVMOptions \
--add-exports='jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED' \
--add-exports='jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED' \
--add-exports='jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED' \
--add-exports='jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED' \
--add-exports='jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED'
@@ -8,6 +8,7 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.function.Supplier;
import org.jetbrains.annotations.Nullable;
@@ -22,6 +23,7 @@ import jadx.api.plugins.JadxPluginInfo;
import jadx.api.plugins.JadxPluginManager;
import jadx.api.plugins.options.JadxPluginOptions;
import jadx.api.plugins.options.OptionDescription;
import jadx.core.utils.Utils;
public class JCommanderWrapper<T> {
private final JCommander jc;
@@ -50,12 +52,24 @@ public class JCommanderWrapper<T> {
if (parameter.isAssigned()) {
// copy assigned field value to obj
Parameterized parameterized = parameter.getParameterized();
Object val = parameterized.get(parameter.getObject());
parameterized.set(obj, val);
Object providedValue = parameterized.get(parameter.getObject());
Object newValue = mergeValues(parameterized.getType(), providedValue, () -> parameterized.get(obj));
parameterized.set(obj, newValue);
}
}
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private static Object mergeValues(Class<?> type, Object value, Supplier<Object> prevValueProvider) {
if (type.isAssignableFrom(Map.class)) {
// merge maps instead replacing whole map
Map prevMap = (Map) prevValueProvider.get();
return Utils.mergeMaps(prevMap, (Map) value); // value map will override keys in prevMap
}
// simple override
return value;
}
public void printUsage() {
// print usage in not sorted fields order (by default its sorted by description)
PrintStream out = System.out;
@@ -179,11 +193,11 @@ public class JCommanderWrapper<T> {
return false;
}
JadxPluginInfo pluginInfo = plugin.getPluginInfo();
out.append("\n ").append(k).append(") ");
out.append(pluginInfo.getPluginId()).append(" (").append(pluginInfo.getDescription()).append(") ");
out.append("\n ").append(k).append(") ");
out.append(pluginInfo.getPluginId()).append(": ").append(pluginInfo.getDescription());
for (OptionDescription desc : descs) {
StringBuilder opt = new StringBuilder();
opt.append(" -P").append(desc.name());
opt.append(" - ").append(desc.name());
addSpaces(opt, maxNamesLen - opt.length());
opt.append("- ").append(desc.description());
if (!desc.values().isEmpty()) {
+9 -3
View File
@@ -21,7 +21,7 @@ public class JadxCLI {
} catch (JadxArgsValidateException e) {
LOG.error("Incorrect arguments: {}", e.getMessage());
result = 1;
} catch (Exception e) {
} catch (Throwable e) {
LOG.error("Process error:", e);
result = 1;
} finally {
@@ -66,8 +66,14 @@ public class JadxCLI {
private static boolean checkForErrors(JadxDecompiler jadx) {
if (jadx.getRoot().getClasses().isEmpty()) {
LOG.error("Load failed! No classes for decompile!");
return true;
if (jadx.getArgs().isSkipResources()) {
LOG.error("Load failed! No classes for decompile!");
return true;
}
if (!jadx.getArgs().isSkipSources()) {
LOG.warn("No classes to decompile; decoding resources only");
jadx.getArgs().setSkipSources(true);
}
}
if (jadx.getErrorsCount() > 0) {
LOG.error("Load with errors! Check log for details");
@@ -123,9 +123,6 @@ public class JadxCLIArgs {
)
protected DeobfuscationMapFileMode deobfuscationMapFileMode = DeobfuscationMapFileMode.READ;
@Parameter(names = { "--deobf-rewrite-cfg" }, description = "set '--deobf-cfg-file-mode' to 'overwrite' (deprecated)")
protected boolean deobfuscationForceSave = false;
@Parameter(names = { "--deobf-use-sourcename" }, description = "use source file name as class name alias")
protected boolean deobfuscationUseSourceNameAsAlias = false;
@@ -259,11 +256,7 @@ public class JadxCLIArgs {
args.setReplaceConsts(replaceConsts);
args.setDeobfuscationOn(deobfuscationOn);
args.setDeobfuscationMapFile(FileUtils.toFile(deobfuscationMapFile));
if (deobfuscationForceSave) {
args.setDeobfuscationMapFileMode(DeobfuscationMapFileMode.OVERWRITE);
} else {
args.setDeobfuscationMapFileMode(deobfuscationMapFileMode);
}
args.setDeobfuscationMapFileMode(deobfuscationMapFileMode);
args.setDeobfuscationMinLength(deobfuscationMinLength);
args.setDeobfuscationMaxLength(deobfuscationMaxLength);
args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias);
@@ -377,10 +370,6 @@ public class JadxCLIArgs {
return deobfuscationMapFileMode;
}
public boolean isDeobfuscationForceSave() {
return deobfuscationForceSave;
}
public boolean isDeobfuscationUseSourceNameAsAlias() {
return deobfuscationUseSourceNameAsAlias;
}
@@ -1,9 +1,14 @@
package jadx.cli;
import java.util.Collections;
import java.util.Map;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static jadx.core.utils.Utils.newConstStringMap;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
@@ -47,6 +52,40 @@ public class JadxCLIArgsTest {
assertThat(override(args, "").isUseImports(), is(false));
}
@Test
public void testPluginOptionsOverride() {
// add key to empty base map
checkPluginOptionsMerge(
Collections.emptyMap(),
"-Poption=otherValue",
newConstStringMap("option", "otherValue"));
// override one key
checkPluginOptionsMerge(
newConstStringMap("option", "value"),
"-Poption=otherValue",
newConstStringMap("option", "otherValue"));
// merge different keys
checkPluginOptionsMerge(
Collections.singletonMap("option1", "value1"),
"-Poption2=otherValue2",
newConstStringMap("option1", "value1", "option2", "otherValue2"));
// merge and override
checkPluginOptionsMerge(
newConstStringMap("option1", "value1", "option2", "value2"),
"-Poption2=otherValue2",
newConstStringMap("option1", "value1", "option2", "otherValue2"));
}
private void checkPluginOptionsMerge(Map<String, String> baseMap, String providedArgs, Map<String, String> expectedMap) {
JadxCLIArgs args = new JadxCLIArgs();
args.pluginOptions = baseMap;
Map<String, String> resultMap = override(args, providedArgs).getPluginOptions();
assertThat(resultMap, Matchers.equalTo(expectedMap));
}
private JadxCLIArgs parse(String... args) {
return parse(new JadxCLIArgs(), args);
}
@@ -44,6 +44,33 @@ public class TestInput {
decompile("multi", "samples/hello.dex", "samples/HelloWorld.smali");
}
@Test
public void testResourceOnly() throws Exception {
decode("resourceOnly", "samples/resources-only.apk");
}
private void decode(String tmpDirName, String apkSample) throws URISyntaxException, IOException {
List<String> args = new ArrayList<>();
Path tempDir = FileUtils.createTempDir(tmpDirName);
args.add("-v");
args.add("-d");
args.add(tempDir.toAbsolutePath().toString());
URL resource = getClass().getClassLoader().getResource(apkSample);
assertThat(resource).isNotNull();
String sampleFile = resource.toURI().getRawPath();
args.add(sampleFile);
int result = JadxCLI.execute(args.toArray(new String[0]));
assertThat(result).isEqualTo(0);
List<Path> files = Files.find(
tempDir,
3,
(file, attr) -> file.getFileName().toString().equalsIgnoreCase("AndroidManifest.xml"))
.collect(Collectors.toList());
assertThat(files.isEmpty()).isFalse();
}
private void decompile(String tmpDirName, String... inputSamples) throws URISyntaxException, IOException {
List<String> args = new ArrayList<>();
Path tempDir = FileUtils.createTempDir(tmpDirName);
+6 -7
View File
@@ -6,21 +6,20 @@ dependencies {
api(project(':jadx-plugins:jadx-plugins-api'))
implementation 'com.google.code.gson:gson:2.9.0'
implementation 'com.android.tools.build:aapt2-proto:4.2.1-7147631'
constraints {
// Force protobuf version to prevent Java-7 issue
implementation 'com.google.protobuf:protobuf-java:3.11.4'
}
// TODO: move resources decoding to separate plugin module
implementation 'com.android.tools.build:aapt2-proto:7.2.1-7984345'
implementation 'com.google.protobuf:protobuf-java:3.21.2' // forcing latest version
testImplementation 'org.apache.commons:commons-lang3:3.12.0'
testRuntimeOnly(project(':jadx-plugins:jadx-dex-input'))
testImplementation(project(':jadx-plugins:jadx-dex-input'))
testRuntimeOnly(project(':jadx-plugins:jadx-smali-input'))
testRuntimeOnly(project(':jadx-plugins:jadx-java-convert'))
testRuntimeOnly(project(':jadx-plugins:jadx-java-input'))
testRuntimeOnly(project(':jadx-plugins:jadx-raung-input'))
testImplementation 'org.eclipse.jdt:ecj:3.29.0'
testImplementation 'org.eclipse.jdt:ecj:3.30.0'
testImplementation 'tools.profiler:async-profiler:1.8.3'
}
@@ -1,65 +0,0 @@
package jadx.api;
public final class CodePosition {
private final int line;
private final int offset;
private final int pos;
public CodePosition(int line, int offset, int pos) {
this.line = line;
this.offset = offset;
this.pos = pos;
}
public CodePosition(int line) {
this(line, 0, -1);
}
@Deprecated
public CodePosition(int line, int offset) {
this(line, offset, -1);
}
public int getPos() {
return pos;
}
public int getLine() {
return line;
}
public int getOffset() {
return offset;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
CodePosition that = (CodePosition) o;
return line == that.line && offset == that.offset;
}
@Override
public int hashCode() {
return line + 31 * offset;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(line);
if (offset != 0) {
sb.append(':').append(offset);
}
if (pos > 0) {
sb.append('@').append(pos);
}
return sb.toString();
}
}
@@ -1,13 +1,21 @@
package jadx.api;
import java.io.Closeable;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public interface ICodeCache {
public interface ICodeCache extends Closeable {
void add(String clsFullName, ICodeInfo codeInfo);
void remove(String clsFullName);
@Nullable
@NotNull
ICodeInfo get(String clsFullName);
@Nullable
String getCode(String clsFullName);
boolean contains(String clsFullName);
}
@@ -1,8 +1,7 @@
package jadx.api;
import java.util.Map;
import jadx.api.impl.SimpleCodeInfo;
import jadx.api.metadata.ICodeMetadata;
public interface ICodeInfo {
@@ -10,7 +9,7 @@ public interface ICodeInfo {
String getCodeStr();
Map<Integer, Integer> getLineMapping();
ICodeMetadata getCodeMetadata();
Map<CodePosition, Object> getAnnotations();
boolean hasMetadata();
}
@@ -2,7 +2,10 @@ package jadx.api;
import java.util.Map;
import jadx.core.dex.attributes.ILineAttributeNode;
import org.jetbrains.annotations.ApiStatus;
import jadx.api.metadata.ICodeAnnotation;
import jadx.api.metadata.ICodeNodeRef;
public interface ICodeWriter {
String NL = System.getProperty("line.separator");
@@ -38,13 +41,21 @@ public interface ICodeWriter {
void setIndent(int indent);
/**
* Return current line (only if metadata is supported)
*/
int getLine();
void attachDefinition(ILineAttributeNode obj);
/**
* Return start line position (only if metadata is supported)
*/
int getLineStartPos();
void attachAnnotation(Object obj);
void attachDefinition(ICodeNodeRef obj);
void attachLineAnnotation(Object obj);
void attachAnnotation(ICodeAnnotation obj);
void attachLineAnnotation(ICodeAnnotation obj);
void attachSourceLine(int sourceLine);
@@ -56,5 +67,6 @@ public interface ICodeWriter {
StringBuilder getRawBuf();
Map<CodePosition, Object> getRawAnnotations();
@ApiStatus.Internal
Map<Integer, ICodeAnnotation> getRawAnnotations();
}
@@ -1,6 +1,7 @@
package jadx.api;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
@@ -11,12 +12,17 @@ import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.args.DeobfuscationMapFileMode;
import jadx.api.data.ICodeData;
import jadx.api.impl.AnnotatedCodeWriter;
import jadx.api.impl.InMemoryCodeCache;
import jadx.core.utils.files.FileUtils;
public class JadxArgs {
private static final Logger LOG = LoggerFactory.getLogger(JadxArgs.class);
public static final int DEFAULT_THREADS_COUNT = Math.max(1, Runtime.getRuntime().availableProcessors() / 2);
@@ -55,6 +61,11 @@ public class JadxArgs {
*/
private Predicate<String> classFilter = null;
/**
* Save dependencies for classes accepted by {@code classFilter}
*/
private boolean includeDependencies = false;
private boolean deobfuscationOn = false;
private boolean useSourceNameAsClassAlias = false;
private boolean parseKotlinMetadata = false;
@@ -115,6 +126,19 @@ public class JadxArgs {
setOutDirRes(new File(rootDir, DEFAULT_RES_DIR));
}
public void close() {
try {
inputFiles = null;
if (codeCache != null) {
codeCache.close();
}
} catch (Exception e) {
LOG.error("Failed to close JadxArgs", e);
} finally {
codeCache = null;
}
}
public List<File> getInputFiles() {
return inputFiles;
}
@@ -261,6 +285,14 @@ public class JadxArgs {
this.skipSources = skipSources;
}
public void setIncludeDependencies(boolean includeDependencies) {
this.includeDependencies = includeDependencies;
}
public boolean isIncludeDependencies() {
return includeDependencies;
}
public Predicate<String> getClassFilter() {
return classFilter;
}
@@ -501,6 +533,21 @@ public class JadxArgs {
this.pluginOptions = pluginOptions;
}
/**
* Hash of all options that can change result code
*/
public String makeCodeArgsHash() {
String argStr = "args:" + decompilationMode + useImports + showInconsistentCode
+ inlineAnonymousClasses + inlineMethods
+ deobfuscationOn + deobfuscationMinLength + deobfuscationMaxLength
+ parseKotlinMetadata + useKotlinMethodsForVarNames
+ insertDebugLines + extractFinally
+ debugInfo + useSourceNameAsClassAlias + escapeUnicode + replaceConsts
+ respectBytecodeAccModifiers + fsCaseSensitive + renameFlags
+ commentsLevel + useDxInput + pluginOptions;
return FileUtils.md5Sum(argStr.getBytes(StandardCharsets.US_ASCII));
}
@Override
public String toString() {
return "JadxArgs{" + "inputFiles=" + inputFiles
@@ -513,12 +560,15 @@ public class JadxArgs {
+ ", useImports=" + useImports
+ ", skipResources=" + skipResources
+ ", skipSources=" + skipSources
+ ", includeDependencies=" + includeDependencies
+ ", deobfuscationOn=" + deobfuscationOn
+ ", deobfuscationMapFile=" + deobfuscationMapFile
+ ", deobfuscationMapFileMode=" + deobfuscationMapFileMode
+ ", useSourceNameAsClassAlias=" + useSourceNameAsClassAlias
+ ", parseKotlinMetadata=" + parseKotlinMetadata
+ ", useKotlinMethodsForVarNames=" + useKotlinMethodsForVarNames
+ ", insertDebugLines=" + insertDebugLines
+ ", extractFinally=" + extractFinally
+ ", deobfuscationMinLength=" + deobfuscationMinLength
+ ", deobfuscationMaxLength=" + deobfuscationMaxLength
+ ", escapeUnicode=" + escapeUnicode
@@ -13,8 +13,9 @@ public class JadxArgsValidator {
private static final Logger LOG = LoggerFactory.getLogger(JadxArgsValidator.class);
public static void validate(JadxArgs args) {
checkInputFiles(args);
public static void validate(JadxDecompiler jadx) {
JadxArgs args = jadx.getArgs();
checkInputFiles(jadx, args);
validateOutDirs(args);
if (LOG.isDebugEnabled()) {
@@ -22,9 +23,9 @@ public class JadxArgsValidator {
}
}
private static void checkInputFiles(JadxArgs args) {
private static void checkInputFiles(JadxDecompiler jadx, JadxArgs args) {
List<File> inputFiles = args.getInputFiles();
if (inputFiles.isEmpty()) {
if (inputFiles.isEmpty() && jadx.getCustomLoads().isEmpty()) {
throw new JadxArgsValidateException("Please specify input file");
}
for (File inputFile : inputFiles) {
@@ -66,19 +67,22 @@ public class JadxArgsValidator {
@NotNull
private static File makeDirFromInput(JadxArgs args) {
File outDir;
String outDirName;
File file = args.getInputFiles().get(0);
String name = file.getName();
int pos = name.lastIndexOf('.');
if (pos != -1) {
outDirName = name.substring(0, pos);
List<File> inputFiles = args.getInputFiles();
if (inputFiles.isEmpty()) {
outDirName = JadxArgs.DEFAULT_OUT_DIR;
} else {
outDirName = name + '-' + JadxArgs.DEFAULT_OUT_DIR;
File file = inputFiles.get(0);
String name = file.getName();
int pos = name.lastIndexOf('.');
if (pos != -1) {
outDirName = name.substring(0, pos);
} else {
outDirName = name + '-' + JadxArgs.DEFAULT_OUT_DIR;
}
}
LOG.info("output directory: {}", outDirName);
outDir = new File(outDirName);
return outDir;
return new File(outDirName);
}
private static void checkFile(File file) {
@@ -25,7 +25,11 @@ import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.data.annotations.VarRef;
import jadx.api.metadata.ICodeAnnotation;
import jadx.api.metadata.ICodeNodeRef;
import jadx.api.metadata.annotations.NodeDeclareRef;
import jadx.api.metadata.annotations.VarNode;
import jadx.api.metadata.annotations.VarRef;
import jadx.api.plugins.JadxPlugin;
import jadx.api.plugins.JadxPluginManager;
import jadx.api.plugins.input.JadxInputPlugin;
@@ -33,9 +37,6 @@ import jadx.api.plugins.input.data.ILoadResult;
import jadx.api.plugins.options.JadxPluginOptions;
import jadx.core.Jadx;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.InlinedAttr;
import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
@@ -96,7 +97,9 @@ public final class JadxDecompiler implements Closeable {
private final Map<MethodNode, JavaMethod> methodsMap = new ConcurrentHashMap<>();
private final Map<FieldNode, JavaField> fieldsMap = new ConcurrentHashMap<>();
private final IDecompileScheduler decompileScheduler = new DecompilerScheduler(this);
private final IDecompileScheduler decompileScheduler = new DecompilerScheduler();
private final List<ILoadResult> customLoads = new ArrayList<>();
public JadxDecompiler() {
this(new JadxArgs());
@@ -108,7 +111,7 @@ public final class JadxDecompiler implements Closeable {
public void load() {
reset();
JadxArgsValidator.validate(args);
JadxArgsValidator.validate(this);
LOG.info("loading ...");
loadPlugins(args);
loadInputFiles();
@@ -132,11 +135,20 @@ public final class JadxDecompiler implements Closeable {
loadedInputs.add(loadResult);
}
}
loadedInputs.addAll(customLoads);
if (LOG.isDebugEnabled()) {
LOG.debug("Loaded using {} inputs plugin in {} ms", loadedInputs.size(), System.currentTimeMillis() - start);
}
}
public void addCustomLoad(ILoadResult customLoad) {
customLoads.add(customLoad);
}
public List<ILoadResult> getCustomLoads() {
return customLoads;
}
private void reset() {
root = null;
classes = null;
@@ -147,8 +159,13 @@ public final class JadxDecompiler implements Closeable {
classesMap.clear();
methodsMap.clear();
fieldsMap.clear();
}
@Override
public void close() {
reset();
closeInputs();
args.close();
}
private void closeInputs() {
@@ -162,11 +179,6 @@ public final class JadxDecompiler implements Closeable {
loadedInputs.clear();
}
@Override
public void close() {
reset();
}
private void loadPlugins(JadxArgs args) {
pluginManager.providesSuggestion("java-input", args.isUseDxInput() ? "java-convert" : "java-input");
pluginManager.load();
@@ -188,6 +200,7 @@ public final class JadxDecompiler implements Closeable {
}
}
@SuppressWarnings("unused")
public void registerPlugin(JadxPlugin plugin) {
pluginManager.register(plugin);
}
@@ -229,6 +242,7 @@ public final class JadxDecompiler implements Closeable {
save(false, true);
}
@SuppressWarnings("ResultOfMethodCallIgnored")
private void save(boolean saveSources, boolean saveResources) {
ExecutorService ex = getSaveExecutor(saveSources, saveResources);
ex.shutdown();
@@ -320,10 +334,14 @@ public final class JadxDecompiler implements Closeable {
List<JavaClass> classes = getClasses();
List<JavaClass> processQueue = new ArrayList<>(classes.size());
for (JavaClass cls : classes) {
if (cls.getClassNode().contains(AFlag.DONT_GENERATE)) {
ClassNode clsNode = cls.getClassNode();
if (clsNode.contains(AFlag.DONT_GENERATE)) {
continue;
}
if (classFilter != null && !classFilter.test(cls.getFullName())) {
if (classFilter != null && !classFilter.test(clsNode.getClassInfo().getFullName())) {
if (!args.isIncludeDependencies()) {
clsNode.add(AFlag.DONT_GENERATE);
}
continue;
}
processQueue.add(cls);
@@ -338,8 +356,9 @@ public final class JadxDecompiler implements Closeable {
tasks.add(() -> {
for (JavaClass cls : decompileBatch) {
try {
ICodeInfo code = cls.getCodeInfo();
SaveCode.save(outDir, cls.getClassNode(), code);
ClassNode clsNode = cls.getClassNode();
ICodeInfo code = clsNode.getCode();
SaveCode.save(outDir, clsNode, code);
} catch (Exception e) {
LOG.error("Error saving class: {}", cls, e);
}
@@ -353,14 +372,14 @@ public final class JadxDecompiler implements Closeable {
return Collections.emptyList();
}
if (classes == null) {
List<ClassNode> classNodeList = root.getClasses(false);
List<ClassNode> classNodeList = root.getClasses();
List<JavaClass> clsList = new ArrayList<>(classNodeList.size());
classesMap.clear();
for (ClassNode classNode : classNodeList) {
if (!classNode.contains(AFlag.DONT_GENERATE)) {
JavaClass javaClass = new JavaClass(classNode, this);
clsList.add(javaClass);
classesMap.put(classNode, javaClass);
if (classNode.contains(AFlag.DONT_GENERATE)) {
continue;
}
if (!classNode.getClassInfo().isInner()) {
clsList.add(convertClassNode(classNode));
}
}
classes = Collections.unmodifiableList(clsList);
@@ -368,6 +387,10 @@ public final class JadxDecompiler implements Closeable {
return classes;
}
public List<JavaClass> getClassesWithInners() {
return Utils.collectionMap(root.getClasses(), this::convertClassNode);
}
public List<ResourceFile> getResources() {
if (resources == null) {
if (root == null) {
@@ -425,6 +448,7 @@ public final class JadxDecompiler implements Closeable {
/**
* Internal API. Not Stable!
*/
@ApiStatus.Internal
public RootNode getRoot() {
return root;
}
@@ -443,23 +467,10 @@ public final class JadxDecompiler implements Closeable {
return protoXmlParser;
}
private void loadJavaClass(JavaClass javaClass) {
javaClass.getMethods().forEach(mth -> methodsMap.put(mth.getMethodNode(), mth));
javaClass.getFields().forEach(fld -> fieldsMap.put(fld.getFieldNode(), fld));
for (JavaClass innerCls : javaClass.getInnerClasses()) {
classesMap.put(innerCls.getClassNode(), innerCls);
loadJavaClass(innerCls);
}
for (JavaClass inlinedCls : javaClass.getInlinedClasses()) {
classesMap.put(inlinedCls.getClassNode(), inlinedCls);
loadJavaClass(inlinedCls);
}
}
/**
* Get JavaClass by ClassNode without loading and decompilation
*/
@ApiStatus.Internal
JavaClass convertClassNode(ClassNode cls) {
return classesMap.compute(cls, (node, prevJavaCls) -> {
if (prevJavaCls != null && prevJavaCls.getClassNode() == cls) {
@@ -473,98 +484,20 @@ public final class JadxDecompiler implements Closeable {
});
}
@Nullable("For not generated classes")
@ApiStatus.Internal
public JavaClass getJavaClassByNode(ClassNode cls) {
JavaClass javaClass = classesMap.get(cls);
if (javaClass != null && javaClass.getClassNode() == cls) {
return javaClass;
}
// load parent class if inner
ClassNode parentClass = cls.getTopParentClass();
if (parentClass.contains(AFlag.DONT_GENERATE)) {
return null;
}
JavaClass parentJavaClass = classesMap.get(parentClass);
if (parentJavaClass == null) {
getClasses();
parentJavaClass = classesMap.get(parentClass);
}
if (parentJavaClass != null) {
loadJavaClass(parentJavaClass);
javaClass = classesMap.get(cls);
if (javaClass != null) {
return javaClass;
}
}
// class or parent classes can be excluded from generation
if (cls.hasNotGeneratedParent()) {
return null;
}
throw new JadxRuntimeException("JavaClass not found by ClassNode: " + cls);
JavaField convertFieldNode(FieldNode field) {
return fieldsMap.computeIfAbsent(field, fldNode -> {
JavaClass parentCls = convertClassNode(fldNode.getParentClass());
return new JavaField(parentCls, fldNode);
});
}
@Nullable
private JavaMethod getJavaMethodByNode(MethodNode mth) {
JavaMethod javaMethod = methodsMap.get(mth);
if (javaMethod != null && javaMethod.getMethodNode() == mth) {
return javaMethod;
}
if (mth.contains(AFlag.DONT_GENERATE)) {
return null;
}
// parent class not loaded yet
ClassNode parentClass = mth.getParentClass();
ClassNode codeCls = getCodeParentClass(parentClass);
JavaClass javaClass = getJavaClassByNode(codeCls);
if (javaClass == null) {
return null;
}
loadJavaClass(javaClass);
javaMethod = methodsMap.get(mth);
if (javaMethod != null) {
return javaMethod;
}
if (parentClass.hasNotGeneratedParent()) {
return null;
}
throw new JadxRuntimeException("JavaMethod not found by MethodNode: " + mth);
}
private ClassNode getCodeParentClass(ClassNode cls) {
ClassNode codeCls;
InlinedAttr inlinedAttr = cls.get(AType.INLINED);
if (inlinedAttr != null) {
codeCls = inlinedAttr.getInlineCls().getTopParentClass();
} else {
codeCls = cls.getTopParentClass();
}
if (codeCls == cls) {
return codeCls;
}
return getCodeParentClass(codeCls);
}
@Nullable
private JavaField getJavaFieldByNode(FieldNode fld) {
JavaField javaField = fieldsMap.get(fld);
if (javaField != null && javaField.getFieldNode() == fld) {
return javaField;
}
// parent class not loaded yet
JavaClass javaClass = getJavaClassByNode(fld.getParentClass().getTopParentClass());
if (javaClass == null) {
return null;
}
loadJavaClass(javaClass);
javaField = fieldsMap.get(fld);
if (javaField != null) {
return javaField;
}
if (fld.getParentClass().hasNotGeneratedParent()) {
return null;
}
throw new JadxRuntimeException("JavaField not found by FieldNode: " + fld);
@ApiStatus.Internal
JavaMethod convertMethodNode(MethodNode method) {
return methodsMap.computeIfAbsent(method, mthNode -> {
ClassNode parentCls = mthNode.getParentClass();
return new JavaMethod(convertClassNode(parentCls), mthNode);
});
}
@Nullable
@@ -572,7 +505,7 @@ public final class JadxDecompiler implements Closeable {
return getRoot().getClasses().stream()
.filter(cls -> cls.getClassInfo().getFullName().equals(fullName))
.findFirst()
.map(this::getJavaClassByNode)
.map(this::convertClassNode)
.orElse(null);
}
@@ -593,9 +526,9 @@ public final class JadxDecompiler implements Closeable {
.orElse(null);
if (node != null) {
if (node.contains(AFlag.DONT_GENERATE)) {
return getJavaClassByNode(node.getTopParentClass());
return convertClassNode(node.getTopParentClass());
} else {
return getJavaClassByNode(node);
return convertClassNode(node);
}
}
return null;
@@ -606,86 +539,92 @@ public final class JadxDecompiler implements Closeable {
return getRoot().getClasses().stream()
.filter(cls -> cls.getClassInfo().getAliasFullName().equals(fullName))
.findFirst()
.map(this::getJavaClassByNode)
.map(this::convertClassNode)
.orElse(null);
}
@Nullable
JavaNode convertNode(Object obj) {
if (obj instanceof VarRef) {
VarRef varRef = (VarRef) obj;
MethodNode mthNode = varRef.getMth();
JavaMethod mth = getJavaMethodByNode(mthNode);
if (mth == null) {
public JavaNode getJavaNodeByRef(ICodeNodeRef ann) {
return getJavaNodeByCodeAnnotation(null, ann);
}
@Nullable
public JavaNode getJavaNodeByCodeAnnotation(@Nullable ICodeInfo codeInfo, @Nullable ICodeAnnotation ann) {
if (ann == null) {
return null;
}
switch (ann.getAnnType()) {
case CLASS:
return convertClassNode((ClassNode) ann);
case METHOD:
return convertMethodNode((MethodNode) ann);
case FIELD:
return convertFieldNode((FieldNode) ann);
case DECLARATION:
return getJavaNodeByCodeAnnotation(codeInfo, ((NodeDeclareRef) ann).getNode());
case VAR:
return resolveVarNode((VarNode) ann);
case VAR_REF:
return resolveVarRef(codeInfo, (VarRef) ann);
case OFFSET:
// offset annotation don't have java node object
return null;
default:
throw new JadxRuntimeException("Unknown annotation type: " + ann.getAnnType() + ", class: " + ann.getClass());
}
}
@Nullable
private JavaVariable resolveVarNode(VarNode varNode) {
MethodNode mthNode = varNode.getMth();
JavaMethod mth = convertMethodNode(mthNode);
if (mth == null) {
return null;
}
return new JavaVariable(mth, varNode);
}
@Nullable
private JavaVariable resolveVarRef(ICodeInfo codeInfo, VarRef varRef) {
if (codeInfo == null) {
throw new JadxRuntimeException("Missing code info for resolve VarRef: " + varRef);
}
ICodeAnnotation varNodeAnn = codeInfo.getCodeMetadata().getAt(varRef.getRefPos());
if (varNodeAnn != null && varNodeAnn.getAnnType() == ICodeAnnotation.AnnType.DECLARATION) {
ICodeNodeRef nodeRef = ((NodeDeclareRef) varNodeAnn).getNode();
if (nodeRef.getAnnType() == ICodeAnnotation.AnnType.VAR) {
return resolveVarNode((VarNode) nodeRef);
}
return new JavaVariable(mth, varRef);
}
if (!(obj instanceof LineAttrNode)) {
return null;
}
LineAttrNode node = (LineAttrNode) obj;
if (node.contains(AFlag.DONT_GENERATE)) {
return null;
}
if (obj instanceof ClassNode) {
return convertClassNode((ClassNode) obj);
}
if (obj instanceof MethodNode) {
return getJavaMethodByNode(((MethodNode) obj));
}
if (obj instanceof FieldNode) {
return getJavaFieldByNode((FieldNode) obj);
}
throw new JadxRuntimeException("Unexpected node type: " + obj);
return null;
}
// TODO: make interface for all nodes in code annotations and add common method instead this
Object getInternalNode(JavaNode javaNode) {
if (javaNode instanceof JavaClass) {
return ((JavaClass) javaNode).getClassNode();
}
if (javaNode instanceof JavaMethod) {
return ((JavaMethod) javaNode).getMethodNode();
}
if (javaNode instanceof JavaField) {
return ((JavaField) javaNode).getFieldNode();
}
if (javaNode instanceof JavaVariable) {
return ((JavaVariable) javaNode).getVarRef();
}
throw new JadxRuntimeException("Unexpected node type: " + javaNode);
}
List<JavaNode> convertNodes(Collection<?> nodesList) {
List<JavaNode> convertNodes(Collection<? extends ICodeNodeRef> nodesList) {
return nodesList.stream()
.map(this::convertNode)
.map(this::getJavaNodeByRef)
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
@Nullable
public JavaNode getJavaNodeAtPosition(ICodeInfo codeInfo, int line, int offset) {
Map<CodePosition, Object> map = codeInfo.getAnnotations();
if (map.isEmpty()) {
return null;
}
Object obj = map.get(new CodePosition(line, offset));
if (obj == null) {
return null;
}
return convertNode(obj);
public JavaNode getJavaNodeAtPosition(ICodeInfo codeInfo, int pos) {
ICodeAnnotation ann = codeInfo.getCodeMetadata().getAt(pos);
return getJavaNodeByCodeAnnotation(codeInfo, ann);
}
@Nullable
public CodePosition getDefinitionPosition(JavaNode javaNode) {
JavaClass jCls = javaNode.getTopParentClass();
jCls.decompile();
int defLine = javaNode.getDecompiledLine();
if (defLine == 0) {
public JavaNode getClosestJavaNode(ICodeInfo codeInfo, int pos) {
ICodeAnnotation ann = codeInfo.getCodeMetadata().getClosestUp(pos);
return getJavaNodeByCodeAnnotation(codeInfo, ann);
}
@Nullable
public JavaNode getEnclosingNode(ICodeInfo codeInfo, int pos) {
ICodeNodeRef obj = codeInfo.getCodeMetadata().getNodeAt(pos);
if (obj == null) {
return null;
}
return new CodePosition(defLine, 0, javaNode.getDefPos());
return getJavaNodeByRef(obj);
}
public void reloadCodeData() {
+117 -84
View File
@@ -8,17 +8,25 @@ import java.util.List;
import java.util.Map;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.metadata.ICodeAnnotation;
import jadx.api.metadata.ICodeNodeRef;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.AnonymousClassAttr;
import jadx.core.dex.attributes.nodes.InlinedAttr;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.ListUtils;
public final class JavaClass implements JavaNode {
private static final Logger LOG = LoggerFactory.getLogger(JavaClass.class);
private final JadxDecompiler decompiler;
private final ClassNode cls;
@@ -46,24 +54,24 @@ public final class JavaClass implements JavaNode {
}
public String getCode() {
ICodeInfo code = getCodeInfo();
if (code == null) {
return "";
}
return code.getCodeStr();
return getCodeInfo().getCodeStr();
}
public ICodeInfo getCodeInfo() {
public @NotNull ICodeInfo getCodeInfo() {
ICodeInfo code = load();
if (code != null) {
return code;
}
return cls.decompile();
}
public void decompile() {
cls.decompile();
load();
}
public synchronized void reload() {
public synchronized ICodeInfo reload() {
listsLoaded = false;
cls.reloadCode();
return cls.reloadCode();
}
public void unload() {
@@ -75,10 +83,22 @@ public final class JavaClass implements JavaNode {
return cls.contains(AFlag.DONT_GENERATE);
}
public boolean isInner() {
return cls.isInner();
}
public synchronized String getSmali() {
return cls.getDisassembledCode();
}
@Override
public boolean isOwnCodeAnnotation(ICodeAnnotation ann) {
if (ann.getAnnType() == ICodeAnnotation.AnnType.CLASS) {
return ann.equals(cls);
}
return false;
}
/**
* Internal API. Not Stable!
*/
@@ -87,21 +107,34 @@ public final class JavaClass implements JavaNode {
return cls;
}
private synchronized void loadLists() {
/**
* Decompile class and loads internal lists of fields, methods, etc.
* Do nothing if already loaded.
*
* @return code info if decompilation was executed, null otherwise
*/
private synchronized @Nullable ICodeInfo load() {
if (listsLoaded) {
return;
return null;
}
listsLoaded = true;
decompile();
JadxDecompiler rootDecompiler = getRootDecompiler();
ICodeInfo code;
if (cls.getState().isProcessComplete()) {
// already decompiled -> class internals loaded
code = null;
} else {
code = cls.decompile();
}
JadxDecompiler rootDecompiler = getRootDecompiler();
int inClsCount = cls.getInnerClasses().size();
if (inClsCount != 0) {
List<JavaClass> list = new ArrayList<>(inClsCount);
for (ClassNode inner : cls.getInnerClasses()) {
if (!inner.contains(AFlag.DONT_GENERATE)) {
JavaClass javaClass = rootDecompiler.convertClassNode(inner);
javaClass.loadLists();
javaClass.load();
list.add(javaClass);
}
}
@@ -112,7 +145,7 @@ public final class JavaClass implements JavaNode {
List<JavaClass> list = new ArrayList<>(inlinedClsCount);
for (ClassNode inner : cls.getInlinedClasses()) {
JavaClass javaClass = rootDecompiler.convertClassNode(inner);
javaClass.loadLists();
javaClass.load();
list.add(javaClass);
}
this.inlinedClasses = Collections.unmodifiableList(list);
@@ -123,8 +156,7 @@ public final class JavaClass implements JavaNode {
List<JavaField> flds = new ArrayList<>(fieldsCount);
for (FieldNode f : cls.getFields()) {
if (!f.contains(AFlag.DONT_GENERATE)) {
JavaField javaField = new JavaField(this, f);
flds.add(javaField);
flds.add(rootDecompiler.convertFieldNode(f));
}
}
this.fields = Collections.unmodifiableList(flds);
@@ -135,65 +167,56 @@ public final class JavaClass implements JavaNode {
List<JavaMethod> mths = new ArrayList<>(methodsCount);
for (MethodNode m : cls.getMethods()) {
if (!m.contains(AFlag.DONT_GENERATE)) {
JavaMethod javaMethod = new JavaMethod(this, m);
mths.add(javaMethod);
mths.add(rootDecompiler.convertMethodNode(m));
}
}
mths.sort(Comparator.comparing(JavaMethod::getName));
this.methods = Collections.unmodifiableList(mths);
}
return code;
}
protected JadxDecompiler getRootDecompiler() {
JadxDecompiler getRootDecompiler() {
if (parent != null) {
return parent.getRootDecompiler();
}
return decompiler;
}
public Map<CodePosition, Object> getCodeAnnotations() {
ICodeInfo code = getCodeInfo();
if (code == null) {
return Collections.emptyMap();
}
return code.getAnnotations();
public ICodeAnnotation getAnnotationAt(int pos) {
return getCodeInfo().getCodeMetadata().getAt(pos);
}
public Object getAnnotationAt(CodePosition pos) {
return getCodeAnnotations().get(pos);
}
public Map<CodePosition, JavaNode> getUsageMap() {
Map<CodePosition, Object> map = getCodeAnnotations();
public Map<Integer, JavaNode> getUsageMap() {
Map<Integer, ICodeAnnotation> map = getCodeInfo().getCodeMetadata().getAsMap();
if (map.isEmpty() || decompiler == null) {
return Collections.emptyMap();
}
Map<CodePosition, JavaNode> resultMap = new HashMap<>(map.size());
for (Map.Entry<CodePosition, Object> entry : map.entrySet()) {
CodePosition codePosition = entry.getKey();
Object obj = entry.getValue();
JavaNode node = getRootDecompiler().convertNode(obj);
if (node != null) {
resultMap.put(codePosition, node);
Map<Integer, JavaNode> resultMap = new HashMap<>(map.size());
for (Map.Entry<Integer, ICodeAnnotation> entry : map.entrySet()) {
int codePosition = entry.getKey();
ICodeAnnotation obj = entry.getValue();
if (obj instanceof ICodeNodeRef) {
JavaNode node = getRootDecompiler().getJavaNodeByRef((ICodeNodeRef) obj);
if (node != null) {
resultMap.put(codePosition, node);
}
}
}
return resultMap;
}
public List<CodePosition> getUsageFor(JavaNode javaNode) {
Map<CodePosition, Object> map = getCodeAnnotations();
if (map.isEmpty() || decompiler == null) {
public List<Integer> getUsePlacesFor(ICodeInfo codeInfo, JavaNode javaNode) {
if (!codeInfo.hasMetadata()) {
return Collections.emptyList();
}
Object internalNode = getRootDecompiler().getInternalNode(javaNode);
List<CodePosition> result = new ArrayList<>();
for (Map.Entry<CodePosition, Object> entry : map.entrySet()) {
CodePosition codePosition = entry.getKey();
Object obj = entry.getValue();
if (internalNode.equals(obj)) {
result.add(codePosition);
List<Integer> result = new ArrayList<>();
codeInfo.getCodeMetadata().searchDown(0, (pos, ann) -> {
if (javaNode.isOwnCodeAnnotation(ann)) {
result.add(pos);
}
}
return null;
});
return result;
}
@@ -202,20 +225,8 @@ public final class JavaClass implements JavaNode {
return getRootDecompiler().convertNodes(cls.getUseIn());
}
@Nullable
@Deprecated
public JavaNode getJavaNodeAtPosition(int line, int offset) {
return getRootDecompiler().getJavaNodeAtPosition(getCodeInfo(), line, offset);
}
@Nullable
@Deprecated
public CodePosition getDefinitionPosition() {
return getRootDecompiler().getDefinitionPosition(this);
}
public Integer getSourceLine(int decompiledLine) {
return getCodeInfo().getLineMapping().get(decompiledLine);
return getCodeInfo().getCodeMetadata().getLineMapping().get(decompiledLine);
}
@Override
@@ -241,19 +252,37 @@ public final class JavaClass implements JavaNode {
return parent;
}
@Override
public JavaClass getTopParentClass() {
if (cls.contains(AType.ANONYMOUS_CLASS)) {
// moved to usage class
return getParentForAnonymousClass();
}
return parent == null ? this : parent.getTopParentClass();
public JavaClass getOriginalTopParentClass() {
return parent == null ? this : parent.getOriginalTopParentClass();
}
private JavaClass getParentForAnonymousClass() {
AnonymousClassAttr attr = cls.get(AType.ANONYMOUS_CLASS);
ClassNode topParentClass = attr.getOuterCls().getTopParentClass();
return getRootDecompiler().convertClassNode(topParentClass);
/**
* Return top parent class which contains code of this class.
* Code parent can be different from original parent after move or inline
*
* @return this if already a top class
*/
@Override
public JavaClass getTopParentClass() {
JavaClass codeParent = getCodeParent();
return codeParent == null ? this : codeParent.getTopParentClass();
}
/**
* Return parent class which contains code of this class.
* Code parent can be different for original parent after move or inline
*/
public @Nullable JavaClass getCodeParent() {
AnonymousClassAttr anonymousClsAttr = cls.get(AType.ANONYMOUS_CLASS);
if (anonymousClsAttr != null) {
// moved to usage class
return getRootDecompiler().convertClassNode(anonymousClsAttr.getOuterCls());
}
InlinedAttr inlinedAttr = cls.get(AType.INLINED);
if (inlinedAttr != null) {
return getRootDecompiler().convertClassNode(inlinedAttr.getInlineCls());
}
return parent;
}
public AccessInfo getAccessInfo() {
@@ -261,22 +290,22 @@ public final class JavaClass implements JavaNode {
}
public List<JavaClass> getInnerClasses() {
loadLists();
load();
return innerClasses;
}
public List<JavaClass> getInlinedClasses() {
loadLists();
load();
return inlinedClasses;
}
public List<JavaField> getFields() {
loadLists();
load();
return fields;
}
public List<JavaMethod> getMethods() {
loadLists();
load();
return methods;
}
@@ -286,7 +315,16 @@ public final class JavaClass implements JavaNode {
if (methodNode == null) {
return null;
}
return new JavaMethod(this, methodNode);
return getRootDecompiler().convertMethodNode(methodNode);
}
public List<JavaClass> getDependencies() {
JadxDecompiler d = getRootDecompiler();
return ListUtils.map(cls.getDependencies(), d::convertClassNode);
}
public int getTotalDepsCount() {
return cls.getTotalDepsCount();
}
@Override
@@ -294,11 +332,6 @@ public final class JavaClass implements JavaNode {
this.cls.getClassInfo().removeAlias();
}
@Override
public int getDecompiledLine() {
return cls.getDecompiledLine();
}
@Override
public int getDefPos() {
return cls.getDefPosition();
@@ -4,6 +4,7 @@ import java.util.List;
import org.jetbrains.annotations.ApiStatus;
import jadx.api.metadata.ICodeAnnotation;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.FieldNode;
@@ -50,11 +51,6 @@ public final class JavaField implements JavaNode {
return ArgType.tryToResolveClassAlias(field.root(), field.getType());
}
@Override
public int getDecompiledLine() {
return field.getDecompiledLine();
}
@Override
public int getDefPos() {
return field.getDefPosition();
@@ -70,6 +66,14 @@ public final class JavaField implements JavaNode {
this.field.getFieldInfo().removeAlias();
}
@Override
public boolean isOwnCodeAnnotation(ICodeAnnotation ann) {
if (ann.getAnnType() == ICodeAnnotation.AnnType.FIELD) {
return ann.equals(field);
}
return false;
}
/**
* Internal API. Not Stable!
*/
@@ -9,6 +9,7 @@ import org.jetbrains.annotations.ApiStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.metadata.ICodeAnnotation;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
import jadx.core.dex.info.AccessInfo;
@@ -18,6 +19,7 @@ import jadx.core.utils.Utils;
public final class JavaMethod implements JavaNode {
private static final Logger LOG = LoggerFactory.getLogger(JavaMethod.class);
private final MethodNode mth;
private final JavaClass parent;
@@ -78,7 +80,7 @@ public final class JavaMethod implements JavaNode {
JadxDecompiler decompiler = getDeclaringClass().getRootDecompiler();
return ovrdAttr.getRelatedMthNodes().stream()
.map(m -> {
JavaMethod javaMth = (JavaMethod) decompiler.convertNode(m);
JavaMethod javaMth = decompiler.convertMethodNode(m);
if (javaMth == null) {
LOG.warn("Failed convert to java method: {}", m);
}
@@ -96,11 +98,6 @@ public final class JavaMethod implements JavaNode {
return mth.getMethodInfo().isClassInit();
}
@Override
public int getDecompiledLine() {
return mth.getDecompiledLine();
}
@Override
public int getDefPos() {
return mth.getDefPosition();
@@ -111,6 +108,14 @@ public final class JavaMethod implements JavaNode {
this.mth.getMethodInfo().removeAlias();
}
@Override
public boolean isOwnCodeAnnotation(ICodeAnnotation ann) {
if (ann.getAnnType() == ICodeAnnotation.AnnType.METHOD) {
return ann.equals(mth);
}
return false;
}
/**
* Internal API. Not Stable!
*/
@@ -2,6 +2,8 @@ package jadx.api;
import java.util.List;
import jadx.api.metadata.ICodeAnnotation;
public interface JavaNode {
String getName();
@@ -12,12 +14,12 @@ public interface JavaNode {
JavaClass getTopParentClass();
int getDecompiledLine();
int getDefPos();
List<JavaNode> getUseIn();
default void removeAlias() {
}
boolean isOwnCodeAnnotation(ICodeAnnotation ann);
}
@@ -5,6 +5,8 @@ import java.util.List;
import org.jetbrains.annotations.NotNull;
import jadx.api.metadata.ICodeAnnotation;
public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
private final String name;
private final List<JavaClass> classes;
@@ -39,11 +41,6 @@ public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
return null;
}
@Override
public int getDecompiledLine() {
return 0;
}
@Override
public int getDefPos() {
return 0;
@@ -54,6 +51,11 @@ public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
return Collections.emptyList();
}
@Override
public boolean isOwnCodeAnnotation(ICodeAnnotation ann) {
return false;
}
@Override
public int compareTo(@NotNull JavaPackage o) {
return name.compareTo(o.name);
@@ -4,17 +4,20 @@ import java.util.Collections;
import java.util.List;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import jadx.api.data.annotations.VarDeclareRef;
import jadx.api.data.annotations.VarRef;
import jadx.api.metadata.ICodeAnnotation;
import jadx.api.metadata.annotations.VarNode;
import jadx.api.metadata.annotations.VarRef;
import jadx.core.dex.instructions.args.ArgType;
public class JavaVariable implements JavaNode {
private final JavaMethod mth;
private final VarRef varRef;
private final VarNode varNode;
public JavaVariable(JavaMethod mth, VarRef varRef) {
public JavaVariable(JavaMethod mth, VarNode varNode) {
this.mth = mth;
this.varRef = varRef;
this.varNode = varNode;
}
public JavaMethod getMth() {
@@ -22,26 +25,30 @@ public class JavaVariable implements JavaNode {
}
public int getReg() {
return varRef.getReg();
return varNode.getReg();
}
public int getSsa() {
return varRef.getSsa();
return varNode.getSsa();
}
@Override
public String getName() {
return varRef.getName();
public @Nullable String getName() {
return varNode.getName();
}
@ApiStatus.Internal
public VarRef getVarRef() {
return varRef;
public VarNode getVarNode() {
return varNode;
}
@Override
public String getFullName() {
return varRef.getType() + " " + varRef.getName() + " (r" + varRef.getReg() + "v" + varRef.getSsa() + ")";
return varNode.getType() + " " + varNode.getName() + " (r" + varNode.getReg() + "v" + varNode.getSsa() + ")";
}
public ArgType getType() {
return ArgType.tryToResolveClassAlias(mth.getMethodNode().root(), varNode.getType());
}
@Override
@@ -54,20 +61,9 @@ public class JavaVariable implements JavaNode {
return mth.getTopParentClass();
}
@Override
public int getDecompiledLine() {
if (varRef instanceof VarDeclareRef) {
return ((VarDeclareRef) varRef).getDecompiledLine();
}
return 0;
}
@Override
public int getDefPos() {
if (varRef instanceof VarDeclareRef) {
return ((VarDeclareRef) varRef).getDefPosition();
}
return 0;
return varNode.getDefPosition();
}
@Override
@@ -75,9 +71,18 @@ public class JavaVariable implements JavaNode {
return Collections.singletonList(mth);
}
@Override
public boolean isOwnCodeAnnotation(ICodeAnnotation ann) {
if (ann.getAnnType() == ICodeAnnotation.AnnType.VAR_REF) {
VarRef varRef = (VarRef) ann;
return varRef.getRefPos() == getDefPos();
}
return false;
}
@Override
public int hashCode() {
return varRef.hashCode();
return varNode.hashCode();
}
@Override
@@ -88,6 +93,6 @@ public class JavaVariable implements JavaNode {
if (!(o instanceof JavaVariable)) {
return false;
}
return varRef.equals(((JavaVariable) o).varRef);
return varNode.equals(((JavaVariable) o).varNode);
}
}
@@ -1,5 +0,0 @@
package jadx.api.data.annotations;
public interface ICodeRawOffset {
int getOffset();
}
@@ -1,57 +0,0 @@
package jadx.api.data.annotations;
import jadx.core.dex.attributes.ILineAttributeNode;
import jadx.core.dex.instructions.args.CodeVar;
import jadx.core.dex.nodes.MethodNode;
public class VarDeclareRef extends VarRef implements ILineAttributeNode {
public static VarDeclareRef get(MethodNode mth, CodeVar codeVar) {
VarDeclareRef ref = new VarDeclareRef(mth, codeVar);
codeVar.setCachedVarRef(ref);
return ref;
}
private int sourceLine;
private int decompiledLine;
private int defPosition;
private VarDeclareRef(MethodNode mth, CodeVar codeVar) {
super(mth, codeVar.getAnySsaVar());
}
@Override
public int getSourceLine() {
return sourceLine;
}
@Override
public void setSourceLine(int sourceLine) {
this.sourceLine = sourceLine;
}
@Override
public int getDecompiledLine() {
return decompiledLine;
}
@Override
public void setDecompiledLine(int decompiledLine) {
this.decompiledLine = decompiledLine;
}
@Override
public int getDefPosition() {
return defPosition;
}
@Override
public void setDefPosition(int pos) {
this.defPosition = pos;
}
@Override
public String toString() {
return "VarDeclareRef{r" + getReg() + 'v' + getSsa() + '}';
}
}
@@ -1,98 +0,0 @@
package jadx.api.data.annotations;
import org.jetbrains.annotations.Nullable;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.CodeVar;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.nodes.MethodNode;
public class VarRef {
@Nullable
public static VarRef get(MethodNode mth, RegisterArg reg) {
SSAVar ssaVar = reg.getSVar();
if (ssaVar == null) {
return null;
}
CodeVar codeVar = ssaVar.getCodeVar();
VarRef cachedVarRef = codeVar.getCachedVarRef();
if (cachedVarRef != null) {
if (cachedVarRef.getName() == null) {
cachedVarRef.setName(codeVar.getName());
}
return cachedVarRef;
}
VarRef newVarRef = new VarRef(mth, ssaVar);
codeVar.setCachedVarRef(newVarRef);
return newVarRef;
}
private final MethodNode mth;
private final int reg;
private final int ssa;
private final ArgType type;
private String name;
protected VarRef(MethodNode mth, SSAVar ssaVar) {
this(mth, ssaVar.getRegNum(), ssaVar.getVersion(),
ssaVar.getCodeVar().getType(), ssaVar.getCodeVar().getName());
}
private VarRef(MethodNode mth, int reg, int ssa, ArgType type, String name) {
this.mth = mth;
this.reg = reg;
this.ssa = ssa;
this.type = type;
this.name = name;
}
public MethodNode getMth() {
return mth;
}
public int getReg() {
return reg;
}
public int getSsa() {
return ssa;
}
public ArgType getType() {
return type;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof VarRef)) {
return false;
}
VarRef other = (VarRef) o;
return getReg() == other.getReg()
&& getSsa() == other.getSsa()
&& getMth().equals(other.getMth());
}
@Override
public int hashCode() {
return 31 * getReg() + getSsa();
}
@Override
public String toString() {
return "VarUseRef{r" + reg + 'v' + ssa + '}';
}
}
@@ -3,7 +3,7 @@ package jadx.api.data.impl;
import jadx.api.JavaVariable;
import jadx.api.data.CodeRefType;
import jadx.api.data.IJavaCodeRef;
import jadx.api.data.annotations.VarRef;
import jadx.api.metadata.annotations.VarNode;
public class JadxCodeRef implements IJavaCodeRef {
@@ -23,8 +23,8 @@ public class JadxCodeRef implements IJavaCodeRef {
return forVar(javaVariable.getReg(), javaVariable.getSsa());
}
public static JadxCodeRef forVar(VarRef varRef) {
return forVar(varRef.getReg(), varRef.getSsa());
public static JadxCodeRef forVar(VarNode varNode) {
return forVar(varNode.getReg(), varNode.getSsa());
}
public static JadxCodeRef forCatch(int handlerOffset) {
@@ -2,23 +2,19 @@ package jadx.api.impl;
import java.util.Map;
import jadx.api.CodePosition;
import jadx.api.ICodeInfo;
import jadx.api.metadata.ICodeAnnotation;
import jadx.api.metadata.ICodeMetadata;
import jadx.api.metadata.impl.CodeMetadataStorage;
public class AnnotatedCodeInfo implements ICodeInfo {
private final String code;
private final Map<Integer, Integer> lineMapping;
private final Map<CodePosition, Object> annotations;
private final ICodeMetadata metadata;
public AnnotatedCodeInfo(ICodeInfo codeInfo) {
this(codeInfo.getCodeStr(), codeInfo.getLineMapping(), codeInfo.getAnnotations());
}
public AnnotatedCodeInfo(String code, Map<Integer, Integer> lineMapping, Map<CodePosition, Object> annotations) {
public AnnotatedCodeInfo(String code, Map<Integer, Integer> lineMapping, Map<Integer, ICodeAnnotation> annotations) {
this.code = code;
this.lineMapping = lineMapping;
this.annotations = annotations;
this.metadata = CodeMetadataStorage.build(lineMapping, annotations);
}
@Override
@@ -27,13 +23,13 @@ public class AnnotatedCodeInfo implements ICodeInfo {
}
@Override
public Map<Integer, Integer> getLineMapping() {
return lineMapping;
public ICodeMetadata getCodeMetadata() {
return metadata;
}
@Override
public Map<CodePosition, Object> getAnnotations() {
return annotations;
public boolean hasMetadata() {
return metadata != ICodeMetadata.EMPTY;
}
@Override
@@ -5,18 +5,20 @@ import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import jadx.api.CodePosition;
import jadx.api.ICodeInfo;
import jadx.api.ICodeWriter;
import jadx.api.JadxArgs;
import jadx.core.dex.attributes.ILineAttributeNode;
import jadx.api.metadata.ICodeAnnotation;
import jadx.api.metadata.ICodeNodeRef;
import jadx.api.metadata.annotations.NodeDeclareRef;
import jadx.api.metadata.annotations.VarRef;
import jadx.core.utils.StringUtils;
public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter {
private int line = 1;
private int offset;
private Map<CodePosition, Object> annotations = Collections.emptyMap();
private Map<Integer, ICodeAnnotation> annotations = Collections.emptyMap();
private Map<Integer, Integer> lineMap = Collections.emptyMap();
public AnnotatedCodeWriter() {
@@ -59,19 +61,17 @@ public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter
@Override
public ICodeWriter add(ICodeWriter cw) {
if ((!(cw instanceof AnnotatedCodeWriter))) {
if (!cw.isMetadataSupported()) {
buf.append(cw.getCodeStr());
return this;
}
AnnotatedCodeWriter code = ((AnnotatedCodeWriter) cw);
line--;
int startLine = line;
int startPos = getLength();
for (Map.Entry<CodePosition, Object> entry : code.annotations.entrySet()) {
CodePosition codePos = entry.getKey();
int newLine = startLine + codePos.getLine();
int newPos = startPos + codePos.getPos();
attachAnnotation(entry.getValue(), new CodePosition(newLine, codePos.getOffset(), newPos));
for (Map.Entry<Integer, ICodeAnnotation> entry : code.annotations.entrySet()) {
int pos = entry.getKey();
int newPos = startPos + pos;
attachAnnotation(entry.getValue(), newPos);
}
for (Map.Entry<Integer, Integer> entry : code.lineMap.entrySet()) {
attachSourceLine(line + entry.getKey(), entry.getValue());
@@ -101,44 +101,36 @@ public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter
return line;
}
private static final class DefinitionWrapper {
private final ILineAttributeNode node;
private DefinitionWrapper(ILineAttributeNode node) {
this.node = node;
}
public ILineAttributeNode getNode() {
return node;
}
@Override
public int getLineStartPos() {
return getLength() - offset;
}
@Override
public void attachDefinition(ILineAttributeNode obj) {
public void attachDefinition(ICodeNodeRef obj) {
if (obj == null) {
return;
}
attachAnnotation(obj);
attachAnnotation(new DefinitionWrapper(obj), new CodePosition(line, offset, getLength()));
attachAnnotation(new NodeDeclareRef(obj));
}
@Override
public void attachAnnotation(Object obj) {
public void attachAnnotation(ICodeAnnotation obj) {
if (obj == null) {
return;
}
attachAnnotation(obj, new CodePosition(line, offset + 1, getLength()));
attachAnnotation(obj, getLength());
}
@Override
public void attachLineAnnotation(Object obj) {
public void attachLineAnnotation(ICodeAnnotation obj) {
if (obj == null) {
return;
}
attachAnnotation(obj, new CodePosition(line, 0, getLength() - offset));
attachAnnotation(obj, getLineStartPos());
}
private void attachAnnotation(Object obj, CodePosition pos) {
private void attachAnnotation(ICodeAnnotation obj, int pos) {
if (annotations.isEmpty()) {
annotations = new HashMap<>();
}
@@ -164,29 +156,39 @@ public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter
public ICodeInfo finish() {
removeFirstEmptyLine();
processDefinitionAnnotations();
validateAnnotations();
String code = buf.toString();
buf = null;
return new AnnotatedCodeInfo(code, lineMap, annotations);
}
@Override
public Map<CodePosition, Object> getRawAnnotations() {
public Map<Integer, ICodeAnnotation> getRawAnnotations() {
return annotations;
}
private void processDefinitionAnnotations() {
if (!annotations.isEmpty()) {
annotations.entrySet().removeIf(entry -> {
Object v = entry.getValue();
if (v instanceof DefinitionWrapper) {
ILineAttributeNode l = ((DefinitionWrapper) v).getNode();
CodePosition codePos = entry.getKey();
l.setDecompiledLine(codePos.getLine());
l.setDefPosition(codePos.getPos());
return true;
annotations.forEach((k, v) -> {
if (v instanceof NodeDeclareRef) {
NodeDeclareRef declareRef = (NodeDeclareRef) v;
declareRef.setDefPos(k);
declareRef.getNode().setDefPosition(k);
}
return false;
});
}
}
private void validateAnnotations() {
if (annotations.isEmpty()) {
return;
}
annotations.values().removeIf(v -> {
if (v.getAnnType() == ICodeAnnotation.AnnType.VAR_REF) {
VarRef varRef = (VarRef) v;
return varRef.getRefPos() == 0;
}
return false;
});
}
}
@@ -0,0 +1,49 @@
package jadx.api.impl;
import java.io.IOException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import jadx.api.ICodeCache;
import jadx.api.ICodeInfo;
public abstract class DelegateCodeCache implements ICodeCache {
protected final ICodeCache backCache;
public DelegateCodeCache(ICodeCache backCache) {
this.backCache = backCache;
}
@Override
public void add(String clsFullName, ICodeInfo codeInfo) {
backCache.add(clsFullName, codeInfo);
}
@Override
public void remove(String clsFullName) {
backCache.remove(clsFullName);
}
@Override
public @NotNull ICodeInfo get(String clsFullName) {
return backCache.get(clsFullName);
}
@Override
@Nullable
public String getCode(String clsFullName) {
return backCache.getCode(clsFullName);
}
@Override
public boolean contains(String clsFullName) {
return backCache.contains(clsFullName);
}
@Override
public void close() throws IOException {
backCache.close();
}
}
@@ -1,8 +1,10 @@
package jadx.api.impl;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import jadx.api.ICodeCache;
@@ -22,9 +24,33 @@ public class InMemoryCodeCache implements ICodeCache {
storage.remove(clsFullName);
}
@NotNull
@Override
public @Nullable ICodeInfo get(String clsFullName) {
return storage.get(clsFullName);
public ICodeInfo get(String clsFullName) {
ICodeInfo codeInfo = storage.get(clsFullName);
if (codeInfo == null) {
return ICodeInfo.EMPTY;
}
return codeInfo;
}
@Override
public @Nullable String getCode(String clsFullName) {
ICodeInfo codeInfo = storage.get(clsFullName);
if (codeInfo == null) {
return null;
}
return codeInfo.getCodeStr();
}
@Override
public boolean contains(String clsFullName) {
return storage.containsKey(clsFullName);
}
@Override
public void close() throws IOException {
storage.clear();
}
@Override
@@ -1,5 +1,6 @@
package jadx.api.impl;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import jadx.api.ICodeCache;
@@ -7,6 +8,8 @@ import jadx.api.ICodeInfo;
public class NoOpCodeCache implements ICodeCache {
public static final NoOpCodeCache INSTANCE = new NoOpCodeCache();
@Override
public void add(String clsFullName, ICodeInfo codeInfo) {
// do nothing
@@ -18,10 +21,26 @@ public class NoOpCodeCache implements ICodeCache {
}
@Override
public @Nullable ICodeInfo get(String clsFullName) {
@NotNull
public ICodeInfo get(String clsFullName) {
return ICodeInfo.EMPTY;
}
@Override
public @Nullable String getCode(String clsFullName) {
return null;
}
@Override
public boolean contains(String clsFullName) {
return false;
}
@Override
public void close() {
// do nothing
}
@Override
public String toString() {
return "NoOpCodeCache";
@@ -1,10 +1,7 @@
package jadx.api.impl;
import java.util.Collections;
import java.util.Map;
import jadx.api.CodePosition;
import jadx.api.ICodeInfo;
import jadx.api.metadata.ICodeMetadata;
public class SimpleCodeInfo implements ICodeInfo {
@@ -20,13 +17,13 @@ public class SimpleCodeInfo implements ICodeInfo {
}
@Override
public Map<Integer, Integer> getLineMapping() {
return Collections.emptyMap();
public ICodeMetadata getCodeMetadata() {
return ICodeMetadata.EMPTY;
}
@Override
public Map<CodePosition, Object> getAnnotations() {
return Collections.emptyMap();
public boolean hasMetadata() {
return false;
}
@Override
@@ -6,11 +6,11 @@ import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.CodePosition;
import jadx.api.ICodeInfo;
import jadx.api.ICodeWriter;
import jadx.api.JadxArgs;
import jadx.core.dex.attributes.ILineAttributeNode;
import jadx.api.metadata.ICodeAnnotation;
import jadx.api.metadata.ICodeNodeRef;
import jadx.core.utils.Utils;
/**
@@ -193,17 +193,22 @@ public class SimpleCodeWriter implements ICodeWriter {
}
@Override
public void attachDefinition(ILineAttributeNode obj) {
public int getLineStartPos() {
return 0;
}
@Override
public void attachDefinition(ICodeNodeRef obj) {
// no op
}
@Override
public void attachAnnotation(Object obj) {
public void attachAnnotation(ICodeAnnotation obj) {
// no op
}
@Override
public void attachLineAnnotation(Object obj) {
public void attachLineAnnotation(ICodeAnnotation obj) {
// no op
}
@@ -238,7 +243,7 @@ public class SimpleCodeWriter implements ICodeWriter {
}
@Override
public Map<CodePosition, Object> getRawAnnotations() {
public Map<Integer, ICodeAnnotation> getRawAnnotations() {
return Collections.emptyMap();
}
@@ -0,0 +1,17 @@
package jadx.api.metadata;
public interface ICodeAnnotation {
enum AnnType {
CLASS,
FIELD,
METHOD,
VAR,
VAR_REF,
DECLARATION,
OFFSET,
END // class or method body end
}
AnnType getAnnType();
}
@@ -0,0 +1,59 @@
package jadx.api.metadata;
import java.util.Map;
import java.util.function.BiFunction;
import org.jetbrains.annotations.Nullable;
import jadx.api.metadata.impl.CodeMetadataStorage;
public interface ICodeMetadata {
ICodeMetadata EMPTY = CodeMetadataStorage.empty();
@Nullable
ICodeAnnotation getAt(int position);
@Nullable
ICodeAnnotation getClosestUp(int position);
@Nullable
ICodeAnnotation searchUp(int position, ICodeAnnotation.AnnType annType);
@Nullable
ICodeAnnotation searchUp(int position, int limitPos, ICodeAnnotation.AnnType annType);
/**
* Iterate code annotations from {@code startPos} to smaller positions.
*
* @param visitor
* return not null value to stop iterations
*/
@Nullable
<T> T searchUp(int startPos, BiFunction<Integer, ICodeAnnotation, T> visitor);
/**
* Iterate code annotations from {@code startPos} to higher positions.
*
* @param visitor
* return not null value to stop iterations
*/
@Nullable
<T> T searchDown(int startPos, BiFunction<Integer, ICodeAnnotation, T> visitor);
/**
* Get current node at position (can be enclosing class or method)
*/
@Nullable
ICodeNodeRef getNodeAt(int position);
/**
* Any definition of class or method below position
*/
@Nullable
ICodeNodeRef getNodeBelow(int position);
Map<Integer, ICodeAnnotation> getAsMap();
Map<Integer, Integer> getLineMapping();
}
@@ -0,0 +1,7 @@
package jadx.api.metadata;
public interface ICodeNodeRef extends ICodeAnnotation {
int getDefPosition();
void setDefPosition(int pos);
}
@@ -1,11 +1,12 @@
package jadx.api.data.annotations;
package jadx.api.metadata.annotations;
import org.jetbrains.annotations.Nullable;
import jadx.api.ICodeWriter;
import jadx.api.metadata.ICodeAnnotation;
import jadx.core.dex.nodes.InsnNode;
public class InsnCodeOffset implements ICodeRawOffset {
public class InsnCodeOffset implements ICodeAnnotation {
public static void attach(ICodeWriter code, InsnNode insn) {
if (insn == null) {
@@ -40,11 +41,15 @@ public class InsnCodeOffset implements ICodeRawOffset {
this.offset = offset;
}
@Override
public int getOffset() {
return offset;
}
@Override
public AnnType getAnnType() {
return AnnType.OFFSET;
}
@Override
public String toString() {
return "offset=" + offset;
@@ -0,0 +1,55 @@
package jadx.api.metadata.annotations;
import java.util.Objects;
import jadx.api.metadata.ICodeAnnotation;
import jadx.api.metadata.ICodeNodeRef;
public class NodeDeclareRef implements ICodeAnnotation {
private final ICodeNodeRef node;
private int defPos;
public NodeDeclareRef(ICodeNodeRef node) {
this.node = Objects.requireNonNull(node);
}
public ICodeNodeRef getNode() {
return node;
}
public int getDefPos() {
return defPos;
}
public void setDefPos(int defPos) {
this.defPos = defPos;
}
@Override
public AnnType getAnnType() {
return AnnType.DECLARATION;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof NodeDeclareRef)) {
return false;
}
return node.equals(((NodeDeclareRef) o).node);
}
@Override
public int hashCode() {
return node.hashCode();
}
@Override
public String toString() {
return "NodeDeclareRef{" + node + '}';
}
}
@@ -0,0 +1,21 @@
package jadx.api.metadata.annotations;
import jadx.api.metadata.ICodeAnnotation;
public class NodeEnd implements ICodeAnnotation {
public static final NodeEnd VALUE = new NodeEnd();
private NodeEnd() {
}
@Override
public AnnType getAnnType() {
return AnnType.END;
}
@Override
public String toString() {
return "END";
}
}
@@ -0,0 +1,147 @@
package jadx.api.metadata.annotations;
import org.jetbrains.annotations.Nullable;
import jadx.api.metadata.ICodeAnnotation;
import jadx.api.metadata.ICodeNodeRef;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.CodeVar;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.nodes.MethodNode;
/**
* Variable info
*/
public class VarNode implements ICodeNodeRef {
@Nullable
public static VarNode get(MethodNode mth, RegisterArg reg) {
SSAVar ssaVar = reg.getSVar();
if (ssaVar == null) {
return null;
}
return get(mth, ssaVar);
}
@Nullable
public static VarNode get(MethodNode mth, CodeVar codeVar) {
return get(mth, codeVar.getAnySsaVar());
}
@Nullable
public static VarNode get(MethodNode mth, SSAVar ssaVar) {
CodeVar codeVar = ssaVar.getCodeVar();
if (codeVar.isThis()) {
return null;
}
VarNode cachedVarNode = codeVar.getCachedVarNode();
if (cachedVarNode != null) {
return cachedVarNode;
}
VarNode newVarNode = new VarNode(mth, ssaVar);
codeVar.setCachedVarNode(newVarNode);
return newVarNode;
}
@Nullable
public static ICodeAnnotation getRef(MethodNode mth, RegisterArg reg) {
VarNode varNode = get(mth, reg);
if (varNode == null) {
return null;
}
return varNode.getVarRef();
}
private final MethodNode mth;
private final int reg;
private final int ssa;
private final ArgType type;
private @Nullable String name;
private int defPos;
private final VarRef varRef;
protected VarNode(MethodNode mth, SSAVar ssaVar) {
this(mth, ssaVar.getRegNum(), ssaVar.getVersion(),
ssaVar.getCodeVar().getType(), ssaVar.getCodeVar().getName());
}
public VarNode(MethodNode mth, int reg, int ssa, ArgType type, String name) {
this.mth = mth;
this.reg = reg;
this.ssa = ssa;
this.type = type;
this.name = name;
this.varRef = VarRef.fromVarNode(this);
}
public MethodNode getMth() {
return mth;
}
public int getReg() {
return reg;
}
public int getSsa() {
return ssa;
}
public ArgType getType() {
return type;
}
@Nullable
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public VarRef getVarRef() {
return varRef;
}
@Override
public int getDefPosition() {
return defPos;
}
@Override
public void setDefPosition(int pos) {
this.defPos = pos;
}
@Override
public AnnType getAnnType() {
return AnnType.VAR;
}
@Override
public int hashCode() {
int h = 31 * getReg() + getSsa();
return 31 * h + mth.hashCode();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof VarNode)) {
return false;
}
VarNode other = (VarNode) o;
return getReg() == other.getReg()
&& getSsa() == other.getSsa()
&& getMth().equals(other.getMth());
}
@Override
public String toString() {
return "VarNode{r" + reg + 'v' + ssa + '}';
}
}
@@ -0,0 +1,68 @@
package jadx.api.metadata.annotations;
import jadx.api.metadata.ICodeAnnotation;
/**
* Variable reference by position of VarNode in code metadata.
* <br>
* Because on creation position not yet known,
* VarRef created using VarNode as a source of ref pos during serialization.
* <br>
* On metadata deserialization created with ref pos directly.
*/
public abstract class VarRef implements ICodeAnnotation {
public static VarRef fromPos(int refPos) {
if (refPos == 0) {
throw new IllegalArgumentException("Zero refPos");
}
return new FixedVarRef(refPos);
}
public static VarRef fromVarNode(VarNode varNode) {
return new RelatedVarRef(varNode);
}
public abstract int getRefPos();
@Override
public AnnType getAnnType() {
return AnnType.VAR_REF;
}
public static final class FixedVarRef extends VarRef {
private final int refPos;
public FixedVarRef(int refPos) {
this.refPos = refPos;
}
@Override
public int getRefPos() {
return refPos;
}
}
public static final class RelatedVarRef extends VarRef {
private final VarNode varNode;
public RelatedVarRef(VarNode varNode) {
this.varNode = varNode;
}
@Override
public int getRefPos() {
return varNode.getDefPosition();
}
@Override
public String toString() {
return "VarRef{" + varNode + ", name=" + varNode.getName() + ", mth=" + varNode.getMth() + '}';
}
}
@Override
public String toString() {
return "VarRef{" + getRefPos() + '}';
}
}
@@ -0,0 +1,151 @@
package jadx.api.metadata.impl;
import java.util.Collections;
import java.util.Comparator;
import java.util.Map;
import java.util.NavigableMap;
import java.util.TreeMap;
import java.util.function.BiFunction;
import org.jetbrains.annotations.Nullable;
import jadx.api.metadata.ICodeAnnotation;
import jadx.api.metadata.ICodeAnnotation.AnnType;
import jadx.api.metadata.ICodeMetadata;
import jadx.api.metadata.ICodeNodeRef;
import jadx.api.metadata.annotations.NodeDeclareRef;
import jadx.core.utils.Utils;
public class CodeMetadataStorage implements ICodeMetadata {
public static ICodeMetadata build(Map<Integer, Integer> lines, Map<Integer, ICodeAnnotation> map) {
if (map.isEmpty() && lines.isEmpty()) {
return ICodeMetadata.EMPTY;
}
Comparator<Integer> reverseCmp = Comparator.comparingInt(Integer::intValue).reversed();
NavigableMap<Integer, ICodeAnnotation> navMap = new TreeMap<>(reverseCmp);
navMap.putAll(map);
return new CodeMetadataStorage(lines, navMap);
}
public static ICodeMetadata empty() {
return new CodeMetadataStorage(Collections.emptyMap(), Collections.emptyNavigableMap());
}
private final Map<Integer, Integer> lines;
private final NavigableMap<Integer, ICodeAnnotation> navMap;
private CodeMetadataStorage(Map<Integer, Integer> lines, NavigableMap<Integer, ICodeAnnotation> navMap) {
this.lines = lines;
this.navMap = navMap;
}
@Override
public ICodeAnnotation getAt(int position) {
return navMap.get(position);
}
@Override
public @Nullable ICodeAnnotation getClosestUp(int position) {
Map.Entry<Integer, ICodeAnnotation> entryBefore = navMap.higherEntry(position);
return entryBefore != null ? entryBefore.getValue() : null;
}
@Override
public @Nullable ICodeAnnotation searchUp(int position, AnnType annType) {
for (ICodeAnnotation v : navMap.tailMap(position, true).values()) {
if (v.getAnnType() == annType) {
return v;
}
}
return null;
}
@Override
public @Nullable ICodeAnnotation searchUp(int position, int limitPos, AnnType annType) {
for (ICodeAnnotation v : navMap.subMap(position, true, limitPos, true).values()) {
if (v.getAnnType() == annType) {
return v;
}
}
return null;
}
@Override
public <T> @Nullable T searchUp(int startPos, BiFunction<Integer, ICodeAnnotation, T> visitor) {
for (Map.Entry<Integer, ICodeAnnotation> entry : navMap.tailMap(startPos, true).entrySet()) {
T value = visitor.apply(entry.getKey(), entry.getValue());
if (value != null) {
return value;
}
}
return null;
}
@Override
public <T> @Nullable T searchDown(int startPos, BiFunction<Integer, ICodeAnnotation, T> visitor) {
NavigableMap<Integer, ICodeAnnotation> map = navMap.headMap(startPos, true).descendingMap();
for (Map.Entry<Integer, ICodeAnnotation> entry : map.entrySet()) {
T value = visitor.apply(entry.getKey(), entry.getValue());
if (value != null) {
return value;
}
}
return null;
}
@Override
public ICodeNodeRef getNodeAt(int position) {
int nesting = 0;
for (ICodeAnnotation ann : navMap.tailMap(position, true).values()) {
switch (ann.getAnnType()) {
case END:
nesting++;
break;
case DECLARATION:
ICodeNodeRef node = ((NodeDeclareRef) ann).getNode();
AnnType nodeType = node.getAnnType();
if (nodeType == AnnType.CLASS || nodeType == AnnType.METHOD) {
if (nesting == 0) {
return node;
}
nesting--;
}
break;
}
}
return null;
}
@Override
public ICodeNodeRef getNodeBelow(int position) {
for (ICodeAnnotation ann : navMap.headMap(position, true).descendingMap().values()) {
if (ann.getAnnType() == AnnType.DECLARATION) {
ICodeNodeRef node = ((NodeDeclareRef) ann).getNode();
AnnType nodeType = node.getAnnType();
if (nodeType == AnnType.CLASS || nodeType == AnnType.METHOD) {
return node;
}
}
}
return null;
}
@Override
public NavigableMap<Integer, ICodeAnnotation> getAsMap() {
return navMap;
}
@Override
public Map<Integer, Integer> getLineMapping() {
return lines;
}
@Override
public String toString() {
return "CodeMetadata{\nlines=" + lines
+ "\nannotations=\n " + Utils.listToString(navMap.descendingMap().entrySet(), "\n ") + "\n}";
}
}
@@ -0,0 +1,38 @@
package jadx.api.utils;
import jadx.api.ICodeWriter;
public class CodeUtils {
public static String getLineForPos(String code, int pos) {
int start = getLineStartForPos(code, pos);
int end = getLineEndForPos(code, pos);
return code.substring(start, end);
}
public static int getLineStartForPos(String code, int pos) {
String newLine = ICodeWriter.NL;
int start = code.lastIndexOf(newLine, pos);
return start == -1 ? 0 : start + newLine.length();
}
public static int getLineEndForPos(String code, int pos) {
int end = code.indexOf(ICodeWriter.NL, pos);
return end == -1 ? code.length() : end;
}
public static int getLineNumForPos(String code, int pos) {
String newLine = ICodeWriter.NL;
int newLineLen = newLine.length();
int line = 1;
int prev = 0;
while (true) {
int next = code.indexOf(newLine, prev);
if (next >= pos) {
return line;
}
prev = next + newLineLen;
line++;
}
}
}
@@ -7,6 +7,7 @@ public class Consts {
public static final boolean DEBUG_TYPE_INFERENCE = false;
public static final boolean DEBUG_OVERLOADED_CASTS = false;
public static final boolean DEBUG_EXC_HANDLERS = false;
public static final boolean DEBUG_FINALLY = false;
public static final String CLASS_OBJECT = "java.lang.Object";
public static final String CLASS_STRING = "java.lang.String";
+10 -3
View File
@@ -238,9 +238,17 @@ public class Jadx {
private static String version;
public static String getVersion() {
if (version != null) {
return version;
if (version == null) {
version = searchJadxVersion();
}
return version;
}
public static boolean isDevVersion() {
return getVersion().equals(VERSION_DEV);
}
private static String searchJadxVersion() {
try {
ClassLoader classLoader = Jadx.class.getClassLoader();
if (classLoader != null) {
@@ -250,7 +258,6 @@ public class Jadx {
Manifest manifest = new Manifest(is);
String ver = manifest.getMainAttributes().getValue("jadx-version");
if (ver != null) {
version = ver;
return ver;
}
}
@@ -99,6 +99,10 @@ public class ProcessClass {
return generateCode(topParentClass);
}
try {
if (cls.contains(AFlag.DONT_GENERATE)) {
process(cls, false);
return ICodeInfo.EMPTY;
}
for (ClassNode depCls : cls.getDependencies()) {
process(depCls, false);
}
@@ -203,6 +203,9 @@ public class ClspGraph {
if (isNew) {
addSuperTypes(parentCls, result);
}
} else {
// parent type is unknown
result.add(parentType.getObject());
}
}
}
@@ -19,6 +19,7 @@ import jadx.api.CommentsLevel;
import jadx.api.ICodeInfo;
import jadx.api.ICodeWriter;
import jadx.api.JadxArgs;
import jadx.api.metadata.annotations.NodeEnd;
import jadx.api.plugins.input.data.AccessFlags;
import jadx.api.plugins.input.data.annotations.EncodedType;
import jadx.api.plugins.input.data.annotations.EncodedValue;
@@ -256,6 +257,7 @@ public class ClassGen {
addInnerClsAndMethods(clsCode);
clsCode.decIndent();
clsCode.startLine('}');
clsCode.attachAnnotation(NodeEnd.VALUE);
}
private void addInnerClsAndMethods(ICodeWriter clsCode) {
@@ -369,6 +371,7 @@ public class ClassGen {
mthGen.addInstructions(code);
code.decIndent();
code.startLine('}');
code.attachAnnotation(NodeEnd.VALUE);
}
}
@@ -614,21 +617,23 @@ public class ClassGen {
if (useCls.equals(extClsInfo)) {
return shortName;
}
if (extClsInfo.getPackage().equals("java.lang") && extClsInfo.getParentClass() == null) {
return shortName;
}
if (isClassInnerFor(useCls, extClsInfo)) {
return shortName;
}
if (extClsInfo.isInner()) {
return expandInnerClassName(useCls, extClsInfo);
}
if (searchCollision(cls.root(), useCls, extClsInfo)) {
if (checkInnerCollision(cls.root(), useCls, extClsInfo)
|| checkInPackageCollision(cls.root(), useCls, extClsInfo)) {
return fullName;
}
if (isBothClassesInOneTopClass(useCls, extClsInfo)) {
return shortName;
}
// don't add import for top classes from 'java.lang' package (subpackages excluded)
if (extClsInfo.getPackage().equals("java.lang") && extClsInfo.getParentClass() == null) {
return shortName;
}
// don't add import if this class from same package
if (extClsInfo.getPackage().equals(useCls.getPackage()) && !extClsInfo.isInner()) {
return shortName;
@@ -709,7 +714,7 @@ public class ClassGen {
return false;
}
private static boolean searchCollision(RootNode root, ClassInfo useCls, ClassInfo searchCls) {
private static boolean checkInnerCollision(RootNode root, @Nullable ClassInfo useCls, ClassInfo searchCls) {
if (useCls == null) {
return false;
}
@@ -726,7 +731,20 @@ public class ClassGen {
}
}
}
return searchCollision(root, useCls.getParentClass(), searchCls);
return checkInnerCollision(root, useCls.getParentClass(), searchCls);
}
/**
* Check if class with same name exists in current package
*/
private static boolean checkInPackageCollision(RootNode root, ClassInfo useCls, ClassInfo searchCls) {
String currentPkg = useCls.getAliasPkg();
if (currentPkg.equals(searchCls.getAliasPkg())) {
// search class already from current package
return false;
}
String shortName = searchCls.getAliasShortName();
return root.getClsp().isClsKnown(currentPkg + '.' + shortName);
}
private void insertRenameInfo(ICodeWriter code, ClassNode cls) {
@@ -11,9 +11,8 @@ import org.slf4j.LoggerFactory;
import jadx.api.CommentsLevel;
import jadx.api.ICodeWriter;
import jadx.api.data.annotations.InsnCodeOffset;
import jadx.api.data.annotations.VarDeclareRef;
import jadx.api.data.annotations.VarRef;
import jadx.api.metadata.annotations.InsnCodeOffset;
import jadx.api.metadata.annotations.VarNode;
import jadx.api.plugins.input.data.MethodHandleType;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
@@ -109,7 +108,7 @@ public class InsnGen {
if (arg.isRegister()) {
RegisterArg reg = (RegisterArg) arg;
if (code.isMetadataSupported()) {
code.attachAnnotation(VarRef.get(mth, reg));
code.attachAnnotation(VarNode.getRef(mth, reg));
}
code.add(mgen.getNameGen().useArg(reg));
} else if (arg.isLiteral()) {
@@ -162,10 +161,18 @@ public class InsnGen {
}
useType(code, codeVar.getType());
code.add(' ');
defVar(code, codeVar);
}
/**
* Variable definition without type, only var name
*/
private void defVar(ICodeWriter code, CodeVar codeVar) {
String varName = mgen.getNameGen().assignArg(codeVar);
if (code.isMetadataSupported()) {
code.attachDefinition(VarDeclareRef.get(mth, codeVar));
code.attachDefinition(VarNode.get(mth, codeVar));
}
code.add(mgen.getNameGen().assignArg(codeVar));
code.add(varName);
}
private String lit(LiteralArg arg) {
@@ -754,6 +761,7 @@ public class InsnGen {
ctor.add(AFlag.DONT_GENERATE);
}
}
code.attachDefinition(cls);
code.add("new ");
useClass(code, parent);
MethodNode callMth = mth.root().resolveMethod(insn.getCallMth());
@@ -800,14 +808,9 @@ public class InsnGen {
break;
case SUPER:
ClassInfo superCallCls = getClassForSuperCall(code, callMth);
if (superCallCls != null) {
useClass(code, superCallCls);
code.add('.');
}
// use 'super' instead 'this' in 0 arg
code.add("super").add('.');
k++;
callSuper(code, callMth);
k++; // use 'super' instead 'this' in 0 arg
code.add('.');
break;
case STATIC:
@@ -821,7 +824,9 @@ public class InsnGen {
}
if (callMthNode != null) {
code.attachAnnotation(callMthNode);
code.add(callMthNode.getAlias());
}
if (insn.contains(AFlag.FORCE_RAW_NAME)) {
code.add(callMth.getName());
} else {
code.add(callMth.getAlias());
}
@@ -939,7 +944,7 @@ public class InsnGen {
code.add(", ");
}
CodeVar argCodeVar = callArgs.get(i).getSVar().getCodeVar();
code.add(nameGen.assignArg(argCodeVar));
defVar(code, argCodeVar);
}
}
// force set external arg names into call method args
@@ -947,7 +952,8 @@ public class InsnGen {
int startArg = customNode.getHandleType() == MethodHandleType.INVOKE_STATIC ? 0 : 1; // skip 'this' arg
for (int i = startArg; i < extArgsCount; i++) {
RegisterArg extArg = (RegisterArg) customNode.getArg(i);
callArgs.get(i).setName(extArg.getName());
RegisterArg callRegArg = callArgs.get(i);
callRegArg.getSVar().setCodeVar(extArg.getSVar().getCodeVar());
}
code.add(" -> {");
code.incIndent();
@@ -957,34 +963,43 @@ public class InsnGen {
code.startLine('}');
}
@Nullable
private ClassInfo getClassForSuperCall(ICodeWriter code, MethodInfo callMth) {
ClassNode useCls = mth.getParentClass();
ClassInfo insnCls = useCls.getClassInfo();
ClassInfo declClass = callMth.getDeclClass();
if (insnCls.equals(declClass)) {
return null;
private void callSuper(ICodeWriter code, MethodInfo callMth) {
ClassInfo superCallCls = getClassForSuperCall(callMth);
if (superCallCls == null) {
// unknown class, add comment to keep that info
code.add("super/*").add(callMth.getDeclClass().getFullName()).add("*/");
return;
}
ClassNode topClass = useCls.getTopParentClass();
if (topClass.getClassInfo().equals(declClass)) {
return declClass;
ClassInfo curClass = mth.getParentClass().getClassInfo();
if (superCallCls.equals(curClass)) {
code.add("super");
return;
}
// search call class
ClassNode nextParent = useCls;
do {
ClassInfo nextClsInfo = nextParent.getClassInfo();
if (nextClsInfo.equals(declClass)
|| ArgType.isInstanceOf(mth.root(), nextClsInfo.getType(), declClass.getType())) {
if (nextParent == useCls) {
return null;
}
return nextClsInfo;
}
nextParent = nextParent.getParentClass();
} while (nextParent != null && nextParent != topClass);
// use custom class
useClass(code, superCallCls);
code.add(".super");
}
// search failed, just return parent class
return useCls.getParentClass().getClassInfo();
/**
* Search call class in super types of this
* and all parent classes (needed for inlined synthetic calls)
*/
@Nullable
private ClassInfo getClassForSuperCall(MethodInfo callMth) {
ArgType declClsType = callMth.getDeclClass().getType();
ClassNode parentNode = mth.getParentClass();
while (true) {
ClassInfo parentCls = parentNode.getClassInfo();
if (ArgType.isInstanceOf(root, parentCls.getType(), declClsType)) {
return parentCls;
}
ClassNode nextParent = parentNode.getParentClass();
if (nextParent == parentNode) {
// no parent, class not found
return null;
}
parentNode = nextParent;
}
}
void generateMethodArguments(ICodeWriter code, BaseInvokeNode insn, int startArgNum,
@@ -13,8 +13,8 @@ import org.slf4j.LoggerFactory;
import jadx.api.CommentsLevel;
import jadx.api.ICodeWriter;
import jadx.api.JadxArgs;
import jadx.api.data.annotations.InsnCodeOffset;
import jadx.api.data.annotations.VarDeclareRef;
import jadx.api.metadata.annotations.InsnCodeOffset;
import jadx.api.metadata.annotations.VarNode;
import jadx.api.plugins.input.data.AccessFlags;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.api.plugins.input.data.attributes.JadxAttrType;
@@ -26,6 +26,7 @@ import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.JadxError;
import jadx.core.dex.attributes.nodes.JumpInfo;
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
import jadx.core.dex.attributes.nodes.MethodReplaceAttr;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.ConstStringNode;
import jadx.core.dex.instructions.IfNode;
@@ -144,8 +145,9 @@ public class MethodGen {
} else {
classGen.useType(code, mth.getReturnType());
code.add(' ');
code.attachDefinition(mth);
code.add(mth.getAlias());
MethodNode defMth = getMethodForDefinition();
code.attachDefinition(defMth);
code.add(defMth.getAlias());
}
code.add('(');
@@ -178,6 +180,14 @@ public class MethodGen {
return true;
}
private MethodNode getMethodForDefinition() {
MethodReplaceAttr replaceAttr = mth.get(AType.METHOD_REPLACE);
if (replaceAttr != null) {
return replaceAttr.getReplaceMth();
}
return mth;
}
private void addOverrideAnnotation(ICodeWriter code, MethodNode mth) {
MethodOverrideAttr overrideAttr = mth.get(AType.METHOD_OVERRIDE);
if (overrideAttr == null) {
@@ -243,7 +253,7 @@ public class MethodGen {
code.add(' ');
String varName = nameGen.assignArg(var);
if (code.isMetadataSupported() && ssaVar != null /* for fallback mode */) {
code.attachDefinition(VarDeclareRef.get(mth, var));
code.attachDefinition(VarNode.get(mth, var));
}
code.add(varName);
@@ -7,6 +7,7 @@ import java.util.Set;
import jadx.core.Consts;
import jadx.core.deobf.NameMapper;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.MethodInfo;
@@ -173,7 +174,7 @@ public class NameGen {
private String makeNameForType(ArgType type) {
if (type.isPrimitive()) {
return makeNameForPrimitive(type);
return type.getPrimitiveType().getShortName().toLowerCase();
}
if (type.isArray()) {
return makeNameForType(type.getArrayRootElement()) + "Arr";
@@ -181,10 +182,6 @@ public class NameGen {
return makeNameForObject(type);
}
private static String makeNameForPrimitive(ArgType type) {
return type.getPrimitiveType().getShortName().toLowerCase();
}
private String makeNameForObject(ArgType type) {
if (type.isGenericType()) {
return StringUtils.escape(type.getObject().toLowerCase());
@@ -194,23 +191,32 @@ public class NameGen {
if (alias != null) {
return alias;
}
ClassInfo extClsInfo = ClassInfo.fromType(mth.root(), type);
String shortName = extClsInfo.getShortName();
String vName = fromName(shortName);
if (vName != null) {
return vName;
}
if (shortName != null) {
String lower = StringUtils.escape(shortName.toLowerCase());
if (shortName.equals(lower)) {
return lower + "Var";
}
return lower;
}
return makeNameForCheckedClass(ClassInfo.fromType(mth.root(), type));
}
return StringUtils.escape(type.toString());
}
private String makeNameForCheckedClass(ClassInfo classInfo) {
String shortName = classInfo.getAliasShortName();
String vName = fromName(shortName);
if (vName != null) {
return vName;
}
String lower = StringUtils.escape(shortName.toLowerCase());
if (shortName.equals(lower)) {
return lower + "Var";
}
return lower;
}
private String makeNameForClass(ClassInfo classInfo) {
String alias = getAliasForObject(classInfo.getFullName());
if (alias != null) {
return alias;
}
return makeNameForCheckedClass(classInfo);
}
private static String fromName(String name) {
if (name == null || name.isEmpty()) {
return null;
@@ -241,7 +247,12 @@ public class NameGen {
case CONSTRUCTOR:
ConstructorInsn co = (ConstructorInsn) insn;
return makeNameForObject(co.getClassType().getType());
MethodNode callMth = mth.root().getMethodUtils().resolveMethod(co);
if (callMth != null && callMth.contains(AFlag.ANONYMOUS_CONSTRUCTOR)) {
// don't use name of anonymous class
return null;
}
return makeNameForClass(co.getClassType());
case ARRAY_LENGTH:
return "length";
@@ -267,11 +278,11 @@ public class NameGen {
}
private String makeNameFromInvoke(MethodInfo callMth) {
String name = callMth.getName();
ArgType declType = callMth.getDeclClass().getType();
String name = callMth.getAlias();
ClassInfo declClass = callMth.getDeclClass();
if ("getInstance".equals(name)) {
// e.g. Cipher.getInstance
return makeNameForType(declType);
return makeNameForClass(declClass);
}
if (name.startsWith("get") || name.startsWith("set")) {
return fromName(name.substring(3));
@@ -280,9 +291,9 @@ public class NameGen {
return "it";
}
if ("toString".equals(name)) {
return makeNameForType(declType);
return makeNameForClass(declClass);
}
if ("forName".equals(name) && declType.equals(ArgType.CLASS)) {
if ("forName".equals(name) && declClass.getType().equals(ArgType.CLASS)) {
return OBJ_ALIAS.get(Consts.CLASS_CLASS);
}
if (name.startsWith("to")) {
@@ -10,8 +10,8 @@ import org.slf4j.LoggerFactory;
import jadx.api.CommentsLevel;
import jadx.api.ICodeWriter;
import jadx.api.data.annotations.InsnCodeOffset;
import jadx.api.data.annotations.VarDeclareRef;
import jadx.api.metadata.annotations.InsnCodeOffset;
import jadx.api.metadata.annotations.VarNode;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.core.dex.attributes.AFlag;
@@ -26,6 +26,7 @@ import jadx.core.dex.instructions.args.CodeVar;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.NamedArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.IBlock;
@@ -346,11 +347,11 @@ public class RegionGen extends InsnGen {
if (arg == null) {
code.add("unknown"); // throwing exception is too late at this point
} else if (arg instanceof RegisterArg) {
CodeVar codeVar = ((RegisterArg) arg).getSVar().getCodeVar();
SSAVar ssaVar = ((RegisterArg) arg).getSVar();
if (code.isMetadataSupported()) {
code.attachDefinition(VarDeclareRef.get(mth, codeVar));
code.attachDefinition(VarNode.get(mth, ssaVar));
}
code.add(mgen.getNameGen().assignArg(codeVar));
code.add(mgen.getNameGen().assignArg(ssaVar.getCodeVar()));
} else if (arg instanceof NamedArg) {
code.add(mgen.getNameGen().assignNamedArg((NamedArg) arg));
} else {
@@ -12,13 +12,13 @@ import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import jadx.api.CodePosition;
import jadx.api.ICodeInfo;
import jadx.api.ICodeWriter;
import jadx.api.JadxArgs;
import jadx.api.data.annotations.InsnCodeOffset;
import jadx.api.impl.AnnotatedCodeWriter;
import jadx.api.impl.SimpleCodeWriter;
import jadx.api.metadata.ICodeMetadata;
import jadx.api.metadata.annotations.InsnCodeOffset;
import jadx.core.codegen.ClassGen;
import jadx.core.codegen.MethodGen;
import jadx.core.codegen.json.cls.JsonClass;
@@ -180,24 +180,27 @@ public class JsonCodeGen {
}
String[] lines = codeStr.split(ICodeWriter.NL);
Map<Integer, Integer> lineMapping = code.getLineMapping();
Map<CodePosition, Object> annotations = code.getAnnotations();
Map<Integer, Integer> lineMapping = code.getCodeMetadata().getLineMapping();
ICodeMetadata metadata = code.getCodeMetadata();
long mthCodeOffset = mth.getMethodCodeOffset() + 16;
int linesCount = lines.length;
List<JsonCodeLine> codeLines = new ArrayList<>(linesCount);
int lineStartPos = 0;
int newLineLen = ICodeWriter.NL.length();
for (int i = 0; i < linesCount; i++) {
String codeLine = lines[i];
int line = i + 2;
JsonCodeLine jsonCodeLine = new JsonCodeLine();
jsonCodeLine.setCode(codeLine);
jsonCodeLine.setSourceLine(lineMapping.get(line));
Object obj = annotations.get(new CodePosition(line));
Object obj = metadata.getAt(lineStartPos);
if (obj instanceof InsnCodeOffset) {
long offset = ((InsnCodeOffset) obj).getOffset();
jsonCodeLine.setOffset("0x" + Long.toHexString(mthCodeOffset + offset * 2));
}
codeLines.add(jsonCodeLine);
lineStartPos += codeLine.length() + newLineLen;
}
return codeLines;
}
@@ -25,6 +25,8 @@ public enum AFlag {
HIDDEN, // instruction used inside other instruction but not listed in args
DONT_RENAME, // do not rename during deobfuscation
FORCE_RAW_NAME, // force use of raw name instead alias
ADDED_TO_REGION,
EXC_TOP_SPLITTER,
@@ -80,6 +82,7 @@ public enum AFlag {
RERUN_SSA_TRANSFORM,
METHOD_CANDIDATE_FOR_INLINE,
USE_LINES_HINTS, // source lines info in methods can be trusted
DISABLE_BLOCKS_LOCK,
@@ -5,10 +5,6 @@ public interface ILineAttributeNode {
void setSourceLine(int sourceLine);
int getDecompiledLine();
void setDecompiledLine(int line);
int getDefPosition();
void setDefPosition(int pos);
@@ -7,21 +7,11 @@ public abstract class LineAttrNode extends AttrNode implements ILineAttributeNod
private int sourceLine;
private int decompiledLine;
// the position exactly where a node declared at in decompiled java code.
/**
* Position where a node declared at in decompiled code
*/
private int defPosition;
@Override
public int getDefPosition() {
return this.defPosition;
}
@Override
public void setDefPosition(int defPosition) {
this.defPosition = defPosition;
}
@Override
public int getSourceLine() {
return sourceLine;
@@ -33,13 +23,13 @@ public abstract class LineAttrNode extends AttrNode implements ILineAttributeNod
}
@Override
public int getDecompiledLine() {
return decompiledLine;
public int getDefPosition() {
return this.defPosition;
}
@Override
public void setDecompiledLine(int decompiledLine) {
this.decompiledLine = decompiledLine;
public void setDefPosition(int defPosition) {
this.defPosition = defPosition;
}
public void addSourceLineFrom(LineAttrNode lineAttrNode) {
@@ -50,6 +40,6 @@ public abstract class LineAttrNode extends AttrNode implements ILineAttributeNod
public void copyLines(LineAttrNode lineAttrNode) {
setSourceLine(lineAttrNode.getSourceLine());
setDecompiledLine(lineAttrNode.getDecompiledLine());
setDefPosition(lineAttrNode.getDefPosition());
}
}
@@ -180,12 +180,12 @@ public final class ClassInfo implements Comparable<ClassInfo> {
return makeFullClsName(pkg, name, parentClass, false, true);
}
private String makeAliasFullName() {
public String makeAliasFullName() {
return makeFullClsName(getAliasPkg(), getAliasShortName(), parentClass, true, false);
}
private String makeAliasRawFullName() {
return makeFullClsName(pkg, name, parentClass, true, true);
public String makeAliasRawFullName() {
return makeFullClsName(getAliasPkg(), getAliasShortName(), parentClass, true, true);
}
public String getAliasFullPath() {
@@ -12,9 +12,9 @@ import org.jetbrains.annotations.TestOnly;
import jadx.core.Consts;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.typeinference.TypeCompareEnum;
import jadx.core.utils.ListUtils;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
@@ -874,28 +874,37 @@ public abstract class ArgType {
}
public static ArgType tryToResolveClassAlias(RootNode root, ArgType type) {
if (!type.isObject() || type.isGenericType()) {
if (type.isGenericType()) {
return type;
}
if (type.isArray()) {
ArgType rootType = type.getArrayRootElement();
ArgType aliasType = tryToResolveClassAlias(root, rootType);
if (aliasType == rootType) {
return type;
}
return ArgType.array(aliasType, type.getArrayDimension());
}
if (type.isObject()) {
ArgType wildcardType = type.getWildcardType();
if (wildcardType != null) {
return new WildcardType(tryToResolveClassAlias(root, wildcardType), type.getWildcardBound());
}
ClassInfo clsInfo = ClassInfo.fromName(root, type.getObject());
ArgType baseType = clsInfo.hasAlias() ? ArgType.object(clsInfo.getAliasFullName()) : type;
if (!type.isGeneric()) {
return baseType;
}
List<ArgType> genericTypes = type.getGenericTypes();
if (genericTypes != null) {
return new GenericObject(baseType.getObject(), tryToResolveClassAlias(root, genericTypes));
}
}
return type;
}
ClassNode cls = root.resolveClass(type);
if (cls == null) {
return type;
}
ClassInfo clsInfo = cls.getClassInfo();
if (!clsInfo.hasAlias()) {
return type;
}
String aliasFullName = clsInfo.getAliasFullName();
if (type.isGeneric()) {
if (type instanceof GenericObject) {
return new GenericObject(aliasFullName, type.getGenericTypes());
}
if (type instanceof WildcardType) {
return new WildcardType(ArgType.object(aliasFullName), type.getWildcardBound());
}
}
return ArgType.object(aliasFullName);
public static List<ArgType> tryToResolveClassAlias(RootNode root, List<ArgType> types) {
return ListUtils.map(types, t -> tryToResolveClassAlias(root, t));
}
@Override
@@ -4,7 +4,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import jadx.api.data.annotations.VarRef;
import jadx.api.metadata.annotations.VarNode;
public class CodeVar {
private String name;
@@ -15,7 +15,7 @@ public class CodeVar {
private boolean isThis;
private boolean isDeclared;
private VarRef cachedVarRef; // set and used at codegen stage
private VarNode cachedVarNode; // set and used at codegen stage
public static CodeVar fromMthArg(RegisterArg mthArg, boolean linkRegister) {
CodeVar var = new CodeVar();
@@ -94,12 +94,12 @@ public class CodeVar {
isDeclared = declared;
}
public VarRef getCachedVarRef() {
return cachedVarRef;
public VarNode getCachedVarNode() {
return cachedVarNode;
}
public void setCachedVarRef(VarRef cachedVarRef) {
this.cachedVarRef = cachedVarRef;
public void setCachedVarNode(VarNode varNode) {
this.cachedVarNode = varNode;
}
/**
@@ -170,6 +170,10 @@ public final class BlockNode extends AttrNode implements IBlock, Comparable<Bloc
return contains(AFlag.RETURN);
}
public boolean isEmpty() {
return instructions.isEmpty();
}
@Override
public int hashCode() {
return startOffset;
@@ -21,6 +21,7 @@ import jadx.api.ICodeCache;
import jadx.api.ICodeInfo;
import jadx.api.ICodeWriter;
import jadx.api.JadxArgs;
import jadx.api.impl.SimpleCodeInfo;
import jadx.api.plugins.input.data.IClassData;
import jadx.api.plugins.input.data.IFieldData;
import jadx.api.plugins.input.data.IMethodData;
@@ -374,12 +375,20 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
String clsRawName = getRawName();
if (searchInCache) {
ICodeInfo code = codeCache.get(clsRawName);
if (code != null && code != ICodeInfo.EMPTY) {
if (code != ICodeInfo.EMPTY) {
return code;
}
}
ICodeInfo codeInfo = root.getProcessClasses().generateCode(this);
codeCache.add(clsRawName, codeInfo);
ICodeInfo codeInfo;
try {
codeInfo = root.getProcessClasses().generateCode(this);
} catch (Throwable e) {
addError("Code generation failed", e);
codeInfo = new SimpleCodeInfo(Utils.getStackTrace(e));
}
if (codeInfo != ICodeInfo.EMPTY) {
codeCache.add(clsRawName, codeInfo);
}
return codeInfo;
}
@@ -817,6 +826,11 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
return clsData == null ? "synthetic" : clsData.getInputFileName();
}
@Override
public AnnType getAnnType() {
return AnnType.CLASS;
}
@Override
public int hashCode() {
return clsInfo.hashCode();
@@ -100,6 +100,11 @@ public class FieldNode extends NotificationAttrNode implements ICodeNode {
return parentClass.root();
}
@Override
public AnnType getAnnType() {
return AnnType.FIELD;
}
@Override
public int hashCode() {
return fieldInfo.hashCode();
@@ -1,9 +1,10 @@
package jadx.core.dex.nodes;
import jadx.api.metadata.ICodeNodeRef;
import jadx.core.dex.attributes.IAttributeNode;
import jadx.core.dex.info.AccessInfo;
public interface ICodeNode extends IDexNode, IAttributeNode, IUsageInfoNode {
public interface ICodeNode extends IDexNode, IAttributeNode, IUsageInfoNode, ICodeNodeRef {
AccessInfo getAccessFlags();
void setAccessFlags(AccessInfo newAccessFlags);
@@ -316,6 +316,10 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
return blocks;
}
public void setBasicBlocks(List<BlockNode> blocks) {
this.blocks = blocks;
}
public BlockNode getEnterBlock() {
return enterBlock;
}
@@ -461,6 +465,10 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
return regsCount;
}
public int getArgsStartReg() {
return argsStartReg;
}
public SSAVar makeNewSVar(@NotNull RegisterArg assignArg) {
int regNum = assignArg.getRegNum();
return makeNewSVar(regNum, getNextSVarVersion(regNum), assignArg);
@@ -592,6 +600,11 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
this.useIn = useIn;
}
@Override
public AnnType getAnnType() {
return AnnType.METHOD;
}
@Override
public int hashCode() {
return mthInfo.hashCode();
@@ -42,7 +42,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.ResTableParser;
import jadx.core.xmlgen.IResParser;
import jadx.core.xmlgen.ResDecoder;
import jadx.core.xmlgen.ResourceStorage;
import jadx.core.xmlgen.entry.ResourceEntry;
import jadx.core.xmlgen.entry.ValuesParser;
@@ -115,21 +116,25 @@ public class RootNode {
}
private void addDummyClass(IClassData classData, Exception exc) {
String typeStr = classData.getType();
String name = null;
try {
ClassInfo clsInfo = ClassInfo.fromName(this, typeStr);
if (clsInfo != null) {
name = clsInfo.getShortName();
String typeStr = classData.getType();
String name = null;
try {
ClassInfo clsInfo = ClassInfo.fromName(this, typeStr);
if (clsInfo != null) {
name = clsInfo.getShortName();
}
} catch (Exception e) {
LOG.error("Failed to get name for class with type {}", typeStr, e);
}
} catch (Exception e) {
LOG.error("Failed to get name for class with type {}", typeStr, e);
if (name == null || name.isEmpty()) {
name = "CLASS_" + typeStr;
}
ClassNode clsNode = ClassNode.addSyntheticClass(this, name, classData.getAccessFlags());
ErrorsCounter.error(clsNode, "Load error", exc);
} catch (Exception innerExc) {
LOG.error("Failed to load class from file: {}", classData.getInputFileName(), exc);
}
if (name == null || name.isEmpty()) {
name = "CLASS_" + typeStr;
}
ClassNode clsNode = ClassNode.addSyntheticClass(this, name, classData.getAccessFlags());
ErrorsCounter.error(clsNode, "Load error", exc);
}
private static void markDuplicatedClasses(List<ClassNode> classes) {
@@ -159,23 +164,13 @@ public class RootNode {
}
public void loadResources(List<ResourceFile> resources) {
ResourceFile arsc = null;
for (ResourceFile rf : resources) {
if (rf.getType() == ResourceType.ARSC) {
arsc = rf;
break;
}
}
ResourceFile arsc = getResourceFile(resources);
if (arsc == null) {
LOG.debug("'.arsc' file not found");
return;
}
try {
ResTableParser parser = ResourcesLoader.decodeStream(arsc, (size, is) -> {
ResTableParser tableParser = new ResTableParser(this);
tableParser.decode(is);
return tableParser;
});
IResParser parser = ResourcesLoader.decodeStream(arsc, (size, is) -> ResDecoder.decode(this, arsc, is));
if (parser != null) {
processResources(parser.getResStorage());
updateObfuscatedFiles(parser, resources);
@@ -185,6 +180,15 @@ public class RootNode {
}
}
private @Nullable ResourceFile getResourceFile(List<ResourceFile> resources) {
for (ResourceFile rf : resources) {
if (rf.getType() == ResourceType.ARSC) {
return rf;
}
}
return null;
}
public void processResources(ResourceStorage resStorage) {
constValues.setResourcesNames(resStorage.getResourcesNames());
appPackage = resStorage.getAppPackage();
@@ -205,7 +209,7 @@ public class RootNode {
}
}
private void updateObfuscatedFiles(ResTableParser parser, List<ResourceFile> resources) {
private void updateObfuscatedFiles(IResParser parser, List<ResourceFile> resources) {
if (args.isSkipResources()) {
return;
}
@@ -265,6 +269,7 @@ public class RootNode {
public void runPreDecompileStage() {
boolean debugEnabled = LOG.isDebugEnabled();
for (IDexTreeVisitor pass : preDecompilePasses) {
Utils.checkThreadInterrupt();
long start = debugEnabled ? System.currentTimeMillis() : 0;
try {
pass.init(this);
@@ -45,6 +45,15 @@ public class MethodUtils {
return root.getClsp().getMethodDetails(callMth);
}
@Nullable
public MethodNode resolveMethod(BaseInvokeNode invokeNode) {
IMethodDetails methodDetails = getMethodDetails(invokeNode);
if (methodDetails instanceof MethodNode) {
return ((MethodNode) methodDetails);
}
return null;
}
/**
* Search methods with same name and args count in class hierarchy starting from {@code startCls}
* Beware {@code startCls} can be different from {@code mthInfo.getDeclClass()}
@@ -278,6 +278,16 @@ public final class IfCondition extends AttrNode {
return list;
}
public int getSourceLine() {
for (InsnNode insn : collectInsns()) {
int line = insn.getSourceLine();
if (line != 0) {
return line;
}
}
return 0;
}
@Nullable
public InsnNode getFirstInsn() {
if (mode == Mode.COMPARE) {
@@ -14,9 +14,9 @@ import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.IContainer;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
public class ExceptionHandler {
@@ -33,9 +33,14 @@ public class ExceptionHandler {
private boolean removed = false;
public ExceptionHandler(int addr, @Nullable ClassInfo type) {
public static ExceptionHandler build(MethodNode mth, int addr, @Nullable ClassInfo type) {
ExceptionHandler eh = new ExceptionHandler(addr);
eh.addCatchType(mth, type);
return eh;
}
private ExceptionHandler(int addr) {
this.handlerOffset = addr;
addCatchType(type);
}
/**
@@ -43,7 +48,7 @@ public class ExceptionHandler {
*
* @param type - null for 'all' or 'Throwable' handler
*/
public boolean addCatchType(@Nullable ClassInfo type) {
public boolean addCatchType(MethodNode mth, @Nullable ClassInfo type) {
if (type != null) {
if (catchTypes.contains(type)) {
return false;
@@ -51,14 +56,16 @@ public class ExceptionHandler {
return catchTypes.add(type);
}
if (!this.catchTypes.isEmpty()) {
throw new JadxRuntimeException("Null type added to not empty exception handler: " + this);
mth.addDebugComment("Throwable added to exception handler: '" + catchTypeStr() + "', keep only Throwable");
catchTypes.clear();
return true;
}
return false;
}
public void addCatchTypes(Collection<ClassInfo> types) {
public void addCatchTypes(MethodNode mth, Collection<ClassInfo> types) {
for (ClassInfo type : types) {
addCatchType(type);
addCatchType(mth, type);
}
}
@@ -133,7 +133,7 @@ public class AttachTryCatchVisitor extends AbstractVisitor {
ExcHandlerAttr excHandlerAttr = insn.get(AType.EXC_HANDLER);
if (excHandlerAttr != null) {
ExceptionHandler handler = excHandlerAttr.getHandler();
if (handler.addCatchType(type)) {
if (handler.addCatchType(mth, type)) {
// exist handler updated (assume from same try block) - don't add again
return null;
}
@@ -143,7 +143,7 @@ public class AttachTryCatchVisitor extends AbstractVisitor {
} else {
insn = insertNOP(insnByOffset, handlerOffset);
}
ExceptionHandler handler = new ExceptionHandler(handlerOffset, type);
ExceptionHandler handler = ExceptionHandler.build(mth, handlerOffset, type);
mth.addExceptionHandler(handler);
insn.addAttr(new ExcHandlerAttr(handler));
return handler;
@@ -10,6 +10,7 @@ import java.util.Objects;
import jadx.api.plugins.input.data.AccessFlags;
import jadx.core.Consts;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
import jadx.core.dex.attributes.nodes.MethodReplaceAttr;
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
@@ -225,7 +226,7 @@ public class ClassModifier extends AbstractVisitor {
}
private static boolean removeBridgeMethod(ClassNode cls, MethodNode mth) {
if (cls.root().getArgs().isRenameValid()) {
if (cls.root().getArgs().isInlineMethods()) { // simple wrapper remove is same as inline
List<InsnNode> allInsns = BlockUtils.collectAllInsns(mth.getBasicBlocks());
if (allInsns.size() == 1) {
InsnNode wrappedInsn = allInsns.get(0);
@@ -235,12 +236,10 @@ public class ClassModifier extends AbstractVisitor {
wrappedInsn = ((InsnWrapArg) arg).getWrapInsn();
}
}
if (checkSyntheticWrapper(mth, wrappedInsn)) {
return true;
}
return checkSyntheticWrapper(mth, wrappedInsn);
}
}
return !isMethodUnique(cls, mth);
return false;
}
private static boolean checkSyntheticWrapper(MethodNode mth, InsnNode insn) {
@@ -283,6 +282,9 @@ public class ClassModifier extends AbstractVisitor {
if (!Objects.equals(wrappedMth.getAlias(), alias)) {
wrappedMth.getMethodInfo().setAlias(alias);
}
wrappedMth.addAttr(new MethodReplaceAttr(mth));
wrappedMth.copyAttributeFrom(mth, AType.METHOD_OVERRIDE);
wrappedMth.addDebugComment("Method merged with bridge method");
return true;
}
@@ -299,20 +301,6 @@ public class ClassModifier extends AbstractVisitor {
return false;
}
private static boolean isMethodUnique(ClassNode cls, MethodNode mth) {
MethodInfo mi = mth.getMethodInfo();
for (MethodNode otherMth : cls.getMethods()) {
if (otherMth != mth) {
MethodInfo omi = otherMth.getMethodInfo();
if (omi.getName().equals(mi.getName())
&& Objects.equals(omi.getArgumentsTypes(), mi.getArgumentsTypes())) {
return false;
}
}
}
return true;
}
/**
* Remove public empty constructors (static or default)
*/
@@ -18,6 +18,7 @@ import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.EnumClassAttr;
import jadx.core.dex.attributes.nodes.EnumClassAttr.EnumField;
import jadx.core.dex.attributes.nodes.RenameReasonAttr;
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.ClassInfo;
@@ -26,6 +27,7 @@ import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.InvokeNode;
import jadx.core.dex.instructions.InvokeType;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.InsnWrapArg;
@@ -59,6 +61,7 @@ import static jadx.core.utils.InsnUtils.getWrappedInsn;
public class EnumVisitor extends AbstractVisitor {
private MethodInfo enumValueOfMth;
private MethodInfo cloneMth;
@Override
public void init(RootNode root) {
@@ -68,6 +71,12 @@ public class EnumVisitor extends AbstractVisitor {
"valueOf",
Arrays.asList(ArgType.CLASS, ArgType.STRING),
ArgType.ENUM);
cloneMth = MethodInfo.fromDetails(root,
ClassInfo.fromType(root, ArgType.OBJECT),
"clone",
Collections.emptyList(),
ArgType.OBJECT);
}
@Override
@@ -377,6 +386,7 @@ public class EnumVisitor extends AbstractVisitor {
return enumFieldNode;
}
@SuppressWarnings("StatementWithEmptyBody")
private EnumField createEnumFieldByConstructor(ClassNode cls, FieldNode enumFieldNode, ConstructorInsn co) {
// usually constructor signature is '<init>(Ljava/lang/String;I)V'.
// sometimes for one field enum second arg can be omitted
@@ -417,13 +427,12 @@ public class EnumVisitor extends AbstractVisitor {
}
private void removeEnumMethods(ClassNode cls, ArgType clsType, FieldNode valuesField) {
String valuesMethod = "values()" + TypeGen.signature(ArgType.array(clsType));
FieldInfo valuesFieldInfo = valuesField.getFieldInfo();
String valuesMethodShortId = "values()" + TypeGen.signature(ArgType.array(clsType));
MethodNode valuesMethod = null;
// remove compiler generated methods
for (MethodNode mth : cls.getMethods()) {
MethodInfo mi = mth.getMethodInfo();
if (mi.isClassInit()) {
if (mi.isClassInit() || mth.isNoCode()) {
continue;
}
String shortId = mi.getShortId();
@@ -432,12 +441,33 @@ public class EnumVisitor extends AbstractVisitor {
mth.add(AFlag.DONT_GENERATE);
}
markArgsForSkip(mth);
} else if (shortId.equals(valuesMethod)
|| usesValuesField(mth, valuesFieldInfo)
|| simpleValueOfMth(mth, clsType)) {
} else if (mi.getShortId().equals(valuesMethodShortId)) {
if (isValuesMethod(mth, clsType)) {
valuesMethod = mth;
mth.add(AFlag.DONT_GENERATE);
} else {
// custom values method => rename to resolve conflict with enum method
mth.getMethodInfo().setAlias("valuesCustom");
mth.addAttr(new RenameReasonAttr(mth).append("to resolve conflict with enum method"));
}
} else if (isValuesMethod(mth, clsType)) {
if (!mth.getMethodInfo().getAlias().equals("values") && !mth.getUseIn().isEmpty()) {
// rename to use default values method
mth.getMethodInfo().setAlias("values");
mth.addAttr(new RenameReasonAttr(mth).append("to match enum method name"));
mth.add(AFlag.DONT_RENAME);
}
valuesMethod = mth;
mth.add(AFlag.DONT_GENERATE);
} else if (simpleValueOfMth(mth, clsType)) {
mth.add(AFlag.DONT_GENERATE);
}
}
FieldInfo valuesFieldInfo = valuesField.getFieldInfo();
for (MethodNode mth : cls.getMethods()) {
// fix access to 'values' field and 'values()' method
fixValuesAccess(mth, valuesFieldInfo, clsType, valuesMethod);
}
}
private void markArgsForSkip(MethodNode mth) {
@@ -458,6 +488,25 @@ public class EnumVisitor extends AbstractVisitor {
return false;
}
// TODO: support other method patterns ???
private boolean isValuesMethod(MethodNode mth, ArgType clsType) {
ArgType retType = mth.getReturnType();
if (!retType.isArray() || !retType.getArrayElement().equals(clsType)) {
return false;
}
InsnNode returnInsn = BlockUtils.getOnlyOneInsnFromMth(mth);
if (returnInsn == null || returnInsn.getType() != InsnType.RETURN || returnInsn.getArgsCount() != 1) {
return false;
}
InsnNode wrappedInsn = getWrappedInsn(getSingleArg(returnInsn));
IndexInsnNode castInsn = (IndexInsnNode) checkInsnType(wrappedInsn, InsnType.CHECK_CAST);
if (castInsn != null && Objects.equals(castInsn.getIndex(), ArgType.array(clsType))) {
InvokeNode invokeInsn = (InvokeNode) checkInsnType(getWrappedInsn(getSingleArg(castInsn)), InsnType.INVOKE);
return invokeInsn != null && invokeInsn.getCallMth().equals(cloneMth);
}
return false;
}
private boolean simpleValueOfMth(MethodNode mth, ArgType clsType) {
InsnNode returnInsn = InsnUtils.searchSingleReturnInsn(mth, insn -> insn.getArgsCount() == 1);
if (returnInsn == null) {
@@ -472,9 +521,41 @@ public class EnumVisitor extends AbstractVisitor {
return false;
}
private boolean usesValuesField(MethodNode mth, FieldInfo valuesFieldInfo) {
private void fixValuesAccess(MethodNode mth, FieldInfo valuesFieldInfo, ArgType clsType, @Nullable MethodNode valuesMethod) {
MethodInfo mi = mth.getMethodInfo();
if (mi.isConstructor() || mi.isClassInit() || mth.isNoCode() || mth == valuesMethod) {
return;
}
// search value field usage
Predicate<InsnNode> insnTest = insn -> Objects.equals(((IndexInsnNode) insn).getIndex(), valuesFieldInfo);
return InsnUtils.searchInsn(mth, InsnType.SGET, insnTest) != null;
InsnNode useInsn = InsnUtils.searchInsn(mth, InsnType.SGET, insnTest);
if (useInsn == null) {
return;
}
// replace 'values' field with 'values()' method
InsnUtils.replaceInsns(mth, insn -> {
if (insn.getType() == InsnType.SGET && insnTest.test(insn)) {
MethodInfo valueMth = valuesMethod == null
? getValueMthInfo(mth.root(), clsType)
: valuesMethod.getMethodInfo();
InvokeNode invokeNode = new InvokeNode(valueMth, InvokeType.STATIC, 0);
invokeNode.setResult(insn.getResult());
if (valuesMethod == null) {
// forcing enum method (can overlap and get renamed by custom method)
invokeNode.add(AFlag.FORCE_RAW_NAME);
}
mth.addDebugComment("Replace access to removed values field (" + valuesFieldInfo.getName() + ") with 'values()' method");
return invokeNode;
}
return null;
});
}
private MethodInfo getValueMthInfo(RootNode root, ArgType clsType) {
return MethodInfo.fromDetails(root,
ClassInfo.fromType(root, clsType),
"values",
Collections.emptyList(), ArgType.array(clsType));
}
private static void processEnumCls(ClassNode cls, EnumField field, ClassNode innerCls) {
@@ -362,8 +362,7 @@ public class ModVisitor extends AbstractVisitor {
private static void removeCheckCast(MethodNode mth, BlockNode block, int i, IndexInsnNode insn) {
InsnArg castArg = insn.getArg(0);
ArgType castType = (ArgType) insn.getIndex();
if (!ArgType.isCastNeeded(mth.root(), castArg.getType(), castType)
|| isCastDuplicate(insn)) {
if (!ArgType.isCastNeeded(mth.root(), castArg.getType(), castType)) {
RegisterArg result = insn.getResult();
result.setType(castArg.getType());
@@ -371,10 +370,19 @@ public class ModVisitor extends AbstractVisitor {
move.setResult(result);
move.addArg(castArg);
replaceInsn(mth, block, i, move);
return;
}
InsnNode prevCast = isCastDuplicate(insn);
if (prevCast != null) {
// replace previous cast with move
InsnNode move = new InsnNode(InsnType.MOVE, 1);
move.setResult(prevCast.getResult());
move.addArg(prevCast.getArg(0));
replaceInsn(mth, block, prevCast, move);
}
}
private static boolean isCastDuplicate(IndexInsnNode castInsn) {
private static @Nullable InsnNode isCastDuplicate(IndexInsnNode castInsn) {
InsnArg arg = castInsn.getArg(0);
if (arg.isRegister()) {
SSAVar sVar = ((RegisterArg) arg).getSVar();
@@ -382,11 +390,13 @@ public class ModVisitor extends AbstractVisitor {
InsnNode assignInsn = sVar.getAssign().getParentInsn();
if (assignInsn != null && assignInsn.getType() == InsnType.CHECK_CAST) {
ArgType assignCastType = (ArgType) ((IndexInsnNode) assignInsn).getIndex();
return assignCastType.equals(castInsn.getIndex());
if (assignCastType.equals(castInsn.getIndex())) {
return assignInsn;
}
}
}
}
return false;
return null;
}
/**
@@ -61,7 +61,9 @@ public class MoveInlineVisitor extends AbstractVisitor {
}
SSAVar ssaVar = resultArg.getSVar();
if (ssaVar.isUsedInPhi()) {
return deleteMove(mth, move);
return false;
// TODO: review conditions of 'up' move inline (test TestMoveInline)
// return deleteMove(mth, move);
}
RegDebugInfoAttr debugInfo = moveArg.get(AType.REG_DEBUG_INFO);
for (RegisterArg useArg : ssaVar.getUseList()) {
@@ -50,7 +50,7 @@ public class BlockExceptionHandler {
return false;
}
BlockProcessor.updateCleanSuccessors(mth);
BlockProcessor.computeDominanceFrontier(mth);
DominatorTree.computeDominanceFrontier(mth);
processCatchAttr(mth);
initExcHandlers(mth);
@@ -549,7 +549,7 @@ public class BlockExceptionHandler {
if (handler == resultHandler) {
return false;
}
resultHandler.addCatchTypes(handler.getCatchTypes());
resultHandler.addCatchTypes(mth, handler.getCatchTypes());
handler.markForRemove();
return true;
});
@@ -1,9 +1,6 @@
package jadx.core.dex.visitors.blocks;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections;
import java.util.Deque;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
@@ -31,7 +28,6 @@ import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
import static jadx.core.dex.visitors.blocks.BlockSplitter.connect;
import static jadx.core.utils.EmptyBitSet.EMPTY;
public class BlockProcessor extends AbstractVisitor {
private static final Logger LOG = LoggerFactory.getLogger(BlockProcessor.class);
@@ -50,29 +46,23 @@ public class BlockProcessor extends AbstractVisitor {
computeDominators(mth);
if (independentBlockTreeMod(mth)) {
checkForUnreachableBlocks(mth);
clearBlocksState(mth);
computeDominators(mth);
}
if (FixMultiEntryLoops.process(mth)) {
clearBlocksState(mth);
computeDominators(mth);
}
updateCleanSuccessors(mth);
int i = 0;
while (modifyBlocksTree(mth)) {
// revert calculations
clearBlocksState(mth);
// recalculate dominators tree
computeDominators(mth);
if (i++ > 100) {
throw new JadxRuntimeException("CFG modification limit reached, blocks count: " + mth.getBasicBlocks().size());
}
}
checkForUnreachableBlocks(mth);
computeDominanceFrontier(mth);
DominatorTree.computeDominanceFrontier(mth);
registerLoops(mth);
processNestedLoops(mth);
@@ -209,139 +199,9 @@ public class BlockProcessor extends AbstractVisitor {
}
private static void computeDominators(MethodNode mth) {
List<BlockNode> basicBlocks = mth.getBasicBlocks();
int nBlocks = basicBlocks.size();
for (int i = 0; i < nBlocks; i++) {
BlockNode block = basicBlocks.get(i);
block.setId(i);
block.setDoms(new BitSet(nBlocks));
block.getDoms().set(0, nBlocks);
}
BlockNode entryBlock = mth.getEnterBlock();
calcDominators(basicBlocks, entryBlock);
clearBlocksState(mth);
DominatorTree.compute(mth);
markLoops(mth);
// clear self dominance
basicBlocks.forEach(block -> {
block.getDoms().clear(block.getId());
if (block.getDoms().isEmpty()) {
block.setDoms(EMPTY);
}
});
calcImmediateDominators(mth, basicBlocks, entryBlock);
}
private static void calcDominators(List<BlockNode> basicBlocks, BlockNode entryBlock) {
entryBlock.getDoms().clear();
entryBlock.getDoms().set(entryBlock.getId());
BitSet domSet = new BitSet(basicBlocks.size());
boolean changed;
do {
changed = false;
for (BlockNode block : basicBlocks) {
if (block == entryBlock) {
continue;
}
BitSet d = block.getDoms();
if (!changed) {
domSet.clear();
domSet.or(d);
}
for (BlockNode pred : block.getPredecessors()) {
d.and(pred.getDoms());
}
d.set(block.getId());
if (!changed && !d.equals(domSet)) {
changed = true;
}
}
} while (changed);
}
private static void calcImmediateDominators(MethodNode mth, List<BlockNode> basicBlocks, BlockNode entryBlock) {
for (BlockNode block : basicBlocks) {
if (block == entryBlock) {
continue;
}
BlockNode idom;
List<BlockNode> preds = block.getPredecessors();
if (preds.size() == 1) {
idom = preds.get(0);
} else {
BitSet bs = new BitSet(block.getDoms().length());
bs.or(block.getDoms());
for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i + 1)) {
BlockNode dom = basicBlocks.get(i);
bs.andNot(dom.getDoms());
}
if (bs.cardinality() != 1) {
throw new JadxRuntimeException("Can't find immediate dominator for block " + block
+ " in " + bs + " preds:" + preds);
}
idom = basicBlocks.get(bs.nextSetBit(0));
}
block.setIDom(idom);
idom.addDominatesOn(block);
}
}
static void computeDominanceFrontier(MethodNode mth) {
mth.getExitBlock().setDomFrontier(EMPTY);
List<BlockNode> domSortedBlocks = new ArrayList<>(mth.getBasicBlocks().size());
Deque<BlockNode> stack = new LinkedList<>();
stack.push(mth.getEnterBlock());
while (!stack.isEmpty()) {
BlockNode node = stack.pop();
for (BlockNode dominated : node.getDominatesOn()) {
stack.push(dominated);
}
domSortedBlocks.add(node);
}
Collections.reverse(domSortedBlocks);
for (BlockNode block : domSortedBlocks) {
try {
computeBlockDF(mth, block);
} catch (Exception e) {
throw new JadxRuntimeException("Failed compute block dominance frontier", e);
}
}
}
private static void computeBlockDF(MethodNode mth, BlockNode block) {
if (block.getDomFrontier() != null) {
return;
}
List<BlockNode> blocks = mth.getBasicBlocks();
BitSet domFrontier = null;
for (BlockNode s : block.getSuccessors()) {
if (s.getIDom() != block) {
if (domFrontier == null) {
domFrontier = new BitSet(blocks.size());
}
domFrontier.set(s.getId());
}
}
for (BlockNode c : block.getDominatesOn()) {
BitSet frontier = c.getDomFrontier();
if (frontier == null) {
throw new JadxRuntimeException("Dominance frontier not calculated for dominated block: " + c + ", from: " + block);
}
for (int p = frontier.nextSetBit(0); p >= 0; p = frontier.nextSetBit(p + 1)) {
if (blocks.get(p).getIDom() != block) {
if (domFrontier == null) {
domFrontier = new BitSet(blocks.size());
}
domFrontier.set(p);
}
}
}
if (domFrontier == null || domFrontier.isEmpty()) {
domFrontier = EMPTY;
}
block.setDomFrontier(domFrontier);
}
private static void markLoops(MethodNode mth) {
@@ -349,7 +209,7 @@ public class BlockProcessor extends AbstractVisitor {
// Every successor that dominates its predecessor is a header of a loop,
// block -> successor is a back edge.
block.getSuccessors().forEach(successor -> {
if (block.getDoms().get(successor.getId())) {
if (block.getDoms().get(successor.getId()) || block == successor) {
successor.add(AFlag.LOOP_START);
block.add(AFlag.LOOP_END);
@@ -0,0 +1,175 @@
package jadx.core.dex.visitors.blocks;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.BlockUtils;
import jadx.core.utils.EmptyBitSet;
import jadx.core.utils.exceptions.JadxRuntimeException;
/**
* Build dominator tree based on the algorithm described in paper:
* Cooper, Keith D.; Harvey, Timothy J; Kennedy, Ken (2001).
* "A Simple, Fast Dominance Algorithm"
* http://www.hipersoft.rice.edu/grads/publications/dom14.pdf
*/
@SuppressWarnings("JavadocLinkAsPlainText")
public class DominatorTree {
public static void compute(MethodNode mth) {
List<BlockNode> sorted = sortBlocks(mth);
BlockNode[] doms = build(sorted);
apply(sorted, doms);
mth.setBasicBlocks(sorted);
}
private static List<BlockNode> sortBlocks(MethodNode mth) {
int blocksCount = mth.getBasicBlocks().size();
BitSet reachSet = new BitSet(blocksCount);
List<BlockNode> sorted = new ArrayList<>(blocksCount);
BlockUtils.dfsVisit(mth, b -> {
sorted.add(b);
reachSet.set(b.getId());
});
if (reachSet.cardinality() != blocksCount) {
throw new JadxRuntimeException("Found unreachable blocks");
}
for (int i = 0; i < blocksCount; i++) {
sorted.get(i).setId(i);
}
return sorted;
}
@NotNull
private static BlockNode[] build(List<BlockNode> sorted) {
int blocksCount = sorted.size();
BlockNode[] doms = new BlockNode[blocksCount];
doms[0] = sorted.get(0);
boolean changed = true;
while (changed) {
changed = false;
for (int blockId = 1; blockId < blocksCount; blockId++) {
BlockNode b = sorted.get(blockId);
List<BlockNode> preds = b.getPredecessors();
int pickedPred = -1;
BlockNode newIDom = null;
for (BlockNode pred : preds) {
int id = pred.getId();
if (doms[id] != null) {
newIDom = pred;
pickedPred = id;
break;
}
}
if (newIDom == null) {
throw new JadxRuntimeException("No predecessors for block: " + b);
}
for (BlockNode predBlock : preds) {
int predId = predBlock.getId();
if (predId == pickedPred) {
continue;
}
if (doms[predId] != null) {
newIDom = intersect(sorted, doms, predBlock, newIDom);
}
}
if (doms[blockId] != newIDom) {
doms[blockId] = newIDom;
changed = true;
}
}
}
return doms;
}
private static BlockNode intersect(List<BlockNode> sorted, BlockNode[] doms, BlockNode b1, BlockNode b2) {
int f1 = b1.getId();
int f2 = b2.getId();
while (f1 != f2) {
while (f1 > f2) {
f1 = doms[f1].getId();
}
while (f2 > f1) {
f2 = doms[f2].getId();
}
}
return sorted.get(f1);
}
private static void apply(List<BlockNode> sorted, BlockNode[] doms) {
BlockNode enterBlock = sorted.get(0);
enterBlock.setDoms(EmptyBitSet.EMPTY);
enterBlock.setIDom(null);
int blocksCount = sorted.size();
for (int i = 1; i < blocksCount; i++) {
BlockNode block = sorted.get(i);
BlockNode idom = doms[i];
block.setIDom(idom);
idom.addDominatesOn(block);
BitSet domBS = collectDoms(doms, idom);
domBS.clear(i);
block.setDoms(domBS);
}
}
private static BitSet collectDoms(BlockNode[] doms, BlockNode idom) {
BitSet domBS = new BitSet(doms.length);
BlockNode nextIDom = idom;
while (true) {
int id = nextIDom.getId();
if (domBS.get(id)) {
break;
}
domBS.set(id);
BitSet curDoms = nextIDom.getDoms();
if (curDoms != null) {
// use already collected set
domBS.or(curDoms);
break;
}
nextIDom = doms[id];
}
return domBS;
}
public static void computeDominanceFrontier(MethodNode mth) {
List<BlockNode> blocks = mth.getBasicBlocks();
for (BlockNode block : blocks) {
block.setDomFrontier(null);
}
int blocksCount = blocks.size();
for (BlockNode block : blocks) {
List<BlockNode> preds = block.getPredecessors();
if (preds.size() >= 2) {
BlockNode idom = block.getIDom();
for (BlockNode pred : preds) {
BlockNode runner = pred;
while (runner != idom) {
addToDF(runner, block, blocksCount);
runner = runner.getIDom();
}
}
}
}
for (BlockNode block : blocks) {
BitSet df = block.getDomFrontier();
if (df == null || df.isEmpty()) {
block.setDomFrontier(EmptyBitSet.EMPTY);
}
}
}
private static void addToDF(BlockNode block, BlockNode dfBlock, int blocksCount) {
BitSet df = block.getDomFrontier();
if (df == null) {
df = new BitSet(blocksCount);
block.setDomFrontier(df);
}
df.set(dfBlock.getId());
}
}
@@ -1,12 +1,15 @@
package jadx.core.dex.visitors.debuginfo;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import jadx.api.plugins.input.data.IDebugInfo;
import jadx.api.plugins.input.data.ILocalVar;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.nodes.LocalVarsDebugInfoAttr;
import jadx.core.dex.attributes.nodes.RegDebugInfoAttr;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.RegisterArg;
@@ -17,6 +20,7 @@ import jadx.core.dex.visitors.AbstractVisitor;
import jadx.core.dex.visitors.JadxVisitor;
import jadx.core.dex.visitors.blocks.BlockSplitter;
import jadx.core.dex.visitors.ssa.SSATransform;
import jadx.core.utils.ListUtils;
import jadx.core.utils.exceptions.JadxException;
@JadxVisitor(
@@ -52,17 +56,30 @@ public class DebugInfoAttachVisitor extends AbstractVisitor {
if (lineMapping.isEmpty()) {
return;
}
Map<Integer, Integer> linesStat = new HashMap<>(); // count repeating lines
for (Map.Entry<Integer, Integer> entry : lineMapping.entrySet()) {
try {
Integer offset = entry.getKey();
InsnNode insn = insnArr[offset];
if (insn != null) {
insn.setSourceLine(entry.getValue());
int line = entry.getValue();
insn.setSourceLine(line);
if (insn.getType() != InsnType.NOP) {
linesStat.merge(line, 1, (v, one) -> v + 1);
}
}
} catch (Exception e) {
mth.addWarnComment("Error attach source line", e);
}
}
// 3 here is allowed maximum for lines repeat,
// can occur in indexed 'for' loops (3 instructions with same line)
List<Map.Entry<Integer, Integer>> repeatingLines = ListUtils.filter(linesStat.entrySet(), p -> p.getValue() > 3);
if (repeatingLines.isEmpty()) {
mth.add(AFlag.USE_LINES_HINTS);
} else {
mth.addDebugComment("Don't trust debug lines info. Repeating lines: " + repeatingLines);
}
}
private void attachDebugInfo(MethodNode mth, List<ILocalVar> localVars, InsnNode[] insnArr) {
@@ -6,9 +6,12 @@ import java.util.List;
import java.util.Set;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.trycatch.ExceptionHandler;
import jadx.core.utils.Utils;
public class FinallyExtractInfo {
private final MethodNode mth;
private final ExceptionHandler finallyHandler;
private final List<BlockNode> allHandlerBlocks;
private final List<InsnsSlice> duplicateSlices = new ArrayList<>();
@@ -16,12 +19,17 @@ public class FinallyExtractInfo {
private final InsnsSlice finallyInsnsSlice = new InsnsSlice();
private final BlockNode startBlock;
public FinallyExtractInfo(ExceptionHandler finallyHandler, BlockNode startBlock, List<BlockNode> allHandlerBlocks) {
public FinallyExtractInfo(MethodNode mth, ExceptionHandler finallyHandler, BlockNode startBlock, List<BlockNode> allHandlerBlocks) {
this.mth = mth;
this.finallyHandler = finallyHandler;
this.startBlock = startBlock;
this.allHandlerBlocks = allHandlerBlocks;
}
public MethodNode getMth() {
return mth;
}
public ExceptionHandler getFinallyHandler() {
return finallyHandler;
}
@@ -45,4 +53,12 @@ public class FinallyExtractInfo {
public BlockNode getStartBlock() {
return startBlock;
}
@Override
public String toString() {
return "FinallyExtractInfo{"
+ "\n finally:\n " + finallyInsnsSlice
+ "\n dups:\n " + Utils.listToString(duplicateSlices, "\n ")
+ "\n}";
}
}
@@ -9,6 +9,7 @@ import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.Consts;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.instructions.InsnType;
@@ -38,6 +39,7 @@ import jadx.core.utils.Utils;
)
public class MarkFinallyVisitor extends AbstractVisitor {
private static final Logger LOG = LoggerFactory.getLogger(MarkFinallyVisitor.class);
// private static final Logger LOG = LoggerFactory.getLogger(MarkFinallyVisitor.class);
@Override
public void visit(MethodNode mth) {
@@ -60,7 +62,7 @@ public class MarkFinallyVisitor extends AbstractVisitor {
}
}
} catch (Exception e) {
LOG.warn("Undo finally extract visitor, mth: {}", mth, e);
mth.addWarnComment("Undo finally extract visitor", e);
undoFinallyVisitor(mth);
}
}
@@ -100,20 +102,23 @@ public class MarkFinallyVisitor extends AbstractVisitor {
List<BlockNode> handlerBlocks =
new ArrayList<>(BlockUtils.collectBlocksDominatedByWithExcHandlers(mth, handlerBlock, handlerBlock));
handlerBlocks.remove(handlerBlock); // exclude block with 'move-exception'
handlerBlocks.removeIf(b -> BlockUtils.checkLastInsnType(b, InsnType.THROW));
cutPathEnds(mth, handlerBlocks);
if (handlerBlocks.isEmpty() || BlockUtils.isAllBlocksEmpty(handlerBlocks)) {
// remove empty catch
allHandler.getTryBlock().removeHandler(allHandler);
return true;
}
BlockNode startBlock = Utils.getOne(handlerBlock.getCleanSuccessors());
FinallyExtractInfo extractInfo = new FinallyExtractInfo(allHandler, startBlock, handlerBlocks);
FinallyExtractInfo extractInfo = new FinallyExtractInfo(mth, allHandler, startBlock, handlerBlocks);
if (Consts.DEBUG_FINALLY) {
LOG.debug("Finally info: handler=({}), start={}, blocks={}", allHandler, startBlock, handlerBlocks);
}
boolean hasInnerBlocks = !tryBlock.getInnerTryBlocks().isEmpty();
List<ExceptionHandler> handlers;
if (hasInnerBlocks) {
// collect handlers from this and all inner blocks (intentionally not using recursive collect for
// now)
// collect handlers from this and all inner blocks
// (intentionally not using recursive collect for now)
handlers = new ArrayList<>(tryBlock.getHandlers());
for (TryCatchBlockAttr innerTryBlock : tryBlock.getInnerTryBlocks()) {
handlers.addAll(innerTryBlock.getHandlers());
@@ -137,10 +142,12 @@ public class MarkFinallyVisitor extends AbstractVisitor {
}
}
}
if (Consts.DEBUG_FINALLY) {
LOG.debug("Handlers slices:\n{}", extractInfo);
}
boolean mergeInnerTryBlocks;
int duplicatesCount = extractInfo.getDuplicateSlices().size();
boolean fullTryBlock = duplicatesCount == (handlers.size() - 1);
if (fullTryBlock) {
if (duplicatesCount == (handlers.size() - 1)) {
// all collected handlers have duplicate block
mergeInnerTryBlocks = hasInnerBlocks;
} else {
@@ -170,15 +177,24 @@ public class MarkFinallyVisitor extends AbstractVisitor {
if (upPath.size() < handlerBlocks.size()) {
continue;
}
if (Consts.DEBUG_FINALLY) {
LOG.debug("Checking dup path starts: {} from {}", upPath, pred);
}
for (BlockNode block : upPath) {
if (searchDuplicateInsns(block, extractInfo)) {
found = true;
if (Consts.DEBUG_FINALLY) {
LOG.debug("Found dup in: {} from {}", block, pred);
}
break;
} else {
extractInfo.getFinallyInsnsSlice().resetIncomplete();
}
}
}
if (Consts.DEBUG_FINALLY) {
LOG.debug("Result slices:\n{}", extractInfo);
}
if (!found) {
return false;
}
@@ -204,6 +220,28 @@ public class MarkFinallyVisitor extends AbstractVisitor {
return true;
}
private static void cutPathEnds(MethodNode mth, List<BlockNode> handlerBlocks) {
List<BlockNode> throwBlocks = ListUtils.filter(handlerBlocks,
b -> BlockUtils.checkLastInsnType(b, InsnType.THROW));
if (throwBlocks.size() != 1) {
mth.addDebugComment("Finally have unexpected throw blocks count: " + throwBlocks.size() + ", expect 1");
return;
}
BlockNode throwBlock = throwBlocks.get(0);
handlerBlocks.remove(throwBlock);
removeEmptyUpPath(handlerBlocks, throwBlock);
}
private static void removeEmptyUpPath(List<BlockNode> handlerBlocks, BlockNode startBlock) {
for (BlockNode pred : startBlock.getPredecessors()) {
if (pred.isEmpty()) {
if (handlerBlocks.remove(pred) && !BlockUtils.isBackEdge(pred, startBlock)) {
removeEmptyUpPath(handlerBlocks, pred);
}
}
}
}
private static List<BlockNode> getPathStarts(MethodNode mth, BlockNode bottom, BlockNode bottomFinallyBlock) {
Stream<BlockNode> preds = bottom.getPredecessors().stream().filter(b -> b != bottomFinallyBlock);
if (bottom == mth.getExitBlock()) {
@@ -219,9 +257,8 @@ public class MarkFinallyVisitor extends AbstractVisitor {
for (InsnsSlice dupSlice : extractInfo.getDuplicateSlices()) {
List<InsnNode> dupInsnsList = dupSlice.getInsnsList();
if (dupInsnsList.size() != finallyInsnsList.size()) {
if (LOG.isDebugEnabled()) {
LOG.debug("Incorrect finally slice size: {}, expected: {}", dupSlice, finallySlice);
}
extractInfo.getMth().addDebugComment(
"Incorrect finally slice size: " + dupSlice + ", expected: " + finallySlice);
return false;
}
}
@@ -231,9 +268,8 @@ public class MarkFinallyVisitor extends AbstractVisitor {
List<InsnNode> insnsList = dupSlice.getInsnsList();
InsnNode dupInsn = insnsList.get(i);
if (finallyInsn.getType() != dupInsn.getType()) {
if (LOG.isDebugEnabled()) {
LOG.debug("Incorrect finally slice insn: {}, expected: {}", dupInsn, finallyInsn);
}
extractInfo.getMth().addDebugComment(
"Incorrect finally slice insn: " + dupInsn + ", expected: " + finallyInsn);
return false;
}
}
@@ -342,24 +378,29 @@ public class MarkFinallyVisitor extends AbstractVisitor {
private static InsnsSlice isStartBlock(BlockNode dupBlock, BlockNode finallyBlock, FinallyExtractInfo extractInfo) {
List<InsnNode> dupInsns = dupBlock.getInstructions();
List<InsnNode> finallyInsns = finallyBlock.getInstructions();
if (dupInsns.size() < finallyInsns.size()) {
int dupSize = dupInsns.size();
int finSize = finallyInsns.size();
if (dupSize < finSize) {
return null;
}
int startPos = dupInsns.size() - finallyInsns.size();
int startPos;
int endPos = 0;
// fast check from end of block
if (!checkInsns(dupInsns, finallyInsns, startPos)) {
// check from block start
if (checkInsns(dupInsns, finallyInsns, 0)) {
startPos = 0;
endPos = finallyInsns.size();
} else {
if (dupSize == finSize) {
if (!checkInsns(dupInsns, finallyInsns, 0)) {
return null;
}
startPos = 0;
} else {
// dupSize > finSize
startPos = dupSize - finSize;
// fast check from end of block
if (!checkInsns(dupInsns, finallyInsns, startPos)) {
// search start insn
boolean found = false;
for (int i = 1; i < startPos; i++) {
if (checkInsns(dupInsns, finallyInsns, i)) {
startPos = i;
endPos = finallyInsns.size() + i;
endPos = finSize + i;
found = true;
break;
}
@@ -379,7 +420,7 @@ public class MarkFinallyVisitor extends AbstractVisitor {
// both slices completed
complete = true;
} else {
endIndex = dupInsns.size();
endIndex = dupSize;
complete = false;
}
@@ -393,9 +434,8 @@ public class MarkFinallyVisitor extends AbstractVisitor {
if (finallySlice.isComplete()) {
// compare slices
if (finallySlice.getInsnsList().size() != slice.getInsnsList().size()) {
if (LOG.isDebugEnabled()) {
LOG.debug("Another duplicated slice has different insns count: {}, finally: {}", slice, finallySlice);
}
extractInfo.getMth().addDebugComment(
"Another duplicated slice has different insns count: " + slice + ", finally: " + finallySlice);
return null;
}
// TODO: add additional slices checks
@@ -522,7 +562,7 @@ public class MarkFinallyVisitor extends AbstractVisitor {
DepthTraversal.visit(visitor, mth);
}
} catch (Exception e) {
LOG.error("Undo finally extract failed, mth: {}", mth, e);
mth.addError("Undo finally extract failed", e);
}
}
}
@@ -38,15 +38,105 @@ public class IfRegionVisitor extends AbstractVisitor {
public boolean enterRegion(MethodNode mth, IRegion region) {
if (region instanceof IfRegion) {
IfRegion ifRegion = (IfRegion) region;
simplifyIfCondition(ifRegion);
moveReturnToThenBlock(mth, ifRegion);
moveBreakToThenBlock(ifRegion);
markElseIfChains(ifRegion);
orderBranches(mth, ifRegion);
markElseIfChains(mth, ifRegion);
}
return true;
}
}
@SuppressWarnings("UnnecessaryReturnStatement")
private static void orderBranches(MethodNode mth, IfRegion ifRegion) {
if (RegionUtils.isEmpty(ifRegion.getElseRegion())) {
return;
}
if (RegionUtils.isEmpty(ifRegion.getThenRegion())) {
invertIfRegion(ifRegion);
return;
}
if (mth.contains(AFlag.USE_LINES_HINTS)) {
int thenLine = RegionUtils.getFirstSourceLine(ifRegion.getThenRegion());
int elseLine = RegionUtils.getFirstSourceLine(ifRegion.getElseRegion());
if (thenLine != 0 && elseLine != 0) {
if (thenLine > elseLine) {
invertIfRegion(ifRegion);
}
return;
}
}
if (ifRegion.simplifyCondition()) {
IfCondition condition = ifRegion.getCondition();
if (condition != null && condition.getMode() == Mode.NOT) {
invertIfRegion(ifRegion);
}
}
int thenSize = insnsCount(ifRegion.getThenRegion());
int elseSize = insnsCount(ifRegion.getElseRegion());
if (isSimpleExitBlock(mth, ifRegion.getElseRegion())) {
if (isSimpleExitBlock(mth, ifRegion.getThenRegion())) {
if (elseSize < thenSize) {
invertIfRegion(ifRegion);
return;
}
}
boolean lastRegion = ifRegion == RegionUtils.getLastRegion(mth.getRegion());
if (elseSize == 1 && lastRegion && mth.isVoidReturn()) {
// single return at method end will be removed later
return;
}
if (!lastRegion) {
invertIfRegion(ifRegion);
}
return;
}
boolean thenExit = RegionUtils.hasExitBlock(ifRegion.getThenRegion());
boolean elseExit = RegionUtils.hasExitBlock(ifRegion.getElseRegion());
if (elseExit && (!thenExit || elseSize < thenSize)) {
invertIfRegion(ifRegion);
return;
}
// move 'if' from 'then' branch to make 'else if' chain
if (isIfRegion(ifRegion.getThenRegion())
&& !isIfRegion(ifRegion.getElseRegion())
&& !thenExit) {
invertIfRegion(ifRegion);
return;
}
// move 'break' into 'then' branch
if (RegionUtils.hasBreakInsn(ifRegion.getElseRegion())) {
invertIfRegion(ifRegion);
return;
}
}
private static boolean isIfRegion(IContainer container) {
if (container instanceof IfRegion) {
return true;
}
if (container instanceof IRegion) {
List<IContainer> subBlocks = ((IRegion) container).getSubBlocks();
return subBlocks.size() == 1 && subBlocks.get(0) instanceof IfRegion;
}
return false;
}
/**
* Mark if-else-if chains
*/
private static void markElseIfChains(MethodNode mth, IfRegion ifRegion) {
if (isSimpleExitBlock(mth, ifRegion.getThenRegion())) {
return;
}
IContainer elsRegion = ifRegion.getElseRegion();
if (elsRegion instanceof Region) {
List<IContainer> subBlocks = ((Region) elsRegion).getSubBlocks();
if (subBlocks.size() == 1 && subBlocks.get(0) instanceof IfRegion) {
subBlocks.get(0).add(AFlag.ELSE_IF_CHAIN);
elsRegion.add(AFlag.ELSE_IF_CHAIN);
}
}
}
private static class RemoveRedundantElseVisitor implements IRegionIterativeVisitor {
@Override
public boolean visitRegion(MethodNode mth, IRegion region) {
@@ -57,76 +147,6 @@ public class IfRegionVisitor extends AbstractVisitor {
}
}
private static void simplifyIfCondition(IfRegion ifRegion) {
if (ifRegion.simplifyCondition()) {
IfCondition condition = ifRegion.getCondition();
if (condition.getMode() == Mode.NOT) {
invertIfRegion(ifRegion);
}
}
IContainer elseRegion = ifRegion.getElseRegion();
if (elseRegion == null || RegionUtils.isEmpty(elseRegion)) {
return;
}
boolean thenIsEmpty = RegionUtils.isEmpty(ifRegion.getThenRegion());
if (thenIsEmpty || hasSimpleReturnBlock(ifRegion.getThenRegion())) {
invertIfRegion(ifRegion);
}
if (!thenIsEmpty) {
// move 'if' from then to make 'else if' chain
if (isIfRegion(ifRegion.getThenRegion())
&& !isIfRegion(elseRegion)) {
invertIfRegion(ifRegion);
}
}
}
private static boolean isIfRegion(IContainer container) {
if (container instanceof IfRegion) {
return true;
}
if (container instanceof IRegion) {
List<IContainer> subBlocks = ((IRegion) container).getSubBlocks();
if (subBlocks.size() == 1 && subBlocks.get(0) instanceof IfRegion) {
return true;
}
}
return false;
}
private static void moveReturnToThenBlock(MethodNode mth, IfRegion ifRegion) {
if (!mth.isVoidReturn()
&& hasSimpleReturnBlock(ifRegion.getElseRegion())
/* && insnsCount(ifRegion.getThenRegion()) < 2 */) {
invertIfRegion(ifRegion);
}
}
private static void moveBreakToThenBlock(IfRegion ifRegion) {
if (ifRegion.getElseRegion() != null
&& RegionUtils.hasBreakInsn(ifRegion.getElseRegion())) {
invertIfRegion(ifRegion);
}
}
/**
* Mark if-else-if chains
*/
private static void markElseIfChains(IfRegion ifRegion) {
if (hasSimpleReturnBlock(ifRegion.getThenRegion())) {
return;
}
IContainer elsRegion = ifRegion.getElseRegion();
if (elsRegion instanceof Region) {
List<IContainer> subBlocks = ((Region) elsRegion).getSubBlocks();
if (subBlocks.size() == 1 && subBlocks.get(0) instanceof IfRegion) {
subBlocks.get(0).add(AFlag.ELSE_IF_CHAIN);
elsRegion.add(AFlag.ELSE_IF_CHAIN);
}
}
}
private static boolean removeRedundantElseBlock(MethodNode mth, IfRegion ifRegion) {
if (ifRegion.getElseRegion() == null
|| ifRegion.contains(AFlag.ELSE_IF_CHAIN)
@@ -162,16 +182,16 @@ public class IfRegionVisitor extends AbstractVisitor {
}
}
private static boolean hasSimpleReturnBlock(IContainer region) {
if (region == null) {
private static boolean isSimpleExitBlock(MethodNode mth, IContainer container) {
if (container == null) {
return false;
}
if (region.contains(AFlag.RETURN)) {
if (container.contains(AFlag.RETURN) || RegionUtils.isExitBlock(mth, container)) {
return true;
}
if (region instanceof IRegion) {
List<IContainer> subBlocks = ((IRegion) region).getSubBlocks();
return subBlocks.size() == 1 && subBlocks.get(0).contains(AFlag.RETURN);
if (container instanceof IRegion) {
List<IContainer> subBlocks = ((IRegion) container).getSubBlocks();
return subBlocks.size() == 1 && RegionUtils.isExitBlock(mth, subBlocks.get(0));
}
return false;
}
@@ -93,7 +93,8 @@ public class TernaryMod extends AbstractRegionVisitor implements IRegionIterativ
InsnNode thenInsn = tb.getInstructions().get(0);
InsnNode elseInsn = eb.getInstructions().get(0);
if (thenInsn.getSourceLine() != elseInsn.getSourceLine()) {
if (mth.contains(AFlag.USE_LINES_HINTS)
&& thenInsn.getSourceLine() != elseInsn.getSourceLine()) {
if (thenInsn.getSourceLine() != 0 && elseInsn.getSourceLine() != 0) {
// sometimes source lines incorrect
if (!checkLineStats(thenInsn, elseInsn)) {
@@ -134,6 +135,8 @@ public class TernaryMod extends AbstractRegionVisitor implements IRegionIterativ
InsnArg thenArg = InsnArg.wrapInsnIntoArg(thenInsn);
InsnArg elseArg = InsnArg.wrapInsnIntoArg(elseInsn);
TernaryInsn ternInsn = new TernaryInsn(ifRegion.getCondition(), resArg, thenArg, elseArg);
int branchLine = Math.max(thenInsn.getSourceLine(), elseInsn.getSourceLine());
ternInsn.setSourceLine(Math.max(ifRegion.getSourceLine(), branchLine));
InsnRemover.unbindResult(mth, elseInsn);
@@ -14,9 +14,7 @@ import jadx.core.Consts;
import jadx.core.codegen.json.JsonMappingGen;
import jadx.core.deobf.Deobfuscator;
import jadx.core.deobf.NameMapper;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.nodes.RenameReasonAttr;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.nodes.ClassNode;
@@ -45,8 +43,8 @@ public class RenameVisitor extends AbstractVisitor {
deobfuscator.execute();
}
checkClasses(deobfuscator, root, args);
UserRenames.applyForNodes(root);
checkClasses(deobfuscator, root, args);
if (args.isDeobfuscationOn() || !args.isJsonOutput()) {
deobfuscator.savePresets();
@@ -196,11 +194,6 @@ public class RenameVisitor extends AbstractVisitor {
if (args.isRenameValid()) {
Set<String> names = new HashSet<>(methods.size());
for (MethodNode mth : methods) {
AccessInfo accessFlags = mth.getAccessFlags();
if (accessFlags.isBridge() || accessFlags.isSynthetic()
|| mth.contains(AFlag.DONT_GENERATE) /* this flag not set yet */) {
continue;
}
String signature = mth.getMethodInfo().makeSignature(true, false);
if (!names.add(signature)) {
deobfuscator.forceRenameMethod(mth);
@@ -182,6 +182,16 @@ public class BlockUtils {
return null;
}
public static int getFirstSourceLine(IBlock block) {
for (InsnNode insn : block.getInstructions()) {
int line = insn.getSourceLine();
if (line != 0) {
return line;
}
}
return 0;
}
@Nullable
public static InsnNode getFirstInsn(@Nullable IBlock block) {
if (block == null) {
@@ -207,11 +217,22 @@ public class BlockUtils {
}
public static boolean isExitBlock(MethodNode mth, BlockNode block) {
BlockNode exitBlock = mth.getExitBlock();
if (block == exitBlock) {
if (block == mth.getExitBlock()) {
return true;
}
return exitBlock.getPredecessors().contains(block);
return isExitBlock(block);
}
public static boolean isExitBlock(BlockNode block) {
List<BlockNode> successors = block.getSuccessors();
if (successors.isEmpty()) {
return true;
}
if (successors.size() == 1) {
BlockNode next = successors.get(0);
return next.getSuccessors().isEmpty();
}
return false;
}
public static boolean containsExitInsn(IBlock block) {
@@ -992,6 +1013,9 @@ public class BlockUtils {
*/
@Nullable
public static InsnNode getOnlyOneInsnFromMth(MethodNode mth) {
if (mth.isNoCode()) {
return null;
}
InsnNode insn = null;
for (BlockNode block : mth.getBasicBlocks()) {
List<InsnNode> blockInsns = block.getInstructions();
@@ -4,9 +4,9 @@ import java.util.List;
import org.jetbrains.annotations.Nullable;
import jadx.api.CodePosition;
import jadx.api.CommentsLevel;
import jadx.api.ICodeWriter;
import jadx.api.metadata.ICodeAnnotation;
import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.api.plugins.input.data.attributes.types.SourceFileAttr;
import jadx.core.dex.attributes.AType;
@@ -95,7 +95,7 @@ public class CodeGenUtils {
private static void addMultiLineComment(ICodeWriter code, List<String> comments) {
boolean first = true;
String indent = "";
Object lineAnn = null;
ICodeAnnotation lineAnn = null;
for (String comment : comments) {
for (String line : comment.split("\n")) {
if (first) {
@@ -104,7 +104,7 @@ public class CodeGenUtils {
int startLinePos = buf.lastIndexOf(ICodeWriter.NL) + 1;
indent = Utils.strRepeat(" ", buf.length() - startLinePos);
if (code.isMetadataSupported()) {
lineAnn = code.getRawAnnotations().get(new CodePosition(code.getLine()));
lineAnn = code.getRawAnnotations().get(startLinePos);
}
} else {
code.newLine().add(indent);
@@ -246,4 +246,29 @@ public class DebugUtils {
Set<Object> seen = ConcurrentHashMap.newKeySet();
return t -> seen.add(keyExtractor.apply(t));
}
private static Map<String, Long> execTimes;
public static void initExecTimes() {
execTimes = new ConcurrentHashMap<>();
}
public static void mergeExecTimeFromStart(String tag, long startTimeMillis) {
mergeExecTime(tag, System.currentTimeMillis() - startTimeMillis);
}
public static void mergeExecTime(String tag, long execTimeMillis) {
execTimes.merge(tag, execTimeMillis, Long::sum);
}
public static void printExecTimes() {
System.out.println("Exec times:");
execTimes.forEach((tag, time) -> System.out.println(" " + tag + ": " + time + "ms"));
}
public static void printExecTimesWithTotal(long totalMillis) {
System.out.println("Exec times: total " + totalMillis + "ms");
execTimes.forEach((tag, time) -> System.out.println(" " + tag + ": " + time + "ms"
+ String.format(" (%.2f%%)", time * 100. / (double) totalMillis)));
}
}
@@ -13,9 +13,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.IDecompileScheduler;
import jadx.api.JadxDecompiler;
import jadx.api.JavaClass;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.utils.exceptions.JadxRuntimeException;
public class DecompilerScheduler implements IDecompileScheduler {
@@ -24,18 +22,11 @@ public class DecompilerScheduler implements IDecompileScheduler {
private static final int MERGED_BATCH_SIZE = 16;
private static final boolean DEBUG_BATCHES = false;
private final JadxDecompiler decompiler;
public DecompilerScheduler(JadxDecompiler decompiler) {
this.decompiler = decompiler;
}
@Override
public List<List<JavaClass>> buildBatches(List<JavaClass> classes) {
try {
long start = System.currentTimeMillis();
List<List<ClassNode>> batches = internalBatches(Utils.collectionMap(classes, JavaClass::getClassNode));
List<List<JavaClass>> result = Utils.collectionMap(batches, l -> Utils.collectionMapNoNull(l, decompiler::getJavaClassByNode));
List<List<JavaClass>> result = internalBatches(classes);
if (LOG.isDebugEnabled()) {
LOG.debug("Build decompilation batches in {}ms", System.currentTimeMillis() - start);
}
@@ -53,14 +44,14 @@ public class DecompilerScheduler implements IDecompileScheduler {
* Put classes with many dependencies at the end.
* Build batches for dependencies of single class to avoid locking from another thread.
*/
public List<List<ClassNode>> internalBatches(List<ClassNode> classes) {
public List<List<JavaClass>> internalBatches(List<JavaClass> classes) {
List<DepInfo> deps = sumDependencies(classes);
Set<ClassNode> added = new HashSet<>(classes.size());
Comparator<ClassNode> cmpDepSize = Comparator.comparingInt(ClassNode::getTotalDepsCount);
List<List<ClassNode>> result = new ArrayList<>();
List<ClassNode> mergedBatch = new ArrayList<>(MERGED_BATCH_SIZE);
Set<JavaClass> added = new HashSet<>(classes.size());
Comparator<JavaClass> cmpDepSize = Comparator.comparingInt(JavaClass::getTotalDepsCount);
List<List<JavaClass>> result = new ArrayList<>();
List<JavaClass> mergedBatch = new ArrayList<>(MERGED_BATCH_SIZE);
for (DepInfo depInfo : deps) {
ClassNode cls = depInfo.getCls();
JavaClass cls = depInfo.getCls();
if (!added.add(cls)) {
continue;
}
@@ -73,9 +64,9 @@ public class DecompilerScheduler implements IDecompileScheduler {
mergedBatch = new ArrayList<>(MERGED_BATCH_SIZE);
}
} else {
List<ClassNode> batch = new ArrayList<>(depsSize + 1);
for (ClassNode dep : cls.getDependencies()) {
ClassNode topDep = dep.getTopParentClass();
List<JavaClass> batch = new ArrayList<>(depsSize + 1);
for (JavaClass dep : cls.getDependencies()) {
JavaClass topDep = dep.getTopParentClass();
if (!added.contains(topDep)) {
batch.add(topDep);
added.add(topDep);
@@ -95,11 +86,11 @@ public class DecompilerScheduler implements IDecompileScheduler {
return result;
}
private static List<DepInfo> sumDependencies(List<ClassNode> classes) {
private static List<DepInfo> sumDependencies(List<JavaClass> classes) {
List<DepInfo> deps = new ArrayList<>(classes.size());
for (ClassNode cls : classes) {
for (JavaClass cls : classes) {
int count = 0;
for (ClassNode dep : cls.getDependencies()) {
for (JavaClass dep : cls.getDependencies()) {
count += 1 + dep.getTotalDepsCount();
}
deps.add(new DepInfo(cls, count));
@@ -109,15 +100,15 @@ public class DecompilerScheduler implements IDecompileScheduler {
}
private static final class DepInfo implements Comparable<DepInfo> {
private final ClassNode cls;
private final JavaClass cls;
private final int depsCount;
private DepInfo(ClassNode cls, int depsCount) {
private DepInfo(JavaClass cls, int depsCount) {
this.cls = cls;
this.depsCount = depsCount;
}
public ClassNode getCls() {
public JavaClass getCls() {
return cls;
}
@@ -129,7 +120,7 @@ public class DecompilerScheduler implements IDecompileScheduler {
public int compareTo(@NotNull DecompilerScheduler.DepInfo o) {
int deps = Integer.compare(depsCount, o.depsCount);
if (deps == 0) {
return cls.compareTo(o.cls);
return cls.getClassNode().compareTo(o.cls.getClassNode());
}
return deps;
}
@@ -147,9 +138,9 @@ public class DecompilerScheduler implements IDecompileScheduler {
.collect(Collectors.toList());
}
private void dumpBatchesStats(List<ClassNode> classes, List<List<ClassNode>> result, List<DepInfo> deps) {
private void dumpBatchesStats(List<JavaClass> classes, List<List<JavaClass>> result, List<DepInfo> deps) {
double avg = result.stream().mapToInt(List::size).average().orElse(-1);
int maxSingleDeps = classes.stream().mapToInt(ClassNode::getTotalDepsCount).max().orElse(-1);
int maxSingleDeps = classes.stream().mapToInt(JavaClass::getTotalDepsCount).max().orElse(-1);
int maxSubDeps = deps.stream().mapToInt(DepInfo::getDepsCount).max().orElse(-1);
LOG.info("Batches stats:"
+ "\n input classes: " + classes.size()
@@ -1,6 +1,7 @@
package jadx.core.utils;
import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;
import org.jetbrains.annotations.Nullable;
@@ -140,6 +141,37 @@ public class InsnUtils {
return null;
}
public static void replaceInsns(MethodNode mth, Function<InsnNode, InsnNode> replaceFunction) {
for (BlockNode block : mth.getBasicBlocks()) {
List<InsnNode> insns = block.getInstructions();
int insnsCount = insns.size();
for (int i = 0; i < insnsCount; i++) {
InsnNode insn = insns.get(i);
replaceInsnsInInsn(mth, insn, replaceFunction);
InsnNode replace = replaceFunction.apply(insn);
if (replace != null) {
BlockUtils.replaceInsn(mth, block, i, replace);
}
}
}
}
public static void replaceInsnsInInsn(MethodNode mth, InsnNode insn, Function<InsnNode, InsnNode> replaceFunction) {
int argsCount = insn.getArgsCount();
for (int i = 0; i < argsCount; i++) {
InsnArg arg = insn.getArg(i);
if (arg.isInsnWrap()) {
InsnNode wrapInsn = ((InsnWrapArg) arg).getWrapInsn();
replaceInsnsInInsn(mth, wrapInsn, replaceFunction);
InsnNode replace = replaceFunction.apply(wrapInsn);
if (replace != null) {
InsnRemover.unbindArgUsage(mth, arg);
insn.setArg(i, InsnArg.wrapInsnIntoArg(replace));
}
}
}
}
@Nullable
public static RegisterArg getRegFromInsn(List<RegisterArg> regs, InsnType insnType) {
for (RegisterArg reg : regs) {
@@ -16,6 +16,7 @@ import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.IBlock;
import jadx.core.dex.nodes.IBranchRegion;
import jadx.core.dex.nodes.IConditionRegion;
import jadx.core.dex.nodes.IContainer;
import jadx.core.dex.nodes.IRegion;
import jadx.core.dex.nodes.InsnNode;
@@ -52,6 +53,7 @@ public class RegionUtils {
throw new JadxRuntimeException(unknownContainerType(container));
}
@Nullable
public static InsnNode getFirstInsn(IContainer container) {
if (container instanceof IBlock) {
IBlock block = (IBlock) container;
@@ -74,6 +76,37 @@ public class RegionUtils {
}
}
public static int getFirstSourceLine(IContainer container) {
if (container instanceof IBlock) {
return BlockUtils.getFirstSourceLine((IBlock) container);
}
if (container instanceof IConditionRegion) {
return ((IConditionRegion) container).getConditionSourceLine();
}
if (container instanceof IBranchRegion) {
IBranchRegion branchRegion = (IBranchRegion) container;
return getFirstSourceLine(branchRegion.getBranches());
}
if (container instanceof IRegion) {
IRegion region = (IRegion) container;
return getFirstSourceLine(region.getSubBlocks());
}
return 0;
}
private static int getFirstSourceLine(List<IContainer> containers) {
if (containers.isEmpty()) {
return 0;
}
for (IContainer container : containers) {
int line = getFirstSourceLine(container);
if (line != 0) {
return line;
}
}
return 0;
}
public static InsnNode getLastInsn(IContainer container) {
if (container instanceof IBlock) {
IBlock block = (IBlock) container;
@@ -112,31 +145,58 @@ public class RegionUtils {
}
}
@Nullable
public static IContainer getLastRegion(@Nullable IContainer container) {
if (container == null) {
return null;
}
if (container instanceof IBlock || container instanceof IBranchRegion) {
return container;
}
if (container instanceof IRegion) {
return getLastRegion(Utils.last(((IRegion) container).getSubBlocks()));
}
throw new JadxRuntimeException(unknownContainerType(container));
}
public static boolean isExitBlock(MethodNode mth, IContainer container) {
if (container instanceof BlockNode) {
return BlockUtils.isExitBlock(mth, (BlockNode) container);
}
return false;
}
/**
* Return true if last block in region has no successors or jump out insn (return or break)
*/
public static boolean hasExitBlock(IContainer container) {
if (container == null) {
return false;
}
return hasExitBlock(container, container);
}
private static boolean hasExitBlock(IContainer rootContainer, IContainer container) {
if (container instanceof BlockNode) {
BlockNode blockNode = (BlockNode) container;
if (blockNode.getSuccessors().isEmpty()) {
if (BlockUtils.isExitBlock(blockNode)) {
return true;
}
return isInsnExitContainer(rootContainer, (IBlock) container);
} else if (container instanceof IBranchRegion) {
return false;
} else if (container instanceof IBlock) {
}
if (container instanceof IBranchRegion) {
IBranchRegion branchRegion = (IBranchRegion) container;
return ListUtils.allMatch(branchRegion.getBranches(), RegionUtils::hasExitBlock);
}
if (container instanceof IBlock) {
return isInsnExitContainer(rootContainer, (IBlock) container);
} else if (container instanceof IRegion) {
}
if (container instanceof IRegion) {
List<IContainer> blocks = ((IRegion) container).getSubBlocks();
return !blocks.isEmpty()
&& hasExitBlock(rootContainer, blocks.get(blocks.size() - 1));
} else {
throw new JadxRuntimeException(unknownContainerType(container));
}
throw new JadxRuntimeException(unknownContainerType(container));
}
private static boolean isInsnExitContainer(IContainer rootContainer, IBlock block) {
@@ -192,17 +252,25 @@ public class RegionUtils {
public static int insnsCount(IContainer container) {
if (container instanceof IBlock) {
return ((IBlock) container).getInstructions().size();
} else if (container instanceof IRegion) {
List<InsnNode> insnList = ((IBlock) container).getInstructions();
int count = 0;
for (InsnNode insn : insnList) {
if (insn.contains(AFlag.DONT_GENERATE)) {
continue;
}
count++;
}
return count;
}
if (container instanceof IRegion) {
IRegion region = (IRegion) container;
int count = 0;
for (IContainer block : region.getSubBlocks()) {
count += insnsCount(block);
}
return count;
} else {
throw new JadxRuntimeException(unknownContainerType(container));
}
throw new JadxRuntimeException(unknownContainerType(container));
}
public static boolean isEmpty(IContainer container) {
@@ -428,7 +428,7 @@ public class Utils {
}
public static void checkThreadInterrupt() {
if (Thread.interrupted()) {
if (Thread.currentThread().isInterrupted()) {
throw new JadxRuntimeException("Thread interrupted");
}
}
@@ -8,16 +8,19 @@ import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileVisitOption;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.jetbrains.annotations.NotNull;
@@ -100,6 +103,10 @@ public class FileUtils {
}
}
public static void deleteFileIfExists(Path filePath) throws IOException {
Files.deleteIfExists(filePath);
}
public static boolean deleteDir(File dir) {
File[] content = dir.listFiles();
if (content != null) {
@@ -110,13 +117,24 @@ public class FileUtils {
return dir.delete();
}
public static void deleteDir(Path dir) {
public static void deleteDirIfExists(Path dir) {
if (Files.exists(dir)) {
try {
deleteDir(dir);
} catch (Exception e) {
LOG.error("Failed to delete dir: " + dir.toAbsolutePath(), e);
}
}
}
private static void deleteDir(Path dir) {
try (Stream<Path> pathStream = Files.walk(dir)) {
pathStream.sorted(Comparator.reverseOrder())
.map(Path::toFile)
.forEach(file -> {
if (!file.delete()) {
LOG.warn("Failed to remove file: {}", file.getAbsolutePath());
.forEach(path -> {
try {
Files.delete(path);
} catch (Exception e) {
throw new JadxRuntimeException("Failed to delete path: " + path.toAbsolutePath(), e);
}
});
} catch (Exception e) {
@@ -143,11 +161,11 @@ public class FileUtils {
}
public static void deleteTempRootDir() {
deleteDir(TEMP_ROOT_DIR);
deleteDirIfExists(TEMP_ROOT_DIR);
}
public static void clearTempRootDir() {
deleteDir(TEMP_ROOT_DIR);
deleteDirIfExists(TEMP_ROOT_DIR);
makeDirs(TEMP_ROOT_DIR);
}
@@ -208,6 +226,15 @@ public class FileUtils {
}
}
public static void writeFile(Path file, String data) throws IOException {
FileUtils.makeDirsForFile(file);
Files.write(file, data.getBytes(StandardCharsets.UTF_8));
}
public static String readFile(Path textFile) throws IOException {
return new String(Files.readAllBytes(textFile), StandardCharsets.UTF_8);
}
@NotNull
public static File prepareFile(File file) {
File saveFile = cutFileName(file);
@@ -230,18 +257,41 @@ public class FileUtils {
return new File(file.getParentFile(), name);
}
private static String bytesToHex(byte[] bytes) {
char[] hexArray = "0123456789abcdef".toCharArray();
if (bytes == null || bytes.length <= 0) {
return null;
private static final byte[] HEX_ARRAY = "0123456789abcdef".getBytes(StandardCharsets.US_ASCII);
public static String bytesToHex(byte[] bytes) {
if (bytes == null || bytes.length == 0) {
return "";
}
char[] hexChars = new char[bytes.length * 2];
byte[] hexChars = new byte[bytes.length * 2];
for (int j = 0; j < bytes.length; j++) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
hexChars[j * 2] = HEX_ARRAY[v >>> 4];
hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
}
return new String(hexChars);
return new String(hexChars, StandardCharsets.UTF_8);
}
/**
* Zero padded hex string for first byte
*/
public static String byteToHex(int value) {
int v = value & 0xFF;
byte[] hexChars = new byte[] { HEX_ARRAY[v >>> 4], HEX_ARRAY[v & 0x0F] };
return new String(hexChars, StandardCharsets.US_ASCII);
}
/**
* Zero padded hex string for int value
*/
public static String intToHex(int value) {
byte[] hexChars = new byte[8];
int v = value;
for (int i = 7; i >= 0; i--) {
hexChars[i] = HEX_ARRAY[v & 0x0F];
v >>>= 4;
}
return new String(hexChars, StandardCharsets.US_ASCII);
}
public static boolean isZipFile(File file) {
@@ -275,4 +325,30 @@ public class FileUtils {
}
return new File(path);
}
public static List<Path> toPaths(List<File> files) {
return files.stream().map(File::toPath).collect(Collectors.toList());
}
public static List<Path> toPaths(File[] files) {
return Stream.of(files).map(File::toPath).collect(Collectors.toList());
}
public static List<Path> fileNamesToPaths(List<String> fileNames) {
return fileNames.stream().map(Paths::get).collect(Collectors.toList());
}
public static List<File> toFiles(List<Path> paths) {
return paths.stream().map(Path::toFile).collect(Collectors.toList());
}
public static String md5Sum(byte[] data) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(data);
return bytesToHex(md.digest());
} catch (Exception e) {
throw new JadxRuntimeException("Failed to build hash", e);
}
}
}
@@ -89,7 +89,8 @@ public class BinaryXMLParser extends CommonBinaryParser {
is.mark(4);
int v = is.readInt16(); // version
int h = is.readInt16(); // header size
if (v == 0x0003 && h == 0x0008) {
// Some APK Manifest.xml the version is 0
if (h == 0x0008) {
return true;
}
is.reset();
@@ -0,0 +1,13 @@
package jadx.core.xmlgen;
import java.io.IOException;
import java.io.InputStream;
public interface IResParser {
void decode(InputStream inputStream) throws IOException;
ResourceStorage getResStorage();
String[] getStrings();
}

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