Compare commits

..

4 Commits

Author SHA1 Message Date
Skylot 5640f3b931 fix test 2024-06-02 18:21:21 +01:00
Skylot 02bc27e887 build exclude list in init method to improve performance, add default values migration 2024-06-02 18:21:04 +01:00
bagipro 794e5adb7f Change deobf-min default to 2 2024-05-09 18:22:02 +07:00
bagipro a81cec7701 Improve deobf whitelist 2024-05-08 16:16:08 +07:00
764 changed files with 17719 additions and 30303 deletions
+14 -20
View File
@@ -16,7 +16,7 @@ jobs:
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 21
java-version: 11
- name: Set jadx version
run: |
@@ -24,13 +24,10 @@ jobs:
JADX_VERSION="r${JADX_REV}.${GITHUB_SHA:0:7}"
echo "JADX_VERSION=$JADX_VERSION" >> $GITHUB_ENV
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- name: Build
run: ./gradlew dist distWin
env:
JADX_BUILD_JAVA_VERSION: 11
- name: Build with Gradle
uses: gradle/actions/setup-gradle@v3
with:
arguments: dist copyExe
- name: Save bundle artifact
uses: actions/upload-artifact@v4
@@ -42,12 +39,11 @@ jobs:
if-no-files-found: error
retention-days: 14
- name: Save Windows bundle artifact
- name: Save exe artifact
uses: actions/upload-artifact@v4
with:
name: ${{ format('jadx-gui-{0}-no-jre-win', env.JADX_VERSION) }}
# Upload unpacked files for now
path: jadx-gui/build/jadx-gui-win/*
name: ${{ format('jadx-gui-{0}-no-jre-win.exe', env.JADX_VERSION) }}
path: build/*.exe
if-no-files-found: error
retention-days: 14
@@ -74,17 +70,15 @@ jobs:
JADX_VERSION="r${JADX_REV}.${GITHUB_SHA:0:7}"
echo "JADX_VERSION=$JADX_VERSION" >> $GITHUB_ENV
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- name: Build with Gradle
uses: gradle/actions/setup-gradle@v3
with:
arguments: dist -PbundleJRE=true
- name: Build
run: ./gradlew dist -PbundleJRE=true
- name: Save Windows with JRE bundle artifact
- name: Save exe bundle artifact
uses: actions/upload-artifact@v4
with:
name: ${{ format('jadx-gui-{0}-with-jre-win', env.JADX_VERSION) }}
# Upload unpacked files for now
path: jadx-gui/build/jadx-gui-with-jre-win/*
path: jadx-gui/build/*-with-jre-win/*
if-no-files-found: error
retention-days: 14
+5 -9
View File
@@ -20,13 +20,9 @@ jobs:
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 21
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- name: Build
run: ./gradlew build dist distWin
env:
JADX_BUILD_JAVA_VERSION: 11
java-version: 11
- name: Build with Gradle
uses: gradle/actions/setup-gradle@v3
with:
arguments: build dist copyExe
@@ -0,0 +1,15 @@
name: Validate Gradle Wrapper
on:
push:
branches: [ master, build-test ]
pull_request:
branches: [ master ]
jobs:
validation:
name: Validation
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: gradle/actions/wrapper-validation@v3
-92
View File
@@ -1,92 +0,0 @@
name: Release
on:
push:
tags:
- "v*.*.*"
# additional permissions for provided GitHub token to create new release
permissions:
contents: write
jobs:
build-release-win-bundle:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- name: Set up JDK
uses: oracle-actions/setup-java@v1
with:
release: 23
- name: Set jadx version
uses: actions/github-script@v7
with:
script: |
const jadxVersion = context.ref.split('/').pop().substring(1)
core.exportVariable('JADX_VERSION', jadxVersion);
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- name: Build
run: ./gradlew dist -PbundleJRE=true
- name: Save JRE bundle artifact
uses: actions/upload-artifact@v4
with:
name: ${{ format('jadx-gui-{0}-with-jre-win', env.JADX_VERSION) }}
path: ${{ format('build/distWinWithJre/jadx-gui-{0}-with-jre-win.zip', env.JADX_VERSION) }}
if-no-files-found: error
retention-days: 1
release:
needs: build-release-win-bundle
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up JDK
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 21
- name: Set jadx version and release name
uses: actions/github-script@v7
with:
script: |
const jadxVersion = context.ref.split('/').pop().substring(1)
core.exportVariable('JADX_VERSION', jadxVersion);
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- name: Build
run: ./gradlew dist distWin
env:
JADX_BUILD_JAVA_VERSION: 11
- name: Download Windows JRE bundle
uses: actions/download-artifact@v4
with:
name: ${{ format('jadx-gui-{0}-with-jre-win', env.JADX_VERSION) }}
path: ${{ format('build/jadx-gui-{0}-with-jre-win', env.JADX_VERSION) }}
- run: |
cd build
pwd
ls -l
ls jadx-gui-*-with-jre-win
mv jadx-gui-*-with-jre-win/jadx-gui-*-with-jre-win.zip .
mv distWin/jadx-gui-*-win.zip .
ls -l *.zip
- name: Release
uses: softprops/action-gh-release@v2
with:
name: ${{ env.JADX_VERSION }}
draft: true
fail_on_unmatched_files: true
files: build/jadx-*.zip
-1
View File
@@ -28,7 +28,6 @@ jadx-output/
*-tmp/
**/tmp/
*.jobf
*.jadx
*.class
*.dump
+3 -3
View File
@@ -11,14 +11,14 @@ stages:
java-11:
stage: test
image: eclipse-temurin:11
script: ./gradlew clean build dist distWin
script: ./gradlew clean build dist copyExe
java-17:
stage: test
image: eclipse-temurin:17
script: ./gradlew clean build dist distWin
script: ./gradlew clean build dist copyExe
java-21:
stage: test
image: eclipse-temurin:21
script: ./gradlew clean build dist distWin
script: ./gradlew clean build dist copyExe
+1 -1
View File
@@ -4,7 +4,7 @@
<module name="jadx.jadx-gui.main"/>
<option name="PROGRAM_PARAMETERS" value="-v"/>
<option name="VM_PARAMETERS"
value="-Xms128M -XX:MaxRAMPercentage=70.0 -Dawt.useSystemAAFontSettings=lcd -Dswing.aatext=true -Djava.util.Arrays.useLegacyMergeSort=true -Djdk.util.zip.disableZip64ExtraFieldValidation=true -XX:+IgnoreUnrecognizedVMOptions --add-opens=java.base/java.lang=ALL-UNNAMED -Dsun.java2d.noddraw=true -Dsun.java2d.d3d=false -Dsun.java2d.ddforcevram=true -Dsun.java2d.ddblit=false -Dswing.useflipBufferStrategy=True"/>
value="-Xms128M -XX:MaxRAMPercentage=70.0 -Dawt.useSystemAAFontSettings=lcd -Dswing.aatext=true -Djava.util.Arrays.useLegacyMergeSort=true -Djdk.util.zip.disableZip64ExtraFieldValidation=true -XX:+IgnoreUnrecognizedVMOptions --add-opens=java.base/java.lang=ALL-UNNAMED"/>
<method v="2">
<option name="Make" enabled="true"/>
</method>
+23 -10
View File
@@ -3,7 +3,7 @@
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to make participation in our project and
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
@@ -23,13 +23,13 @@ include:
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
professional setting
## Our Responsibilities
@@ -45,12 +45,25 @@ threatening, offensive, or harmful.
## Scope
This Code of Conduct applies within all project spaces, and it also applies when
an individual is representing the project or its community in public spaces.
Examples of representing a project or community include using an official
project e-mail address, posting via an official social media account, or acting
as an appointed representative at an online or offline event. Representation of
a project may be further defined and clarified by project maintainers.
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at skylot@gmail.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
+87 -93
View File
@@ -91,101 +91,95 @@ commands (use '<command> --help' for command options):
plugins - manage jadx plugins
options:
-d, --output-dir - output directory
-ds, --output-dir-src - output directory for sources
-dr, --output-dir-res - output directory for resources
-r, --no-res - do not decode resources
-s, --no-src - do not decompile source code
--single-class - decompile a single class, full name, raw or alias
--single-class-output - file or dir for write if decompile a single class
--output-format - can be 'java' or 'json', default: java
-e, --export-gradle - save as android gradle project
-j, --threads-count - processing threads count, default: 4
-m, --decompilation-mode - code output mode:
'auto' - trying best options (default)
'restructure' - restore code structure (normal java code)
'simple' - simplified instructions (linear, with goto's)
'fallback' - raw instructions without modifications
--show-bad-code - show inconsistent code (incorrectly decompiled)
--no-xml-pretty-print - do not prettify XML
--no-imports - disable use of imports, always write entire package name
--no-debug-info - disable debug info parsing and processing
--add-debug-lines - add comments with debug line numbers if available
--no-inline-anonymous - disable anonymous classes inline
--no-inline-methods - disable methods inline
--no-move-inner-classes - disable move inner classes into parent
--no-inline-kotlin-lambda - disable inline for Kotlin lambdas
--no-finally - don't extract finally block
--no-restore-switch-over-string - don't restore switch over string
--no-replace-consts - don't replace constant value with matching constant field
--escape-unicode - escape non latin characters in strings (with \u)
--respect-bytecode-access-modifiers - don't change original access modifiers
--mappings-path - deobfuscation mappings file or directory. Allowed formats: Tiny and Tiny v2 (both '.tiny'), Enigma (.mapping) or Enigma directory
--mappings-mode - set mode for handling the deobfuscation mapping file:
'read' - just read, user can always save manually (default)
'read-and-autosave-every-change' - read and autosave after every change
'read-and-autosave-before-closing' - read and autosave before exiting the app or closing the project
'ignore' - don't read or save (can be used to skip loading mapping files referenced in the project file)
--deobf - activate deobfuscation
--deobf-min - min length of name, renamed if shorter, default: 3
--deobf-max - max length of name, renamed if longer, default: 64
--deobf-whitelist - space separated list of classes (full name) and packages (ends with '.*') to exclude from deobfuscation, default: android.support.v4.* android.support.v7.* android.support.v4.os.* android.support.annotation.Px androidx.core.os.* androidx.annotation.Px
--deobf-cfg-file - deobfuscation mappings file used for JADX auto-generated names (in the JOBF file format), default: same dir and name as input file with '.jobf' extension
--deobf-cfg-file-mode - set mode for handling the JADX auto-generated names' deobfuscation map file:
'read' - read if found, don't save (default)
'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-res-name-source - better name source for resources:
'auto' - automatically select best name (default)
'resources' - use resources names
'code' - use R class fields names
--use-source-name-as-class-name-alias - use source name as class name alias:
'always' - always use source name if it's available
'if-better' - use source name if it seems better than the current one
'never' - never use source name, even if it's available
--use-kotlin-methods-for-var-names - use kotlin intrinsic methods to rename variables, values: disable, apply, apply-and-hide, default: apply
--rename-flags - fix options (comma-separated list of):
'case' - fix case sensitivity issues (according to --fs-case-sensitive option),
'valid' - rename java identifiers to make them valid,
'printable' - remove non-printable chars from identifiers,
or single 'none' - to disable all renames
or single 'all' - to enable all (default)
--integer-format - how integers are displayed:
'auto' - automatically select (default)
'decimal' - use decimal
'hexadecimal' - use hexadecimal
--fs-case-sensitive - treat filesystem as case sensitive, false by default
--cfg - save methods control flow graph to dot file
--raw-cfg - save methods control flow graph (use raw instructions)
-f, --fallback - set '--decompilation-mode' to 'fallback' (deprecated)
--use-dx - use dx/d8 to convert java bytecode
--comments-level - set code comments level, values: error, warn, info, debug, user-only, none, default: info
--log-level - set log level, values: quiet, progress, error, warn, info, debug, default: progress
-v, --verbose - verbose output (set --log-level to DEBUG)
-q, --quiet - turn off output (set --log-level to QUIET)
--version - print jadx version
-h, --help - print this help
-d, --output-dir - output directory
-ds, --output-dir-src - output directory for sources
-dr, --output-dir-res - output directory for resources
-r, --no-res - do not decode resources
-s, --no-src - do not decompile source code
--single-class - decompile a single class, full name, raw or alias
--single-class-output - file or dir for write if decompile a single class
--output-format - can be 'java' or 'json', default: java
-e, --export-gradle - save as android gradle project
-j, --threads-count - processing threads count, default: 4
-m, --decompilation-mode - code output mode:
'auto' - trying best options (default)
'restructure' - restore code structure (normal java code)
'simple' - simplified instructions (linear, with goto's)
'fallback' - raw instructions without modifications
--show-bad-code - show inconsistent code (incorrectly decompiled)
--no-xml-pretty-print - do not prettify XML
--no-imports - disable use of imports, always write entire package name
--no-debug-info - disable debug info parsing and processing
--add-debug-lines - add comments with debug line numbers if available
--no-inline-anonymous - disable anonymous classes inline
--no-inline-methods - disable methods inline
--no-move-inner-classes - disable move inner classes into parent
--no-inline-kotlin-lambda - disable inline for Kotlin lambdas
--no-finally - don't extract finally block
--no-replace-consts - don't replace constant value with matching constant field
--escape-unicode - escape non latin characters in strings (with \u)
--respect-bytecode-access-modifiers - don't change original access modifiers
--mappings-path - deobfuscation mappings file or directory. Allowed formats: Tiny and Tiny v2 (both '.tiny'), Enigma (.mapping) or Enigma directory
--mappings-mode - set mode for handling the deobfuscation mapping file:
'read' - just read, user can always save manually (default)
'read-and-autosave-every-change' - read and autosave after every change
'read-and-autosave-before-closing' - read and autosave before exiting the app or closing the project
'ignore' - don't read or save (can be used to skip loading mapping files referenced in the project file)
--deobf - activate deobfuscation
--deobf-min - min length of name, renamed if shorter, default: 3
--deobf-max - max length of name, renamed if longer, default: 64
--deobf-whitelist - space separated list of classes (full name) and packages (ends with '.*') to exclude from deobfuscation, default: android.support.* android.os.* androidx.core.os.* androidx.annotation.*
--deobf-cfg-file - deobfuscation mappings file used for JADX auto-generated names (in the JOBF file format), default: same dir and name as input file with '.jobf' extension
--deobf-cfg-file-mode - set mode for handling the JADX auto-generated names' deobfuscation map file:
'read' - read if found, don't save (default)
'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-use-sourcename - use source file name as class name alias
--deobf-res-name-source - better name source for resources:
'auto' - automatically select best name (default)
'resources' - use resources names
'code' - use R class fields names
--use-kotlin-methods-for-var-names - use kotlin intrinsic methods to rename variables, values: disable, apply, apply-and-hide, default: apply
--rename-flags - fix options (comma-separated list of):
'case' - fix case sensitivity issues (according to --fs-case-sensitive option),
'valid' - rename java identifiers to make them valid,
'printable' - remove non-printable chars from identifiers,
or single 'none' - to disable all renames
or single 'all' - to enable all (default)
--integer-format - how integers are displayed:
'auto' - automatically select (default)
'decimal' - use decimal
'hexadecimal' - use hexadecimal
--fs-case-sensitive - treat filesystem as case sensitive, false by default
--cfg - save methods control flow graph to dot file
--raw-cfg - save methods control flow graph (use raw instructions)
-f, --fallback - set '--decompilation-mode' to 'fallback' (deprecated)
--use-dx - use dx/d8 to convert java bytecode
--comments-level - set code comments level, values: error, warn, info, debug, user-only, none, default: info
--log-level - set log level, values: quiet, progress, error, warn, info, debug, default: progress
-v, --verbose - verbose output (set --log-level to DEBUG)
-q, --quiet - turn off output (set --log-level to QUIET)
--version - print jadx version
-h, --help - print this help
Plugin options (-P<name>=<value>):
dex-input: Load .dex and .apk files
- dex-input.verify-checksum - verify dex file checksum before load, values: [yes, no], default: yes
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
kotlin-metadata: Use kotlin.Metadata annotation for code generation
- kotlin-metadata.class-alias - rename class alias, values: [yes, no], default: yes
- kotlin-metadata.method-args - rename function arguments, values: [yes, no], default: yes
- kotlin-metadata.fields - rename fields, values: [yes, no], default: yes
- kotlin-metadata.companion - rename companion object, values: [yes, no], default: yes
- kotlin-metadata.data-class - add data class modifier, values: [yes, no], default: yes
- kotlin-metadata.to-string - rename fields using toString, values: [yes, no], default: yes
- kotlin-metadata.getters - rename simple getters to field names, values: [yes, no], default: yes
rename-mappings: various mappings support
- rename-mappings.format - mapping format, values: [AUTO, TINY_FILE, TINY_2_FILE, ENIGMA_FILE, ENIGMA_DIR, SRG_FILE, XSRG_FILE, JAM_FILE, CSRG_FILE, TSRG_FILE, TSRG_2_FILE, PROGUARD_FILE, RECAF_SIMPLE_FILE, JOBF_FILE], default: AUTO
- rename-mappings.invert - invert mapping on load, values: [yes, no], default: no
smali-input: Load .smali files
- smali-input.api-level - Android API level, default: 27
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
3) kotlin-metadata: Use kotlin.Metadata annotation for code generation
- kotlin-metadata.class-alias - rename class alias, values: [yes, no], default: yes
- kotlin-metadata.method-args - rename function arguments, values: [yes, no], default: yes
- kotlin-metadata.fields - rename fields, values: [yes, no], default: yes
- kotlin-metadata.companion - rename companion object, values: [yes, no], default: yes
- kotlin-metadata.data-class - add data class modifier, values: [yes, no], default: yes
- kotlin-metadata.to-string - rename fields using toString, values: [yes, no], default: yes
- kotlin-metadata.getters - rename simple getters to field names, values: [yes, no], default: yes
4) rename-mappings: various mappings support
- rename-mappings.format - mapping format, values: [AUTO, TINY_FILE, TINY_2_FILE, ENIGMA_FILE, ENIGMA_DIR, SRG_FILE, XSRG_FILE, CSRG_FILE, TSRG_FILE, TSRG_2_FILE, PROGUARD_FILE], default: AUTO
- rename-mappings.invert - invert mapping on load, values: [yes, no], default: no
Environment variables:
JADX_DISABLE_XML_SECURITY - set to 'true' to disable all security checks for XML files
+3 -4
View File
@@ -2,7 +2,6 @@
## Reporting a Vulnerability
To report a security issue, please open a [new security advisory](https://github.com/skylot/jadx/security/advisories/new).
Please fill the steps you took to create the issue, affected versions, and, if known, mitigations for the issue.
We will check and respond within 3 working days.
If the issue is confirmed as a vulnerability, we will apply required mitigations at the next release.
To report a security issue, please email `skylot@gmail.com` with a description of the issue, the steps you took to create the issue, affected versions, and, if known, mitigations for the issue.
We will check and respond within 3 working days. If the issue is confirmed as a vulnerability, we will apply required mitigations at the next release.
This project follows a 90 day disclosure timeline.
+24 -29
View File
@@ -15,18 +15,6 @@ val jadxVersion by extra { System.getenv("JADX_VERSION") ?: "dev" }
println("jadx version: $jadxVersion")
version = jadxVersion
val jadxBuildJavaVersion by extra { getBuildJavaVersion() }
fun getBuildJavaVersion(): Int? {
val envVarName = "JADX_BUILD_JAVA_VERSION"
val buildJavaVer = System.getenv(envVarName)?.toInt() ?: return null
if (buildJavaVer < 11) {
throw GradleException("'$envVarName' can't be set to lower than 11")
}
println("Set Java toolchain for jadx build to version '$buildJavaVer'")
return buildJavaVer
}
allprojects {
apply(plugin = "java")
apply(plugin = "checkstyle")
@@ -115,34 +103,40 @@ val pack by tasks.registering(Zip::class) {
destinationDirectory.set(layout.buildDirectory)
}
val distWin by tasks.registering(Zip::class) {
val copyExe by tasks.registering(Copy::class) {
group = "jadx"
description = "Build Windows bundle"
description = "Copy exe to build dir"
val guiTask = tasks.getByPath("jadx-gui:copyDistWin")
dependsOn(guiTask)
from(guiTask.outputs)
// next task dependencies not needed, but gradle throws warning because of same output dir
mustRunAfter("jar")
mustRunAfter(pack)
destinationDirectory.set(layout.buildDirectory.dir("distWin"))
archiveFileName.set("jadx-gui-$jadxVersion-win.zip")
from(tasks.getByPath("jadx-gui:createExe"))
include("*.exe")
into(layout.buildDirectory)
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
val distWinWithJre by tasks.registering(Zip::class) {
description = "Build Windows with JRE bundle"
val distWinBundle by tasks.registering(Copy::class) {
group = "jadx"
description = "Copy bundle to build dir"
val guiTask = tasks.getByPath(":jadx-gui:copyDistWinWithJre")
dependsOn(guiTask)
from(guiTask.outputs)
dependsOn(tasks.getByPath(":jadx-gui:distWinWithJre"))
destinationDirectory.set(layout.buildDirectory.dir("distWinWithJre"))
archiveFileName.set("jadx-gui-$jadxVersion-with-jre-win.zip")
// next task dependencies not needed, but gradle throws warning because of same output dir
mustRunAfter("jar")
mustRunAfter(pack)
from(tasks.getByPath("jadx-gui:distWinWithJre").outputs) {
include("*.zip")
}
into(layout.buildDirectory)
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
val dist by tasks.registering {
group = "jadx"
description = "Build jadx distribution zip bundles"
description = "Build jadx distribution zip"
dependsOn(pack)
@@ -150,14 +144,15 @@ val dist by tasks.registering {
if (os.isWindows) {
if (project.hasProperty("bundleJRE")) {
println("Build win bundle with JRE")
dependsOn(distWinWithJre)
dependsOn(distWinBundle)
} else {
dependsOn(distWin)
dependsOn(copyExe)
}
}
}
val cleanBuildDir by tasks.registering(Delete::class) {
group = "jadx"
delete(layout.buildDirectory)
}
tasks.getByName("clean").dependsOn(cleanBuildDir)
+1 -3
View File
@@ -3,9 +3,7 @@ plugins {
}
dependencies {
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.21")
implementation("org.openrewrite:plugin:6.19.1")
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.23")
}
repositories {
+8 -14
View File
@@ -3,27 +3,26 @@ import org.gradle.api.tasks.testing.logging.TestExceptionFormat
plugins {
java
checkstyle
id("jadx-rewrite")
}
val jadxVersion: String by rootProject.extra
val jadxBuildJavaVersion: Int? by rootProject.extra
group = "io.github.skylot"
version = jadxVersion
dependencies {
implementation("org.slf4j:slf4j-api:2.0.16")
compileOnly("org.jetbrains:annotations:26.0.1")
implementation("org.slf4j:slf4j-api:2.0.13")
compileOnly("org.jetbrains:annotations:24.1.0")
testImplementation("ch.qos.logback:logback-classic:1.5.12")
testImplementation("org.assertj:assertj-core:3.26.3")
testImplementation("ch.qos.logback:logback-classic:1.5.6")
testImplementation("org.hamcrest:hamcrest-library:2.2")
testImplementation("org.mockito:mockito-core:5.11.0")
testImplementation("org.assertj:assertj-core:3.25.3")
testImplementation("org.junit.jupiter:junit-jupiter:5.11.3")
testImplementation("org.junit.jupiter:junit-jupiter:5.10.2")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
testCompileOnly("org.jetbrains:annotations:26.0.1")
testCompileOnly("org.jetbrains:annotations:24.1.0")
}
repositories {
@@ -33,11 +32,6 @@ repositories {
}
java {
jadxBuildJavaVersion?.let { buildJavaVer ->
toolchain {
languageVersion = JavaLanguageVersion.of(buildJavaVer)
}
}
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
@@ -30,7 +30,7 @@ publishing {
}
pom {
name.set(project.name)
description.set(project.description ?: "Dex to Java decompiler")
description.set("Dex to Java decompiler")
url.set("https://github.com/skylot/jadx")
licenses {
license {
@@ -42,14 +42,14 @@ publishing {
developer {
id.set("skylot")
name.set("Skylot")
email.set(project.properties["libEmail"].toString())
email.set("skylot@gmail.com")
url.set("https://github.com/skylot")
}
}
scm {
connection.set("scm:git:git://github.com/skylot/jadx.git")
connection .set("scm:git:git://github.com/skylot/jadx.git")
developerConnection.set("scm:git:ssh://github.com:skylot/jadx.git")
url.set("https://github.com/skylot/jadx")
url .set("https://github.com/skylot/jadx")
}
}
}
@@ -1,37 +0,0 @@
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
plugins {
id("org.openrewrite.rewrite")
}
repositories {
mavenCentral()
}
dependencies {
rewrite("org.openrewrite.recipe:rewrite-testing-frameworks:2.21.0")
rewrite("org.openrewrite.recipe:rewrite-logging-frameworks:2.15.1")
rewrite("org.openrewrite.recipe:rewrite-migrate-java:2.28.0")
rewrite("org.openrewrite.recipe:rewrite-static-analysis:1.19.0")
}
tasks {
rewrite {
// exclusion("src/test/java/jadx/tests/integration")
// activeRecipe("org.openrewrite.java.migrate.Java8toJava11")
// checkstyle auto fix
// activeRecipe("org.openrewrite.staticanalysis.CodeCleanup")
// setCheckstyleConfigFile(file("$rootDir/config/checkstyle/checkstyle.xml"))
// logging
// activeRecipe("org.openrewrite.java.logging.slf4j.Slf4jBestPractices")
// activeRecipe("org.openrewrite.java.logging.slf4j.LoggersNamedForEnclosingClass")
// activeRecipe("org.openrewrite.java.logging.slf4j.ParameterizedLogging")
// activeRecipe("org.openrewrite.java.logging.PrintStackTraceToLogError")
// testing
activeRecipe("org.openrewrite.java.testing.assertj.Assertj")
}
}
+2 -6
View File
@@ -120,10 +120,7 @@
<module name="SuppressWarningsHolder"/>
<module name="IllegalType">
<property name="illegalClassNames" value="java.util.ArrayList, java.util.HashMap, java.util.HashSet,
java.util.LinkedHashMap, java.util.LinkedHashSet, java.util.TreeMap, java.util.TreeSet"/>
</module>
<module name="IllegalType"/>
<module name="IllegalImport">
<property name="illegalClasses" value="jadx.core.utils.DebugUtils"/>
</module>
@@ -131,8 +128,7 @@
<property name="id" value="printstacktrace"/>
<property name="format" value="\.printStackTrace\(\)"/>
<property name="ignoreComments" value="true"/>
<property name="message"
value="Using Throwable.printStackTrace() is forbidden. Use logger to print exception"/>
<property name="message" value="Using Throwable.printStackTrace() is forbidden. Use logger to print exception"/>
</module>
</module>
+4 -2
View File
@@ -1,7 +1,7 @@
/*
* Generated on 11/22/21, 8:58 PM
*/
package jadx.gui.ui.codearea;
package jadx.gui.ui.codeearea;
import java.io.*;
import javax.swing.text.Segment;
@@ -9,7 +9,7 @@ import javax.swing.text.Segment;
import org.fife.ui.rsyntaxtextarea.*;
/*
/**
* 用于Smali代码高亮
* MartinKay@qq.com
*/
@@ -173,6 +173,7 @@ import org.fife.ui.rsyntaxtextarea.*;
zzAtEOF = false;
}
%}
Letter = [A-Za-z]
@@ -677,3 +678,4 @@ FLAG_ARRAY = (":array_"{SimpleName})
\n { addToken(start,zzStartRead-1, Token.COMMENT_EOL); addNullToken(); return firstToken; }
<<EOF>> { addToken(start,zzStartRead-1, Token.COMMENT_EOL); addNullToken(); return firstToken; }
}
+13 -16
View File
@@ -57,12 +57,12 @@
private int yychar;
/**
* the number of characters from the last newline up to the start of the
* the number of characters from the last newline up to the start of the
* matched text
*/
private int yycolumn;
/**
/**
* zzAtBOL == true <=> the scanner is currently at the beginning of a line
*/
private boolean zzAtBOL = true;
@@ -102,9 +102,6 @@
zzLexicalState = newState;
}
public final int yystate() {
return zzLexicalState;
}
/**
* Returns the text matched by the current regular expression.
@@ -115,12 +112,12 @@
/**
* Returns the character at position <tt>pos</tt> from the
* matched text.
*
* Returns the character at position <tt>pos</tt> from the
* matched text.
*
* It is equivalent to yytext().charAt(pos), but faster
*
* @param pos the position of the character to fetch.
* @param pos the position of the character to fetch.
* A value from 0 to yylength()-1.
*
* @return the character at position pos
@@ -141,8 +138,8 @@
/**
* Reports an error that occured while scanning.
*
* In a wellformed scanner (no or only correct usage of
* yypushback(int) and a match-all fallback rule) this method
* In a wellformed scanner (no or only correct usage of
* yypushback(int) and a match-all fallback rule) this method
* will only be called with things that "Can't Possibly Happen".
* If this method is called, something is seriously wrong
* (e.g. a JFlex bug producing a faulty scanner etc.).
@@ -162,7 +159,7 @@
}
--- throws clause
}
}
/**
@@ -209,12 +206,12 @@
zzAction = -1;
zzCurrentPosL = zzCurrentPos = zzStartRead = zzMarkedPosL;
--- start admin (lexstate etc)
zzForAction: {
while (true) {
--- next input, line, col, char count, next transition, isFinal action
zzAction = zzState;
zzMarkedPosL = zzCurrentPosL;
@@ -229,11 +226,11 @@
--- char count update
--- actions
default:
default:
if (zzInput == YYEOF && zzStartRead == zzCurrentPos) {
zzAtEOF = true;
--- eofvalue
}
}
else {
--- no match
}
Binary file not shown.
+2 -2
View File
@@ -1,7 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=31c55713e40233a8303827ceb42ca48a47267a0ad4bab9177123121e71524c26
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
distributionSha256Sum=544c35d6bd849ae8a5ed0bcea39ba677dc40f49df7d1835561582da2009b961d
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Vendored
+2 -5
View File
@@ -15,8 +15,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
@@ -57,7 +55,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -86,8 +84,7 @@ done
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
' "$PWD" ) || exit
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
Vendored
-2
View File
@@ -13,8 +13,6 @@
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
+3 -5
View File
@@ -1,16 +1,14 @@
plugins {
id("jadx-java")
id("jadx-library")
id("application")
// use shadow only for application scripts, jar will be copied from jadx-gui
id("com.gradleup.shadow") version "8.3.5"
id("com.github.johnrengelman.shadow") version "8.1.1"
}
dependencies {
implementation(project(":jadx-core"))
implementation(project(":jadx-plugins-tools"))
implementation(project(":jadx-commons:jadx-app-commons"))
runtimeOnly(project(":jadx-plugins:jadx-dex-input"))
runtimeOnly(project(":jadx-plugins:jadx-java-input"))
@@ -22,8 +20,8 @@ dependencies {
runtimeOnly(project(":jadx-plugins:jadx-xapk-input"))
runtimeOnly(project(":jadx-plugins:jadx-aab-input"))
implementation("org.jcommander:jcommander:2.0")
implementation("ch.qos.logback:logback-classic:1.5.12")
implementation("org.jcommander:jcommander:1.83")
implementation("ch.qos.logback:logback-classic:1.5.6")
}
application {
@@ -4,7 +4,7 @@ import java.io.PrintStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -13,7 +13,6 @@ import java.util.function.Supplier;
import org.jetbrains.annotations.Nullable;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.ParameterDescription;
import com.beust.jcommander.ParameterException;
import com.beust.jcommander.Parameterized;
@@ -134,16 +133,14 @@ public class JCommanderWrapper<T> {
out.println("options:");
List<ParameterDescription> params = jc.getParameters();
Map<String, ParameterDescription> paramsMap = new HashMap<>(params.size());
Map<String, ParameterDescription> paramsMap = new LinkedHashMap<>(params.size());
int maxNamesLen = 0;
for (ParameterDescription p : params) {
paramsMap.put(p.getParameterized().getName(), p);
int len = p.getNames().length();
String valueDesc = getValueDesc(p);
if (valueDesc != null) {
len += 1 + valueDesc.length();
if (len > maxNamesLen) {
maxNamesLen = len;
}
maxNamesLen = Math.max(maxNamesLen, len);
}
maxNamesLen += 3;
@@ -156,12 +153,8 @@ public class JCommanderWrapper<T> {
}
StringBuilder opt = new StringBuilder();
opt.append(" ").append(p.getNames());
String valueDesc = getValueDesc(p);
if (valueDesc != null) {
opt.append(' ').append(valueDesc);
}
addSpaces(opt, maxNamesLen - opt.length());
String description = p.getDescription();
addSpaces(opt, maxNamesLen - opt.length());
if (description.contains("\n")) {
String[] lines = description.split("\n");
opt.append("- ").append(lines[0]);
@@ -184,11 +177,6 @@ public class JCommanderWrapper<T> {
return maxNamesLen;
}
private static @Nullable String getValueDesc(ParameterDescription p) {
Parameter parameterAnnotation = p.getParameterAnnotation();
return parameterAnnotation == null ? null : parameterAnnotation.defaultValueDescription();
}
/**
* Get all declared fields of the specified class and all super classes
*/
@@ -240,7 +228,7 @@ public class JCommanderWrapper<T> {
for (PluginContext context : pluginManager.getAllPluginContexts()) {
JadxPluginOptions options = context.getOptions();
if (options != null) {
if (appendPlugin(context.getPluginInfo(), context.getOptions(), sb, maxNamesLen)) {
if (appendPlugin(context.getPluginInfo(), context.getOptions(), sb, maxNamesLen, k)) {
k++;
}
}
@@ -252,12 +240,12 @@ public class JCommanderWrapper<T> {
return "\nPlugin options (-P<name>=<value>):" + sb;
}
private boolean appendPlugin(JadxPluginInfo pluginInfo, JadxPluginOptions options, StringBuilder out, int maxNamesLen) {
private boolean appendPlugin(JadxPluginInfo pluginInfo, JadxPluginOptions options, StringBuilder out, int maxNamesLen, int k) {
List<OptionDescription> descs = options.getOptionsDescriptions();
if (descs.isEmpty()) {
return false;
}
out.append("\n ");
out.append("\n ").append(k).append(") ");
out.append(pluginInfo.getPluginId()).append(": ").append(pluginInfo.getDescription());
for (OptionDescription desc : descs) {
StringBuilder opt = new StringBuilder();
+16 -40
View File
@@ -1,7 +1,5 @@
package jadx.cli;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -10,40 +8,36 @@ import jadx.api.JadxDecompiler;
import jadx.api.impl.AnnotatedCodeWriter;
import jadx.api.impl.NoOpCodeCache;
import jadx.api.impl.SimpleCodeWriter;
import jadx.api.security.JadxSecurityFlag;
import jadx.api.security.impl.JadxSecurity;
import jadx.cli.LogHelper.LogLevelEnum;
import jadx.cli.plugins.JadxFilesGetter;
import jadx.commons.app.JadxCommonEnv;
import jadx.core.utils.exceptions.JadxArgsValidateException;
import jadx.core.utils.files.FileUtils;
import jadx.plugins.tools.JadxExternalPluginsLoader;
public class JadxCLI {
private static final Logger LOG = LoggerFactory.getLogger(JadxCLI.class);
public static void main(String[] args) {
int result = 1;
int result = 0;
try {
result = execute(args);
} catch (JadxArgsValidateException e) {
LOG.error("Incorrect arguments: {}", e.getMessage());
result = 1;
} catch (Throwable e) {
LOG.error("Process error:", e);
result = 1;
} finally {
FileUtils.deleteTempRootDir();
System.exit(result);
}
}
public static int execute(String[] args) {
try {
JadxCLIArgs jadxArgs = new JadxCLIArgs();
if (jadxArgs.processArgs(args)) {
return processAndSave(jadxArgs);
}
return 0;
} catch (JadxArgsValidateException e) {
LOG.error("Incorrect arguments: {}", e.getMessage());
return 1;
} catch (Throwable e) {
LOG.error("Process error:", e);
return 1;
JadxCLIArgs jadxArgs = new JadxCLIArgs();
if (jadxArgs.processArgs(args)) {
return processAndSave(jadxArgs);
}
return 0;
}
private static int processAndSave(JadxCLIArgs cliArgs) {
@@ -52,9 +46,7 @@ public class JadxCLI {
JadxArgs jadxArgs = cliArgs.toJadxArgs();
jadxArgs.setCodeCache(new NoOpCodeCache());
jadxArgs.setPluginLoader(new JadxExternalPluginsLoader());
jadxArgs.setFilesGetter(JadxFilesGetter.INSTANCE);
initCodeWriterProvider(jadxArgs);
applyEnvVars(jadxArgs);
try (JadxDecompiler jadx = new JadxDecompiler(jadxArgs)) {
jadx.load();
if (checkForErrors(jadx)) {
@@ -68,11 +60,11 @@ public class JadxCLI {
if (errorsCount != 0) {
jadx.printErrorsReport();
LOG.error("finished with errors, count: {}", errorsCount);
return 1;
} else {
LOG.info("done");
}
LOG.info("done");
return 0;
}
return 0;
}
private static void initCodeWriterProvider(JadxArgs jadxArgs) {
@@ -87,22 +79,6 @@ public class JadxCLI {
}
}
private static void applyEnvVars(JadxArgs jadxArgs) {
Set<JadxSecurityFlag> flags = JadxSecurityFlag.all();
boolean modified = false;
boolean disableXmlSecurity = JadxCommonEnv.getBool("JADX_DISABLE_XML_SECURITY", false);
if (disableXmlSecurity) {
flags.remove(JadxSecurityFlag.SECURE_XML_PARSER);
// TODO: not related to 'xml security', but kept for compatibility
flags.remove(JadxSecurityFlag.VERIFY_APP_PACKAGE);
modified = true;
}
// TODO: migrate 'ZipSecurity'
if (modified) {
jadxArgs.setSecurity(new JadxSecurity(flags));
}
}
private static boolean checkForErrors(JadxDecompiler jadx) {
if (jadx.getRoot().getClasses().isEmpty()) {
if (jadx.getArgs().isSkipResources()) {
@@ -27,7 +27,6 @@ import jadx.api.JadxDecompiler;
import jadx.api.args.GeneratedRenamesMappingFileMode;
import jadx.api.args.IntegerFormat;
import jadx.api.args.ResourceNameSource;
import jadx.api.args.UseSourceNameAsClassNameAlias;
import jadx.api.args.UserRenamesMappingsMode;
import jadx.core.deobf.conditions.DeobfWhitelist;
import jadx.core.utils.exceptions.JadxArgsValidateException;
@@ -109,9 +108,6 @@ public class JadxCLIArgs {
@Parameter(names = "--no-finally", description = "don't extract finally block")
protected boolean extractFinally = true;
@Parameter(names = "--no-restore-switch-over-string", description = "don't restore switch over string")
protected boolean restoreSwitchOverString = true;
@Parameter(names = "--no-replace-consts", description = "don't replace constant value with matching constant field")
protected boolean replaceConsts = true;
@@ -170,15 +166,8 @@ public class JadxCLIArgs {
)
protected GeneratedRenamesMappingFileMode generatedRenamesMappingFileMode = GeneratedRenamesMappingFileMode.getDefault();
@SuppressWarnings("DeprecatedIsStillUsed")
@Parameter(
names = { "--deobf-use-sourcename" },
description = "use source file name as class name alias."
+ "\nDEPRECATED, use \"--use-source-name-as-class-name-alias\" instead",
hidden = true
)
@Deprecated
protected Boolean deobfuscationUseSourceNameAsAlias = null;
@Parameter(names = { "--deobf-use-sourcename" }, description = "use source file name as class name alias")
protected boolean deobfuscationUseSourceNameAsAlias = false;
@Parameter(
names = { "--deobf-res-name-source" },
@@ -190,16 +179,6 @@ public class JadxCLIArgs {
)
protected ResourceNameSource resourceNameSource = ResourceNameSource.AUTO;
@Parameter(
names = { "--use-source-name-as-class-name-alias" },
description = "use source name as class name alias:"
+ "\n 'always' - always use source name if it's available"
+ "\n 'if-better' - use source name if it seems better than the current one"
+ "\n 'never' - never use source name, even if it's available",
converter = UseSourceNameAsClassNameConverter.class
)
protected UseSourceNameAsClassNameAlias useSourceNameAsClassNameAlias = null;
@Parameter(
names = { "--use-kotlin-methods-for-var-names" },
description = "use kotlin intrinsic methods to rename variables, values: disable, apply, apply-and-hide",
@@ -264,9 +243,6 @@ public class JadxCLIArgs {
@Parameter(names = { "-q", "--quiet" }, description = "turn off output (set --log-level to QUIET)")
protected boolean quiet = false;
@Parameter(names = { "--disable-plugins" }, description = "comma separated list of plugin ids to disable")
protected String disablePlugins = "";
@Parameter(names = { "--version" }, description = "print jadx version")
protected boolean printVersion = false;
@@ -351,7 +327,7 @@ public class JadxCLIArgs {
args.setDeobfuscationMinLength(deobfuscationMinLength);
args.setDeobfuscationMaxLength(deobfuscationMaxLength);
args.setDeobfuscationWhitelist(Arrays.asList(deobfuscationWhitelistStr.split(" ")));
args.setUseSourceNameAsClassNameAlias(getUseSourceNameAsClassNameAlias());
args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias);
args.setUseKotlinMethodsForVarNames(useKotlinMethodsForVarNames);
args.setResourceNameSource(resourceNameSource);
args.setEscapeUnicode(escapeUnicode);
@@ -366,14 +342,12 @@ public class JadxCLIArgs {
args.setMoveInnerClasses(moveInnerClasses);
args.setAllowInlineKotlinLambda(allowInlineKotlinLambda);
args.setExtractFinally(extractFinally);
args.setRestoreSwitchOverString(restoreSwitchOverString);
args.setRenameFlags(renameFlags);
args.setFsCaseSensitive(fsCaseSensitive);
args.setCommentsLevel(commentsLevel);
args.setIntegerFormat(integerFormat);
args.setUseDxInput(useDx);
args.setPluginOptions(pluginOptions);
args.setDisabledPlugins(Arrays.stream(disablePlugins.split(",")).map(String::trim).collect(Collectors.toSet()));
return args;
}
@@ -461,10 +435,6 @@ public class JadxCLIArgs {
return extractFinally;
}
public boolean isRestoreSwitchOverString() {
return restoreSwitchOverString;
}
public Path getUserRenamesMappingsPath() {
return userRenamesMappingsPath;
}
@@ -497,23 +467,8 @@ public class JadxCLIArgs {
return generatedRenamesMappingFileMode;
}
public UseSourceNameAsClassNameAlias getUseSourceNameAsClassNameAlias() {
if (useSourceNameAsClassNameAlias != null) {
return useSourceNameAsClassNameAlias;
} else if (deobfuscationUseSourceNameAsAlias != null) {
// noinspection deprecation
return UseSourceNameAsClassNameAlias.create(deobfuscationUseSourceNameAsAlias);
} else {
return UseSourceNameAsClassNameAlias.getDefault();
}
}
/**
* @deprecated Use {@link #getUseSourceNameAsClassNameAlias()} instead.
*/
@Deprecated
public boolean isDeobfuscationUseSourceNameAsAlias() {
return getUseSourceNameAsClassNameAlias().toBoolean();
return deobfuscationUseSourceNameAsAlias;
}
public ResourceNameSource getResourceNameSource() {
@@ -584,10 +539,6 @@ public class JadxCLIArgs {
return pluginOptions;
}
public String getDisablePlugins() {
return disablePlugins;
}
static class RenameConverter implements IStringConverter<Set<RenameEnum>> {
private final String paramName;
@@ -641,12 +592,6 @@ public class JadxCLIArgs {
}
}
public static class UseSourceNameAsClassNameConverter extends BaseEnumConverter<UseSourceNameAsClassNameAlias> {
public UseSourceNameAsClassNameConverter() {
super(UseSourceNameAsClassNameAlias::valueOf, UseSourceNameAsClassNameAlias::values);
}
}
public static class DecompilationModeConverter extends BaseEnumConverter<DecompilationMode> {
public DecompilationModeConverter() {
super(DecompilationMode::valueOf, DecompilationMode::values);
@@ -1,7 +1,7 @@
package jadx.cli;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;
import com.beust.jcommander.JCommander;
@@ -10,7 +10,7 @@ import jadx.cli.commands.ICommand;
import jadx.core.utils.exceptions.JadxArgsValidateException;
public class JadxCLICommands {
private static final Map<String, ICommand> COMMANDS_MAP = new LinkedHashMap<>();
private static final Map<String, ICommand> COMMANDS_MAP = new TreeMap<>();
static {
JadxCLICommands.register(new CommandPlugins());
@@ -12,7 +12,6 @@ import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs;
import jadx.api.JadxDecompiler;
import jadx.api.args.UseSourceNameAsClassNameAlias;
import jadx.core.clsp.ClsSet;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.files.FileUtils;
@@ -48,7 +47,7 @@ public class ConvertToClsSet {
// disable not needed passes executed at prepare stage
jadxArgs.setDeobfuscationOn(false);
jadxArgs.setRenameFlags(EnumSet.noneOf(JadxArgs.RenameEnum.class));
jadxArgs.setUseSourceNameAsClassNameAlias(UseSourceNameAsClassNameAlias.NEVER);
jadxArgs.setUseSourceNameAsClassAlias(false);
jadxArgs.setMoveInnerClasses(false);
jadxArgs.setInlineAnonymousClasses(false);
jadxArgs.setInlineMethods(false);
@@ -1,18 +1,12 @@
package jadx.cli.commands;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import jadx.api.plugins.JadxPluginInfo;
import jadx.cli.JCommanderWrapper;
import jadx.cli.LogHelper;
import jadx.core.utils.StringUtils;
import jadx.plugins.tools.JadxPluginsList;
import jadx.plugins.tools.JadxPluginsTools;
import jadx.plugins.tools.data.JadxPluginMetadata;
@@ -21,40 +15,24 @@ import jadx.plugins.tools.data.JadxPluginUpdate;
@Parameters(commandDescription = "manage jadx plugins")
public class CommandPlugins implements ICommand {
@Parameter(names = { "-i", "--install" }, description = "install plugin with locationId", defaultValueDescription = "<locationId>")
@Parameter(names = { "-i", "--install" }, description = "install plugin with locationId")
protected String install;
@Parameter(names = { "-j", "--install-jar" }, description = "install plugin from jar file", defaultValueDescription = "<path-to.jar>")
@Parameter(names = { "-j", "--install-jar" }, description = "install plugin from jar file")
protected String installJar;
@Parameter(names = { "-l", "--list" }, description = "list installed plugins")
protected boolean list;
@Parameter(names = { "-a", "--available" }, description = "list available plugins from jadx-plugins-list (aka marketplace)")
@Parameter(names = { "-a", "--available" }, description = "list available plugins")
protected boolean available;
@Parameter(names = { "-u", "--update" }, description = "update installed plugins")
protected boolean update;
@Parameter(names = { "--uninstall" }, description = "uninstall plugin with pluginId", defaultValueDescription = "<pluginId>")
@Parameter(names = { "--uninstall" }, description = "uninstall plugin with pluginId")
protected String uninstall;
@Parameter(names = { "--disable" }, description = "disable plugin with pluginId", defaultValueDescription = "<pluginId>")
protected String disable;
@Parameter(names = { "--enable" }, description = "enable plugin with pluginId", defaultValueDescription = "<pluginId>")
protected String enable;
@Parameter(names = { "--list-all" }, description = "list all plugins including bundled and dropins")
protected boolean listAll;
@Parameter(
names = { "--list-versions" },
description = "fetch latest versions of plugin from locationId (will download all artefacts, limited to 10)",
defaultValueDescription = "<locationId>"
)
protected String listVersions;
@Parameter(names = { "-h", "--help" }, description = "print this help", help = true)
protected boolean printHelp = false;
@@ -63,33 +41,21 @@ public class CommandPlugins implements ICommand {
return "plugins";
}
@SuppressWarnings("UnnecessaryReturnStatement")
@Override
public void process(JCommanderWrapper<?> jcw, JCommander subCommander) {
if (printHelp) {
jcw.printUsage(subCommander);
return;
}
Set<String> unknownOptions = new HashSet<>(subCommander.getUnknownOptions());
boolean verbose = unknownOptions.remove("-v") || unknownOptions.remove("--verbose");
LogHelper.setLogLevel(verbose ? LogHelper.LogLevelEnum.DEBUG : LogHelper.LogLevelEnum.INFO);
if (!unknownOptions.isEmpty()) {
System.out.println("Error: found unknown options: " + unknownOptions);
}
if (install != null) {
installPlugin(install);
return;
}
if (installJar != null) {
installPlugin("file:" + installJar);
return;
}
if (uninstall != null) {
boolean uninstalled = JadxPluginsTools.getInstance().uninstall(uninstall);
System.out.println(uninstalled ? "Uninstalled" : "Plugin not found");
return;
}
if (update) {
List<JadxPluginUpdate> updates = JadxPluginsTools.getInstance().updateAll();
@@ -101,99 +67,27 @@ public class CommandPlugins implements ICommand {
System.out.println(" " + update.getPluginId() + ": " + update.getOldVersion() + " -> " + update.getNewVersion());
}
}
return;
}
if (list) {
printPlugins(JadxPluginsTools.getInstance().getInstalled());
return;
}
if (listAll) {
printAllPlugins();
return;
}
if (listVersions != null) {
printVersions(listVersions, 10);
return;
List<JadxPluginMetadata> installed = JadxPluginsTools.getInstance().getInstalled();
System.out.println("Installed plugins: " + installed.size());
int i = 1;
for (JadxPluginMetadata plugin : installed) {
System.out.println(" " + (i++) + ") "
+ plugin.getPluginId() + " (" + plugin.getVersion() + ") - "
+ plugin.getName() + ": " + plugin.getDescription());
}
}
if (available) {
List<JadxPluginMetadata> availableList = JadxPluginsList.getInstance().get();
System.out.println("Available plugins: " + availableList.size());
int i = 1;
for (JadxPluginMetadata plugin : availableList) {
System.out.println(" - " + plugin.getName() + ": " + plugin.getDescription()
System.out.println(" " + (i++) + ") "
+ plugin.getName() + ": " + plugin.getDescription()
+ " (" + plugin.getLocationId() + ")");
}
return;
}
if (disable != null) {
if (JadxPluginsTools.getInstance().changeDisabledStatus(disable, true)) {
System.out.println("Plugin '" + disable + "' disabled.");
} else {
System.out.println("Plugin '" + disable + "' already disabled.");
}
return;
}
if (enable != null) {
if (JadxPluginsTools.getInstance().changeDisabledStatus(enable, false)) {
System.out.println("Plugin '" + enable + "' enabled.");
} else {
System.out.println("Plugin '" + enable + "' already enabled.");
}
return;
}
}
private static void printPlugins(List<JadxPluginMetadata> installed) {
System.out.println("Installed plugins: " + installed.size());
for (JadxPluginMetadata plugin : installed) {
StringBuilder sb = new StringBuilder();
sb.append(" - ").append(plugin.getPluginId());
String version = plugin.getVersion();
if (version != null) {
sb.append(" (").append(version).append(')');
}
if (plugin.isDisabled()) {
sb.append(" (disabled)");
}
sb.append(" - ").append(plugin.getName());
sb.append(": ").append(plugin.getDescription());
System.out.println(sb);
}
}
private void printVersions(String locationId, int limit) {
System.out.println("Loading ...");
List<JadxPluginMetadata> versions = JadxPluginsTools.getInstance().getVersionsByLocation(locationId, 1, limit);
if (versions.isEmpty()) {
System.out.println("No versions found");
return;
}
JadxPluginMetadata plugin = versions.get(0);
System.out.println("Versions for plugin id: " + plugin.getPluginId());
for (JadxPluginMetadata version : versions) {
StringBuilder sb = new StringBuilder();
sb.append(" - ").append(version.getVersion());
String reqVer = version.getRequiredJadxVersion();
if (StringUtils.notBlank(reqVer)) {
sb.append(", require jadx: ").append(reqVer);
}
System.out.println(sb);
}
}
private static void printAllPlugins() {
List<JadxPluginMetadata> installed = JadxPluginsTools.getInstance().getInstalled();
printPlugins(installed);
Set<String> installedSet = installed.stream().map(JadxPluginMetadata::getPluginId).collect(Collectors.toSet());
List<JadxPluginInfo> plugins = JadxPluginsTools.getInstance().getAllPluginsInfo();
System.out.println("Other plugins: " + plugins.size());
for (JadxPluginInfo plugin : plugins) {
if (!installedSet.contains(plugin.getPluginId())) {
System.out.println(" - " + plugin.getPluginId()
+ " - " + plugin.getName()
+ ": " + plugin.getDescription());
}
}
}
@@ -1,31 +0,0 @@
package jadx.cli.plugins;
import java.nio.file.Path;
import jadx.commons.app.JadxCommonFiles;
import jadx.commons.app.JadxTempFiles;
import jadx.core.plugins.files.IJadxFilesGetter;
public class JadxFilesGetter implements IJadxFilesGetter {
public static final JadxFilesGetter INSTANCE = new JadxFilesGetter();
@Override
public Path getConfigDir() {
return JadxCommonFiles.getConfigDir();
}
@Override
public Path getCacheDir() {
return JadxCommonFiles.getCacheDir();
}
@Override
public Path getTempDir() {
return JadxTempFiles.getTempRootDir();
}
private JadxFilesGetter() {
// singleton
}
}
@@ -12,6 +12,7 @@ import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -19,11 +20,8 @@ import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.android.TextResMapFile;
import jadx.core.utils.files.ZipFile;
import jadx.core.xmlgen.ResTableBinaryParser;
import static jadx.core.utils.files.FileUtils.expandDirs;
/**
* Utility class for convert '.arsc' to simple text file with mapping id to resource name
*/
@@ -32,7 +30,7 @@ public class ConvertArscFile {
private static int rewritesCount;
public static void usage() {
LOG.info("<res-map file> <input .arsc/android.jar files or dir>");
LOG.info("<res-map file> <input .arsc files>");
LOG.info("");
LOG.info("Note: If res-map already exists - it will be merged and updated");
}
@@ -44,7 +42,6 @@ public class ConvertArscFile {
}
List<Path> inputPaths = Stream.of(args).map(Paths::get).collect(Collectors.toList());
Path resMapFile = inputPaths.remove(0);
List<Path> inputResFiles = filterAndSort(expandDirs(inputPaths));
Map<Integer, String> resMap;
if (Files.isReadable(resMapFile)) {
resMap = TextResMapFile.read(resMapFile);
@@ -55,7 +52,8 @@ public class ConvertArscFile {
RootNode root = new RootNode(new JadxArgs()); // not really needed
rewritesCount = 0;
for (Path resFile : inputResFiles) {
for (Path resFile : inputPaths) {
LOG.info("Processing {}", resFile);
ResTableBinaryParser resTableParser = new ResTableBinaryParser(root, true);
if (resFile.getFileName().toString().endsWith(".jar")) {
// Load resources.arsc from android.jar
@@ -86,16 +84,6 @@ public class ConvertArscFile {
LOG.info("done");
}
private static List<Path> filterAndSort(List<Path> inputPaths) {
return inputPaths.stream()
.filter(p -> {
String fileName = p.getFileName().toString();
return fileName.endsWith(".arsc") || fileName.endsWith(".jar");
})
.sorted()
.collect(Collectors.toList());
}
private static void mergeResMaps(Map<Integer, String> mainResMap, Map<Integer, String> newResMap) {
for (Map.Entry<Integer, String> entry : newResMap.entrySet()) {
Integer id = entry.getKey();
@@ -3,12 +3,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.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
public class JadxCLIArgsTest {
@@ -16,38 +18,38 @@ public class JadxCLIArgsTest {
@Test
public void testInvertedBooleanOption() {
assertThat(parse("--no-replace-consts").isReplaceConsts()).isFalse();
assertThat(parse("").isReplaceConsts()).isTrue();
assertThat(parse("--no-replace-consts").isReplaceConsts(), is(false));
assertThat(parse("").isReplaceConsts(), is(true));
}
@Test
public void testEscapeUnicodeOption() {
assertThat(parse("--escape-unicode").isEscapeUnicode()).isTrue();
assertThat(parse("").isEscapeUnicode()).isFalse();
assertThat(parse("--escape-unicode").isEscapeUnicode(), is(true));
assertThat(parse("").isEscapeUnicode(), is(false));
}
@Test
public void testSrcOption() {
assertThat(parse("--no-src").isSkipSources()).isTrue();
assertThat(parse("-s").isSkipSources()).isTrue();
assertThat(parse("").isSkipSources()).isFalse();
assertThat(parse("--no-src").isSkipSources(), is(true));
assertThat(parse("-s").isSkipSources(), is(true));
assertThat(parse("").isSkipSources(), is(false));
}
@Test
public void testOptionsOverride() {
assertThat(override(new JadxCLIArgs(), "--no-imports").isUseImports()).isFalse();
assertThat(override(new JadxCLIArgs(), "--no-debug-info").isDebugInfo()).isFalse();
assertThat(override(new JadxCLIArgs(), "").isUseImports()).isTrue();
assertThat(override(new JadxCLIArgs(), "--no-imports").isUseImports(), is(false));
assertThat(override(new JadxCLIArgs(), "--no-debug-info").isDebugInfo(), is(false));
assertThat(override(new JadxCLIArgs(), "").isUseImports(), is(true));
JadxCLIArgs args = new JadxCLIArgs();
args.useImports = false;
assertThat(override(args, "--no-imports").isUseImports()).isFalse();
assertThat(override(args, "--no-imports").isUseImports(), is(false));
args.debugInfo = false;
assertThat(override(args, "--no-debug-info").isDebugInfo()).isFalse();
assertThat(override(args, "--no-debug-info").isDebugInfo(), is(false));
args = new JadxCLIArgs();
args.useImports = false;
assertThat(override(args, "").isUseImports()).isFalse();
assertThat(override(args, "").isUseImports(), is(false));
}
@Test
@@ -81,7 +83,7 @@ public class JadxCLIArgsTest {
JadxCLIArgs args = new JadxCLIArgs();
args.pluginOptions = baseMap;
Map<String, String> resultMap = override(args, providedArgs).getPluginOptions();
assertThat(resultMap).isEqualTo(expectedMap);
assertThat(resultMap, Matchers.equalTo(expectedMap));
}
private JadxCLIArgs parse(String... args) {
@@ -89,15 +91,15 @@ public class JadxCLIArgsTest {
}
private JadxCLIArgs parse(JadxCLIArgs jadxArgs, String... args) {
return check(jadxArgs, jadxArgs.processArgs(args));
boolean res = jadxArgs.processArgs(args);
assertThat(res, is(true));
LOG.info("Jadx args: {}", jadxArgs.toJadxArgs());
return jadxArgs;
}
private JadxCLIArgs override(JadxCLIArgs jadxArgs, String... args) {
return check(jadxArgs, jadxArgs.overrideProvided(args));
}
private static JadxCLIArgs check(JadxCLIArgs jadxArgs, boolean res) {
assertThat(res).isTrue();
boolean res = jadxArgs.overrideProvided(args);
assertThat(res, is(true));
LOG.info("Jadx args: {}", jadxArgs.toJadxArgs());
return jadxArgs;
}
@@ -9,8 +9,9 @@ import jadx.api.JadxArgs.RenameEnum;
import jadx.cli.JadxCLIArgs.RenameConverter;
import jadx.core.utils.exceptions.JadxArgsValidateException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class RenameConverterTest {
@@ -24,16 +25,16 @@ public class RenameConverterTest {
@Test
public void all() {
Set<RenameEnum> set = converter.convert("all");
assertThat(set).hasSize(3);
assertThat(set).contains(RenameEnum.CASE);
assertThat(set).contains(RenameEnum.VALID);
assertThat(set).contains(RenameEnum.PRINTABLE);
assertEquals(3, set.size());
assertTrue(set.contains(RenameEnum.CASE));
assertTrue(set.contains(RenameEnum.VALID));
assertTrue(set.contains(RenameEnum.PRINTABLE));
}
@Test
public void none() {
Set<RenameEnum> set = converter.convert("none");
assertThat(set).isEmpty();
assertTrue(set.isEmpty());
}
@Test
@@ -42,6 +43,7 @@ public class RenameConverterTest {
() -> converter.convert("wrong"),
"Expected convert() to throw, but it didn't");
assertThat(thrown.getMessage()).isEqualTo("'wrong' is unknown for parameter someParam, possible values are case, valid, printable");
assertEquals("'wrong' is unknown for parameter someParam, possible values are case, valid, printable",
thrown.getMessage());
}
}
+53 -88
View File
@@ -12,111 +12,71 @@ import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.assertj.core.api.Condition;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.utils.files.FileUtils;
import static org.assertj.core.api.Assertions.assertThat;
public class TestInput {
private static final Logger LOG = LoggerFactory.getLogger(TestInput.class);
private static final PathMatcher LOG_ALL_FILES = path -> {
LOG.debug("File in result dir: {}", path);
return true;
};
@TempDir
Path testDir;
@Test
public void testHelp() {
int result = JadxCLI.execute(new String[] { "--help" });
assertThat(result).isEqualTo(0);
}
@Test
public void testApkInput() throws Exception {
int result = JadxCLI.execute(buildArgs(List.of(), "samples/small.apk"));
assertThat(result).isEqualTo(0);
List<Path> resultFiles = collectAllFilesInDir(testDir);
printFiles(resultFiles);
assertThat(resultFiles)
.describedAs("check output files")
.map(p -> p.getFileName().toString())
.haveExactly(2, new Condition<>(f -> f.endsWith(".java"), "java classes"))
.haveExactly(9, new Condition<>(f -> f.endsWith(".xml"), "xml resources"))
.haveExactly(1, new Condition<>(f -> f.equals("classes.dex"), "dex"))
.haveExactly(1, new Condition<>(f -> f.equals("AndroidManifest.xml"), "manifest"))
.hasSize(13);
}
@Test
public void testDexInput() throws Exception {
decompile("samples/hello.dex");
decompile("dex", "samples/hello.dex");
}
@Test
public void testSmaliInput() throws Exception {
decompile("samples/HelloWorld.smali");
decompile("smali", "samples/HelloWorld.smali");
}
@Test
public void testClassInput() throws Exception {
decompile("samples/HelloWorld.class");
decompile("class", "samples/HelloWorld.class");
}
@Test
public void testMultipleInput() throws Exception {
decompile("samples/hello.dex", "samples/HelloWorld.smali");
}
@Test
public void testFallbackMode() throws Exception {
int result = JadxCLI.execute(buildArgs(List.of("-f"), "samples/hello.dex"));
assertThat(result).isEqualTo(0);
List<Path> files = collectJavaFilesInDir(testDir);
assertThat(files).hasSize(1);
}
@Test
public void testSimpleMode() throws Exception {
int result = JadxCLI.execute(buildArgs(List.of("--decompilation-mode", "simple"), "samples/hello.dex"));
assertThat(result).isEqualTo(0);
List<Path> files = collectJavaFilesInDir(testDir);
assertThat(files).hasSize(1);
decompile("multi", "samples/hello.dex", "samples/HelloWorld.smali");
}
@Test
public void testResourceOnly() throws Exception {
int result = JadxCLI.execute(buildArgs(List.of(), "samples/resources-only.apk"));
assertThat(result).isEqualTo(0);
List<Path> files = collectFilesInDir(testDir,
path -> path.getFileName().toString().equalsIgnoreCase("AndroidManifest.xml"));
assertThat(files).isNotEmpty();
decode("resourceOnly", "samples/resources-only.apk");
}
private void decompile(String... inputSamples) throws URISyntaxException, IOException {
int result = JadxCLI.execute(buildArgs(List.of(), inputSamples));
assertThat(result).isEqualTo(0);
List<Path> resultJavaFiles = collectJavaFilesInDir(testDir);
assertThat(resultJavaFiles).isNotEmpty();
// do not copy input files as resources
for (Path path : collectFilesInDir(testDir, LOG_ALL_FILES)) {
for (String inputSample : inputSamples) {
assertThat(path.toAbsolutePath().toString()).doesNotContain(inputSample);
}
}
}
private String[] buildArgs(List<String> options, String... inputSamples) throws URISyntaxException {
List<String> args = new ArrayList<>(options);
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(testDir.toAbsolutePath().toString());
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);
args.add("-v");
args.add("-d");
args.add(tempDir.toAbsolutePath().toString());
for (String inputSample : inputSamples) {
URL resource = getClass().getClassLoader().getResource(inputSample);
@@ -124,13 +84,21 @@ public class TestInput {
String sampleFile = resource.toURI().getRawPath();
args.add(sampleFile);
}
return args.toArray(new String[0]);
}
private void printFiles(List<Path> files) {
LOG.info("Output files (count: {}):", files.size());
for (Path file : files) {
LOG.info(" {}", testDir.relativize(file));
int result = JadxCLI.execute(args.toArray(new String[0]));
assertThat(result).isEqualTo(0);
List<Path> resultJavaFiles = collectJavaFilesInDir(tempDir);
assertThat(resultJavaFiles).isNotEmpty();
// do not copy input files as resources
PathMatcher logAllFiles = path -> {
LOG.debug("File in result dir: {}", path);
return true;
};
for (Path path : collectFilesInDir(tempDir, logAllFiles)) {
for (String inputSample : inputSamples) {
assertThat(path.toAbsolutePath().toString()).doesNotContain(inputSample);
}
}
}
@@ -139,14 +107,6 @@ public class TestInput {
return collectFilesInDir(dir, javaMatcher);
}
private static List<Path> collectAllFilesInDir(Path dir) throws IOException {
try (Stream<Path> pathStream = Files.walk(dir)) {
return pathStream
.filter(Files::isRegularFile)
.collect(Collectors.toList());
}
}
private static List<Path> collectFilesInDir(Path dir, PathMatcher matcher) throws IOException {
try (Stream<Path> pathStream = Files.walk(dir)) {
return pathStream
@@ -155,4 +115,9 @@ public class TestInput {
.collect(Collectors.toList());
}
}
@AfterAll
public static void cleanup() {
FileUtils.clearTempRootDir();
}
}
Binary file not shown.
@@ -3,7 +3,6 @@ package jadx.commons.app;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.function.Function;
import org.jetbrains.annotations.Nullable;
@@ -36,20 +35,20 @@ public class JadxCommonFiles {
public void init() {
try {
configDir = loadEnvDir("JADX_CONFIG_DIR", pd -> pd.configDir);
cacheDir = loadEnvDir("JADX_CACHE_DIR", pd -> pd.cacheDir);
configDir = loadEnvDir("JADX_CONFIG_DIR");
cacheDir = loadEnvDir("JADX_CACHE_DIR");
} catch (Exception e) {
throw new RuntimeException("Failed to init common directories", e);
}
}
private Path loadEnvDir(String envVar, Function<ProjectDirectories, String> dirFunc) throws IOException {
private Path loadEnvDir(String envVar) throws IOException {
String envDir = JadxCommonEnv.get(envVar, null);
String dirStr;
if (envDir != null) {
dirStr = envDir;
} else {
dirStr = dirFunc.apply(loadDirs());
dirStr = loadDirs().configDir;
}
Path path = Path.of(dirStr).toAbsolutePath();
Files.createDirectories(path);
@@ -1,31 +0,0 @@
package jadx.commons.app;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
public class JadxTempFiles {
private static final String JADX_TMP_INSTANCE_PREFIX = "jadx-instance-";
private static final Path TEMP_ROOT_DIR = createTempRootDir();
public static Path getTempRootDir() {
return TEMP_ROOT_DIR;
}
private static Path createTempRootDir() {
try {
String jadxTmpDir = System.getenv("JADX_TMP_DIR");
Path dir;
if (jadxTmpDir != null) {
dir = Files.createTempDirectory(Paths.get(jadxTmpDir), JADX_TMP_INSTANCE_PREFIX);
} else {
dir = Files.createTempDirectory(JADX_TMP_INSTANCE_PREFIX);
}
dir.toFile().deleteOnExit();
return dir;
} catch (Exception e) {
throw new RuntimeException("Failed to create temp root directory", e);
}
}
}
+3 -28
View File
@@ -5,9 +5,9 @@ plugins {
dependencies {
api(project(":jadx-plugins:jadx-input-api"))
implementation("com.google.code.gson:gson:2.11.0")
implementation("com.google.code.gson:gson:2.10.1")
testImplementation("org.apache.commons:commons-lang3:3.17.0")
testImplementation("org.apache.commons:commons-lang3:3.14.0")
testImplementation(project(":jadx-plugins:jadx-dex-input"))
testRuntimeOnly(project(":jadx-plugins:jadx-smali-input"))
@@ -24,31 +24,6 @@ dependencies {
testImplementation("tools.profiler:async-profiler:3.0")
}
val jadxTestJavaVersion = getTestJavaVersion()
fun getTestJavaVersion(): Int? {
val envVarName = "JADX_TEST_JAVA_VERSION"
val testJavaVer = System.getenv(envVarName)?.toInt() ?: return null
val currentJavaVer = java.toolchain.languageVersion.get().asInt()
if (testJavaVer < currentJavaVer) {
throw GradleException("'$envVarName' can't be set to lower version than $currentJavaVer")
}
println("Set Java toolchain for core tests to version '$testJavaVer'")
return testJavaVer
}
tasks.named<Test>("test") {
jadxTestJavaVersion?.let { testJavaVer ->
javaLauncher =
javaToolchains.launcherFor {
languageVersion = JavaLanguageVersion.of(testJavaVer)
}
}
// disable cache to allow test's rerun,
// because most tests are integration and depends on plugins and environment
outputs.cacheIf { false }
// exclude temp tests
tasks.test {
exclude("**/tmp/*")
}
+6 -92
View File
@@ -7,7 +7,6 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -22,7 +21,6 @@ import org.slf4j.LoggerFactory;
import jadx.api.args.GeneratedRenamesMappingFileMode;
import jadx.api.args.IntegerFormat;
import jadx.api.args.ResourceNameSource;
import jadx.api.args.UseSourceNameAsClassNameAlias;
import jadx.api.args.UserRenamesMappingsMode;
import jadx.api.data.ICodeData;
import jadx.api.deobf.IAliasProvider;
@@ -31,17 +29,12 @@ import jadx.api.impl.AnnotatedCodeWriter;
import jadx.api.impl.InMemoryCodeCache;
import jadx.api.plugins.loader.JadxBasePluginLoader;
import jadx.api.plugins.loader.JadxPluginLoader;
import jadx.api.security.IJadxSecurity;
import jadx.api.security.JadxSecurityFlag;
import jadx.api.security.impl.JadxSecurity;
import jadx.api.usage.IUsageInfoCache;
import jadx.api.usage.impl.InMemoryUsageInfoCache;
import jadx.core.deobf.DeobfAliasProvider;
import jadx.core.deobf.conditions.DeobfWhitelist;
import jadx.core.deobf.conditions.JadxRenameConditions;
import jadx.core.plugins.PluginContext;
import jadx.core.plugins.files.IJadxFilesGetter;
import jadx.core.plugins.files.TempFilesGetter;
import jadx.core.utils.files.FileUtils;
public class JadxArgs implements Closeable {
@@ -105,7 +98,7 @@ public class JadxArgs implements Closeable {
private UserRenamesMappingsMode userRenamesMappingsMode = UserRenamesMappingsMode.getDefault();
private boolean deobfuscationOn = false;
private UseSourceNameAsClassNameAlias useSourceNameAsClassNameAlias = UseSourceNameAsClassNameAlias.getDefault();
private boolean useSourceNameAsClassAlias = false;
private File generatedRenamesMappingFile = null;
private GeneratedRenamesMappingFileMode generatedRenamesMappingFileMode = GeneratedRenamesMappingFileMode.getDefault();
@@ -117,7 +110,7 @@ public class JadxArgs implements Closeable {
/**
* List of classes and packages (ends with '.*') to exclude from deobfuscation
*/
private List<String> deobfuscationWhitelist = DeobfWhitelist.DEFAULT_LIST;
private List<String> deobfuscationWhitelist = new ArrayList<>(DeobfWhitelist.DEFAULT_LIST);
/**
* Nodes alias provider for deobfuscator and rename visitor
@@ -134,8 +127,6 @@ public class JadxArgs implements Closeable {
private boolean respectBytecodeAccModifiers = false;
private boolean exportAsGradleProject = false;
private boolean restoreSwitchOverString = true;
private boolean skipXmlPrettyPrint = false;
private boolean fsCaseSensitive;
@@ -172,31 +163,13 @@ public class JadxArgs implements Closeable {
private UseKotlinMethodsForVarNames useKotlinMethodsForVarNames = UseKotlinMethodsForVarNames.APPLY;
/**
* Additional files structure info.
* Defaults to tmp dirs.
*/
private IJadxFilesGetter filesGetter = TempFilesGetter.INSTANCE;
/**
* Additional data validation and security checks
*/
private IJadxSecurity security = new JadxSecurity(JadxSecurityFlag.all());
/**
* Don't save files (can be using for performance testing)
*/
private boolean skipFilesSave = false;
/**
* Run additional expensive checks to verify internal invariants and info integrity
*/
private boolean runDebugChecks = false;
private Map<String, String> pluginOptions = new HashMap<>();
private Set<String> disabledPlugins = new HashSet<>();
private JadxPluginLoader pluginLoader = new JadxBasePluginLoader();
private boolean loadJadxClsSetFile = true;
@@ -452,29 +425,12 @@ public class JadxArgs implements Closeable {
this.generatedRenamesMappingFileMode = mode;
}
public UseSourceNameAsClassNameAlias getUseSourceNameAsClassNameAlias() {
return useSourceNameAsClassNameAlias;
}
public void setUseSourceNameAsClassNameAlias(UseSourceNameAsClassNameAlias useSourceNameAsClassNameAlias) {
this.useSourceNameAsClassNameAlias = useSourceNameAsClassNameAlias;
}
/**
* @deprecated Use {@link #getUseSourceNameAsClassNameAlias()} instead.
*/
@Deprecated
public boolean isUseSourceNameAsClassAlias() {
return getUseSourceNameAsClassNameAlias().toBoolean();
return useSourceNameAsClassAlias;
}
/**
* @deprecated Use {@link #setUseSourceNameAsClassNameAlias(UseSourceNameAsClassNameAlias)} instead.
*/
@Deprecated
public void setUseSourceNameAsClassAlias(boolean useSourceNameAsClassAlias) {
final var useSourceNameAsClassNameAlias = UseSourceNameAsClassNameAlias.create(useSourceNameAsClassAlias);
setUseSourceNameAsClassNameAlias(useSourceNameAsClassNameAlias);
this.useSourceNameAsClassAlias = useSourceNameAsClassAlias;
}
public int getDeobfuscationMinLength() {
@@ -565,14 +521,6 @@ public class JadxArgs implements Closeable {
this.exportAsGradleProject = exportAsGradleProject;
}
public boolean isRestoreSwitchOverString() {
return restoreSwitchOverString;
}
public void setRestoreSwitchOverString(boolean restoreSwitchOverString) {
this.restoreSwitchOverString = restoreSwitchOverString;
}
public boolean isSkipXmlPrettyPrint() {
return skipXmlPrettyPrint;
}
@@ -729,22 +677,6 @@ public class JadxArgs implements Closeable {
this.useKotlinMethodsForVarNames = useKotlinMethodsForVarNames;
}
public IJadxFilesGetter getFilesGetter() {
return filesGetter;
}
public void setFilesGetter(IJadxFilesGetter filesGetter) {
this.filesGetter = filesGetter;
}
public IJadxSecurity getSecurity() {
return security;
}
public void setSecurity(IJadxSecurity security) {
this.security = security;
}
public boolean isSkipFilesSave() {
return skipFilesSave;
}
@@ -753,14 +685,6 @@ public class JadxArgs implements Closeable {
this.skipFilesSave = skipFilesSave;
}
public boolean isRunDebugChecks() {
return runDebugChecks;
}
public void setRunDebugChecks(boolean runDebugChecks) {
this.runDebugChecks = runDebugChecks;
}
public Map<String, String> getPluginOptions() {
return pluginOptions;
}
@@ -769,14 +693,6 @@ public class JadxArgs implements Closeable {
this.pluginOptions = pluginOptions;
}
public Set<String> getDisabledPlugins() {
return disabledPlugins;
}
public void setDisabledPlugins(Set<String> disabledPlugins) {
this.disabledPlugins = disabledPlugins;
}
public JadxPluginLoader getPluginLoader() {
return pluginLoader;
}
@@ -800,11 +716,10 @@ public class JadxArgs implements Closeable {
String argStr = "args:" + decompilationMode + useImports + showInconsistentCode
+ inlineAnonymousClasses + inlineMethods + moveInnerClasses + allowInlineKotlinLambda
+ deobfuscationOn + deobfuscationMinLength + deobfuscationMaxLength + deobfuscationWhitelist
+ useSourceNameAsClassNameAlias
+ resourceNameSource
+ useKotlinMethodsForVarNames
+ insertDebugLines + extractFinally
+ debugInfo + escapeUnicode + replaceConsts + restoreSwitchOverString
+ debugInfo + useSourceNameAsClassAlias + escapeUnicode + replaceConsts
+ respectBytecodeAccModifiers + fsCaseSensitive + renameFlags
+ commentsLevel + useDxInput + integerFormat
+ "|" + buildPluginsHash(decompiler);
@@ -840,7 +755,7 @@ public class JadxArgs implements Closeable {
+ ", generatedRenamesMappingFile=" + generatedRenamesMappingFile
+ ", generatedRenamesMappingFileMode=" + generatedRenamesMappingFileMode
+ ", resourceNameSource=" + resourceNameSource
+ ", useSourceNameAsClassNameAlias=" + useSourceNameAsClassNameAlias
+ ", useSourceNameAsClassAlias=" + useSourceNameAsClassAlias
+ ", useKotlinMethodsForVarNames=" + useKotlinMethodsForVarNames
+ ", insertDebugLines=" + insertDebugLines
+ ", extractFinally=" + extractFinally
@@ -849,7 +764,6 @@ public class JadxArgs implements Closeable {
+ ", deobfuscationWhitelist=" + deobfuscationWhitelist
+ ", escapeUnicode=" + escapeUnicode
+ ", replaceConsts=" + replaceConsts
+ ", restoreSwitchOverString=" + restoreSwitchOverString
+ ", respectBytecodeAccModifiers=" + respectBytecodeAccModifiers
+ ", exportAsGradleProject=" + exportAsGradleProject
+ ", skipXmlPrettyPrint=" + skipXmlPrettyPrint
@@ -50,8 +50,8 @@ import jadx.core.utils.DecompilerScheduler;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.FileUtils;
import jadx.core.utils.files.ZipPatch;
import jadx.core.utils.tasks.TaskExecutor;
import jadx.core.xmlgen.BinaryXMLParser;
import jadx.core.xmlgen.ResourcesSaver;
/**
@@ -85,42 +85,39 @@ public final class JadxDecompiler implements Closeable {
private static final Logger LOG = LoggerFactory.getLogger(JadxDecompiler.class);
private final JadxArgs args;
private final JadxPluginManager pluginManager;
private final JadxPluginManager pluginManager = new JadxPluginManager(this);
private final List<ICodeLoader> loadedInputs = new ArrayList<>();
private RootNode root;
private List<JavaClass> classes;
private List<ResourceFile> resources;
private BinaryXMLParser binaryXmlParser;
private final IDecompileScheduler decompileScheduler = new DecompilerScheduler();
private final ResourcesLoader resourcesLoader;
private final JadxEventsImpl events = new JadxEventsImpl();
private final ResourcesLoader resourcesLoader = new ResourcesLoader(this);
private final List<ICodeLoader> customCodeLoaders = new ArrayList<>();
private final List<CustomResourcesLoader> customResourcesLoaders = new ArrayList<>();
private final Map<JadxPassType, List<JadxPass>> customPasses = new HashMap<>();
private IJadxEvents events = new JadxEventsImpl();
public JadxDecompiler() {
this(new JadxArgs());
}
public JadxDecompiler(JadxArgs args) {
this.args = Objects.requireNonNull(args);
this.pluginManager = new JadxPluginManager(this);
this.resourcesLoader = new ResourcesLoader(this);
this.args = args;
}
public void load() {
reset();
JadxArgsValidator.validate(this);
LOG.info("loading ...");
FileUtils.updateTempRootDir(args.getFilesGetter().getTempDir());
loadPlugins();
loadInputFiles();
root = new RootNode(args);
root.init();
root.setDecompilerRef(this);
root.mergePasses(customPasses);
root.loadClasses(loadedInputs);
@@ -145,9 +142,7 @@ public final class JadxDecompiler implements Closeable {
private void loadInputFiles() {
loadedInputs.clear();
List<File> inputs = ZipPatch.patchZipFiles(args.getInputFiles());
args.setInputFiles(inputs);
List<Path> inputPaths = Utils.collectionMap(inputs, File::toPath);
List<Path> inputPaths = Utils.collectionMap(args.getInputFiles(), File::toPath);
List<Path> inputFiles = FileUtils.expandDirs(inputPaths);
long start = System.currentTimeMillis();
for (PluginContext plugin : pluginManager.getResolvedPluginContexts()) {
@@ -172,6 +167,7 @@ public final class JadxDecompiler implements Closeable {
root = null;
classes = null;
resources = null;
binaryXmlParser = null;
events.reset();
}
@@ -181,7 +177,6 @@ public final class JadxDecompiler implements Closeable {
closeInputs();
closeLoaders();
args.close();
FileUtils.clearTempRootDir();
}
private void closeInputs() {
@@ -200,7 +195,7 @@ public final class JadxDecompiler implements Closeable {
try {
resourcesLoader.close();
} catch (Exception e) {
LOG.error("Failed to close resource loader: {}", resourcesLoader, e);
LOG.error("Failed to close resource loader: " + resourcesLoader, e);
}
}
customResourcesLoaders.clear();
@@ -471,6 +466,13 @@ public final class JadxDecompiler implements Closeable {
return root;
}
synchronized BinaryXMLParser getBinaryXmlParser() {
if (binaryXmlParser == null) {
binaryXmlParser = new BinaryXMLParser(root);
}
return binaryXmlParser;
}
/**
* Get JavaClass by ClassNode without loading and decompilation
*/
@@ -669,10 +671,6 @@ public final class JadxDecompiler implements Closeable {
return events;
}
public void setEventsImpl(IJadxEvents eventsImpl) {
this.events = eventsImpl;
}
public void addCustomCodeLoader(ICodeLoader customCodeLoader) {
customCodeLoaders.add(customCodeLoader);
}
@@ -252,10 +252,6 @@ public final class JavaClass implements JavaNode {
return cls.getPackage();
}
public JavaPackage getJavaPackage() {
return cls.getPackageNode().getJavaNode();
}
@Override
public JavaClass getDeclaringClass() {
return parent;
@@ -2,6 +2,7 @@ package jadx.api;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import org.jetbrains.annotations.ApiStatus;
@@ -78,9 +79,15 @@ public final class JavaMethod implements JavaNode {
return Collections.emptyList();
}
JadxDecompiler decompiler = getDeclaringClass().getRootDecompiler();
return ovrdAttr.getRelatedMthNodes()
.stream()
.map(decompiler::convertMethodNode)
return ovrdAttr.getRelatedMthNodes().stream()
.map(m -> {
JavaMethod javaMth = decompiler.convertMethodNode(m);
if (javaMth == null) {
LOG.warn("Failed convert to java method: {}", m);
}
return javaMth;
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
@@ -97,10 +104,6 @@ public final class JavaMethod implements JavaNode {
return mth.getDefPosition();
}
public String getCodeStr() {
return mth.getCodeStr();
}
@Override
public void removeAlias() {
this.mth.getMethodInfo().removeAlias();
@@ -76,22 +76,6 @@ public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
return !Objects.equals(parent, aliasParent);
}
public boolean isDescendantOf(JavaPackage ancestor) {
JavaPackage current = this;
while (current != null) {
if (ancestor.equals(current)) {
return true;
}
if (current.getPkgNode().getParentPkg() == null) {
current = null;
} else {
current = current.getPkgNode().getParentPkg().getJavaNode();
}
}
return false;
}
@Override
public ICodeNodeRef getCodeNodeRef() {
return pkgNode;
@@ -9,6 +9,7 @@ import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -26,7 +27,6 @@ import jadx.core.utils.android.Res9patchStreamDecoder;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.FileUtils;
import jadx.core.utils.files.ZipFile;
import jadx.core.xmlgen.BinaryXMLParser;
import jadx.core.xmlgen.IResTableParser;
import jadx.core.xmlgen.ResContainer;
@@ -1,38 +0,0 @@
package jadx.api.args;
import jadx.core.utils.exceptions.JadxRuntimeException;
public enum UseSourceNameAsClassNameAlias {
ALWAYS,
IF_BETTER,
NEVER;
public static UseSourceNameAsClassNameAlias getDefault() {
return NEVER;
}
/**
* @deprecated Use {@link UseSourceNameAsClassNameAlias} directly.
*/
@Deprecated
public boolean toBoolean() {
switch (this) {
case IF_BETTER:
return true;
case NEVER:
return false;
case ALWAYS:
throw new JadxRuntimeException("No match between " + this + " and boolean");
default:
throw new JadxRuntimeException("Unhandled strategy: " + this);
}
}
/**
* @deprecated Use {@link UseSourceNameAsClassNameAlias} directly.
*/
@Deprecated
public static UseSourceNameAsClassNameAlias create(boolean useSourceNameAsAlias) {
return useSourceNameAsAlias ? IF_BETTER : NEVER;
}
}
@@ -62,7 +62,7 @@ public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter
buf.append(cw.getCodeStr());
return this;
}
AnnotatedCodeWriter code = (AnnotatedCodeWriter) cw;
AnnotatedCodeWriter code = ((AnnotatedCodeWriter) cw);
line--;
int startPos = getLength();
for (Map.Entry<Integer, ICodeAnnotation> entry : code.annotations.entrySet()) {
@@ -6,7 +6,6 @@ import org.jetbrains.annotations.Nullable;
import jadx.api.JadxArgs;
import jadx.api.JadxDecompiler;
import jadx.api.plugins.data.IJadxFiles;
import jadx.api.plugins.data.IJadxPlugins;
import jadx.api.plugins.events.IJadxEvents;
import jadx.api.plugins.gui.JadxGuiContext;
@@ -54,9 +53,4 @@ public interface JadxPluginContext {
* Access to registered plugins and runtime data
*/
IJadxPlugins plugins();
/**
* Access to plugin specific files and directories
*/
IJadxFiles files();
}
@@ -1,29 +1,15 @@
package jadx.api.plugins;
import org.jetbrains.annotations.Nullable;
public class JadxPluginInfo {
private final String pluginId;
private final String name;
private final String description;
private String homepage;
private final String homepage;
/**
* Conflicting plugins should have the same 'provides' property; only one will be loaded
*/
private String provides;
/**
* Minimum required jadx version to run this plugin.
* <br>
* Format: "<stable version>, r<revision number of unstable version>".
* Example: "1.5.1, r2305"
*
* @see <a href="https://github.com/skylot/jadx/wiki/Jadx-plugins-guide#required-jadx-version">wiki
* page</a>
* for details.
*/
private @Nullable String requiredJadxVersion;
private final String provides;
public JadxPluginInfo(String id, String name, String description) {
this(id, name, description, "", id);
@@ -57,26 +43,10 @@ public class JadxPluginInfo {
return homepage;
}
public void setHomepage(String homepage) {
this.homepage = homepage;
}
public String getProvides() {
return provides;
}
public void setProvides(String provides) {
this.provides = provides;
}
public @Nullable String getRequiredJadxVersion() {
return requiredJadxVersion;
}
public void setRequiredJadxVersion(@Nullable String requiredJadxVersion) {
this.requiredJadxVersion = requiredJadxVersion;
}
@Override
public String toString() {
return pluginId + ": " + name + " - '" + description + '\'';
@@ -4,14 +4,11 @@ import java.util.Objects;
import org.jetbrains.annotations.Nullable;
import jadx.core.plugins.versions.VerifyRequiredVersion;
public class JadxPluginInfoBuilder {
private String pluginId;
private String name;
private String description;
private String homepage = "";
private @Nullable String requiredJadxVersion;
private @Nullable String provides;
/**
@@ -46,11 +43,6 @@ public class JadxPluginInfoBuilder {
return this;
}
public JadxPluginInfoBuilder requiredJadxVersion(String versions) {
this.requiredJadxVersion = versions;
return this;
}
public JadxPluginInfo build() {
Objects.requireNonNull(pluginId, "PluginId is required");
Objects.requireNonNull(name, "Name is required");
@@ -58,11 +50,6 @@ public class JadxPluginInfoBuilder {
if (provides == null) {
provides = pluginId;
}
if (requiredJadxVersion != null) {
VerifyRequiredVersion.verify(requiredJadxVersion);
}
JadxPluginInfo pluginInfo = new JadxPluginInfo(pluginId, name, description, homepage, provides);
pluginInfo.setRequiredJadxVersion(requiredJadxVersion);
return pluginInfo;
return new JadxPluginInfo(pluginId, name, description, homepage, provides);
}
}
@@ -1,21 +0,0 @@
package jadx.api.plugins.data;
import java.nio.file.Path;
public interface IJadxFiles {
/**
* Plugin cache directory.
*/
Path getPluginCacheDir();
/**
* Plugin config directory.
*/
Path getPluginConfigDir();
/**
* Plugin temp directory.
*/
Path getPluginTempDir();
}
@@ -15,15 +15,4 @@ public interface IJadxEvents {
* For public event types check {@link JadxEvents} class.
*/
<E extends IJadxEvent> void addListener(JadxEventType<E> eventType, Consumer<E> listener);
/**
* Remove listener for specific event.
* Listener should be same or equal object.
*/
<E extends IJadxEvent> void removeListener(JadxEventType<E> eventType, Consumer<E> listener);
/**
* Clear all listeners.
*/
void reset();
}
@@ -6,13 +6,4 @@ public abstract class JadxEventType<T extends IJadxEvent> {
return new JadxEventType<>() {
};
}
public static <E extends IJadxEvent> JadxEventType<E> create(String name) {
return new JadxEventType<>() {
@Override
public String toString() {
return name;
}
};
}
}
@@ -16,17 +16,17 @@ public class JadxEvents {
/**
* Notify about renaming done by user (GUI only).
*/
public static final JadxEventType<NodeRenamedByUser> NODE_RENAMED_BY_USER = create("NODE_RENAMED_BY_USER");
public static final JadxEventType<NodeRenamedByUser> NODE_RENAMED_BY_USER = create();
/**
* Request reload of a current project (GUI only).
*/
public static final JadxEventType<ReloadProject> RELOAD_PROJECT = create("RELOAD_PROJECT");
public static final JadxEventType<ReloadProject> RELOAD_PROJECT = create();
/**
* Request reload of a settings window (GUI only).
* Useful for a reload custom settings group which was set with
* {@link JadxGuiSettings#setCustomSettingsGroup(ISettingsGroup)}.
*/
public static final JadxEventType<ReloadSettingsWindow> RELOAD_SETTINGS_WINDOW = create("RELOAD_SETTINGS_WINDOW");
public static final JadxEventType<ReloadSettingsWindow> RELOAD_SETTINGS_WINDOW = create();
}
@@ -3,7 +3,6 @@ package jadx.api.plugins.gui;
import java.util.function.Consumer;
import java.util.function.Function;
import javax.swing.JFrame;
import javax.swing.KeyStroke;
import org.jetbrains.annotations.Nullable;
@@ -51,12 +50,6 @@ public interface JadxGuiContext {
*/
JadxGuiSettings settings();
/**
* Main window component.
* Can be used as a parent for creating new windows or dialogs.
*/
JFrame getMainFrame();
ICodeNodeRef getNodeUnderCaret();
ICodeNodeRef getNodeUnderMouse();
@@ -8,6 +8,7 @@ import java.util.Enumeration;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
@@ -15,7 +16,6 @@ import org.slf4j.LoggerFactory;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.ZipFile;
public class ZipSecurity {
private static final Logger LOG = LoggerFactory.getLogger(ZipSecurity.class);
@@ -1,20 +0,0 @@
package jadx.api.security;
import java.io.InputStream;
import org.w3c.dom.Document;
public interface IJadxSecurity {
/**
* Check if application package is safe
*
* @return normalized/sanitized string or same string if safe
*/
String verifyAppPackage(String appPackage);
/**
* XML document parser
*/
Document parseXml(InputStream in);
}
@@ -1,18 +0,0 @@
package jadx.api.security;
import java.util.EnumSet;
import java.util.Set;
public enum JadxSecurityFlag {
VERIFY_APP_PACKAGE,
SECURE_XML_PARSER;
public static Set<JadxSecurityFlag> all() {
return EnumSet.allOf(JadxSecurityFlag.class);
}
public static Set<JadxSecurityFlag> none() {
return EnumSet.noneOf(JadxSecurityFlag.class);
}
}
@@ -1,73 +0,0 @@
package jadx.api.security.impl;
import java.io.InputStream;
import java.util.Set;
import javax.xml.parsers.DocumentBuilderFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import jadx.api.security.IJadxSecurity;
import jadx.api.security.JadxSecurityFlag;
import jadx.core.deobf.NameMapper;
public class JadxSecurity implements IJadxSecurity {
private static final Logger LOG = LoggerFactory.getLogger(JadxSecurity.class);
private final Set<JadxSecurityFlag> flags;
public JadxSecurity(Set<JadxSecurityFlag> flags) {
this.flags = flags;
}
@Override
public String verifyAppPackage(String appPackage) {
if (flags.contains(JadxSecurityFlag.VERIFY_APP_PACKAGE)
&& !NameMapper.isValidFullIdentifier(appPackage)) {
LOG.warn("App package '{}' has invalid format and will be ignored", appPackage);
return "INVALID_PACKAGE";
}
return appPackage;
}
@Override
public Document parseXml(InputStream in) {
DocumentBuilderFactory dbf;
if (flags.contains(JadxSecurityFlag.SECURE_XML_PARSER)) {
dbf = SecureDBFHolder.INSTANCE;
} else {
dbf = SimpleDBFHolder.INSTANCE;
}
try {
return dbf.newDocumentBuilder().parse(in);
} catch (Exception e) {
throw new RuntimeException("Failed to parse xml", e);
}
}
private static final class SimpleDBFHolder {
private static final DocumentBuilderFactory INSTANCE = DocumentBuilderFactory.newInstance();
}
private static final class SecureDBFHolder {
private static final DocumentBuilderFactory INSTANCE = buildSecureDBF();
private static DocumentBuilderFactory buildSecureDBF() {
try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
dbf.setFeature("http://apache.org/xml/features/dom/create-entity-ref-nodes", false);
dbf.setXIncludeAware(false);
dbf.setExpandEntityReferences(false);
return dbf;
} catch (Exception e) {
throw new RuntimeException("Fail to build secure XML DocumentBuilderFactory", e);
}
}
}
}
@@ -1,13 +1,5 @@
package jadx.api.utils;
import java.util.function.BiFunction;
import jadx.api.ICodeInfo;
import jadx.api.metadata.ICodeAnnotation;
import jadx.api.metadata.ICodeNodeRef;
import jadx.api.metadata.annotations.NodeDeclareRef;
import jadx.core.dex.nodes.MethodNode;
public class CodeUtils {
public static String getLineForPos(String code, int pos) {
@@ -55,71 +47,4 @@ public class CodeUtils {
line++;
}
}
/**
* Cut method code (including comments and annotations) from class code.
*
* @return method code or empty string if metadata is not available
*/
public static String extractMethodCode(MethodNode mth, ICodeInfo codeInfo) {
int end = getMethodEnd(mth, codeInfo);
if (end == -1) {
return "";
}
int start = getMethodStart(mth, codeInfo);
if (end < start) {
return "";
}
return codeInfo.getCodeStr().substring(start, end);
}
/**
* Search first empty line before method definition to include comments and annotations
*/
private static int getMethodStart(MethodNode mth, ICodeInfo codeInfo) {
int pos = mth.getDefPosition();
String newLineStr = mth.root().getArgs().getCodeNewLineStr();
String emptyLine = newLineStr + newLineStr;
int emptyLinePos = codeInfo.getCodeStr().lastIndexOf(emptyLine, pos);
return emptyLinePos == -1 ? pos : emptyLinePos + emptyLine.length();
}
/**
* Search method end position in provided class code info.
*
* @return end pos or -1 if metadata not available
*/
public static int getMethodEnd(MethodNode mth, ICodeInfo codeInfo) {
if (!codeInfo.hasMetadata()) {
return -1;
}
// skip nested nodes DEF/END until first unpaired END annotation (end of this method)
Integer end = codeInfo.getCodeMetadata().searchDown(mth.getDefPosition() + 1, new BiFunction<>() {
int nested = 0;
@Override
public Integer apply(Integer pos, ICodeAnnotation ann) {
switch (ann.getAnnType()) {
case DECLARATION:
ICodeNodeRef node = ((NodeDeclareRef) ann).getNode();
switch (node.getAnnType()) {
case CLASS:
case METHOD:
nested++;
break;
}
break;
case END:
if (nested == 0) {
return pos;
}
nested--;
break;
}
return null;
}
});
return end == null ? -1 : end;
}
}
@@ -10,7 +10,7 @@ public class Consts {
public static final boolean DEBUG_FINALLY = false;
public static final boolean DEBUG_ATTRIBUTES = false;
public static final boolean DEBUG_RESTRUCTURE = false;
public static final boolean DEBUG_EVENTS = Jadx.isDevVersion();
public static final boolean DEBUG_EVENTS = true;
public static final String CLASS_OBJECT = "java.lang.Object";
public static final String CLASS_STRING = "java.lang.String";
+3 -7
View File
@@ -13,6 +13,7 @@ import org.slf4j.LoggerFactory;
import jadx.api.CommentsLevel;
import jadx.api.JadxArgs;
import jadx.core.deobf.DeobfuscatorVisitor;
import jadx.core.deobf.InitRenameProviders;
import jadx.core.deobf.SaveDeobfMapping;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.visitors.AnonymousClassVisitor;
@@ -28,6 +29,7 @@ import jadx.core.dex.visitors.DotGraphVisitor;
import jadx.core.dex.visitors.EnumVisitor;
import jadx.core.dex.visitors.ExtractFieldInit;
import jadx.core.dex.visitors.FallbackModeVisitor;
import jadx.core.dex.visitors.FixAccessModifiers;
import jadx.core.dex.visitors.FixSwitchOverEnum;
import jadx.core.dex.visitors.GenericTypesVisitor;
import jadx.core.dex.visitors.IDexTreeVisitor;
@@ -47,13 +49,11 @@ import jadx.core.dex.visitors.ReplaceNewArray;
import jadx.core.dex.visitors.ShadowFieldVisitor;
import jadx.core.dex.visitors.SignatureProcessor;
import jadx.core.dex.visitors.SimplifyVisitor;
import jadx.core.dex.visitors.blocks.BlockFinisher;
import jadx.core.dex.visitors.blocks.BlockProcessor;
import jadx.core.dex.visitors.blocks.BlockSplitter;
import jadx.core.dex.visitors.debuginfo.DebugInfoApplyVisitor;
import jadx.core.dex.visitors.debuginfo.DebugInfoAttachVisitor;
import jadx.core.dex.visitors.finaly.MarkFinallyVisitor;
import jadx.core.dex.visitors.fixaccessmodifiers.FixAccessModifiers;
import jadx.core.dex.visitors.kotlin.ProcessKotlinInternals;
import jadx.core.dex.visitors.prepare.AddAndroidConstants;
import jadx.core.dex.visitors.prepare.CollectConstValues;
@@ -63,7 +63,6 @@ import jadx.core.dex.visitors.regions.IfRegionVisitor;
import jadx.core.dex.visitors.regions.LoopRegionVisitor;
import jadx.core.dex.visitors.regions.RegionMakerVisitor;
import jadx.core.dex.visitors.regions.ReturnVisitor;
import jadx.core.dex.visitors.regions.SwitchOverStringVisitor;
import jadx.core.dex.visitors.regions.variables.ProcessVariables;
import jadx.core.dex.visitors.rename.CodeRenameVisitor;
import jadx.core.dex.visitors.rename.RenameVisitor;
@@ -104,6 +103,7 @@ public class Jadx {
passes.add(new CollectConstValues());
// rename and deobfuscation
passes.add(new InitRenameProviders());
passes.add(new DeobfuscatorVisitor());
passes.add(new SourceFileRename());
passes.add(new RenameVisitor());
@@ -132,7 +132,6 @@ public class Jadx {
// blocks IR
passes.add(new BlockSplitter());
passes.add(new BlockProcessor());
passes.add(new BlockFinisher());
if (args.isRawCFGOutput()) {
passes.add(DotGraphVisitor.dumpRaw());
}
@@ -173,9 +172,6 @@ public class Jadx {
// regions IR
passes.add(new RegionMakerVisitor());
passes.add(new IfRegionVisitor());
if (args.isRestoreSwitchOverString()) {
passes.add(new SwitchOverStringVisitor());
}
passes.add(new ReturnVisitor());
passes.add(new CleanRegions());
@@ -8,6 +8,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.ICodeInfo;
import jadx.api.JadxArgs;
import jadx.api.impl.SimpleCodeInfo;
import jadx.core.codegen.CodeGen;
import jadx.core.dex.attributes.AFlag;
@@ -31,8 +32,8 @@ public class ProcessClass {
private final List<IDexTreeVisitor> passes;
public ProcessClass(List<IDexTreeVisitor> passesList) {
this.passes = passesList;
public ProcessClass(JadxArgs args) {
this.passes = Jadx.getPassesList(args);
}
@Nullable
@@ -124,33 +125,6 @@ public class ProcessClass {
}
}
/**
* Load and process class without its deps
*/
public void forceProcess(ClassNode cls) {
ClassNode topParentClass = cls.getTopParentClass();
if (topParentClass != cls) {
forceProcess(topParentClass);
return;
}
try {
process(cls, false);
} catch (Throwable e) {
throw new JadxRuntimeException("Failed to process class: " + cls.getFullName(), e);
}
}
/**
* Generate code for class without processing its deps
*/
public @Nullable ICodeInfo forceGenerateCode(ClassNode cls) {
try {
return process(cls, true);
} catch (Throwable e) {
throw new JadxRuntimeException("Failed to generate code for class: " + cls.getFullName(), e);
}
}
public void initPasses(RootNode root) {
for (IDexTreeVisitor pass : passes) {
try {
@@ -24,7 +24,7 @@ public class ClspClass {
private Map<String, ClspMethod> methodsMap = Collections.emptyMap();
private List<ArgType> typeParameters = Collections.emptyList();
private final ClspClassSource source;
private ClspClassSource source;
public ClspClass(ArgType clsType, int id, int accFlags, ClspClassSource source) {
this.clsType = clsType;
@@ -652,13 +652,6 @@ public class ClassGen {
code.add(clsName);
}
public void addClsShortNameForced(ICodeWriter code, ClassInfo classInfo) {
code.add(classInfo.getAliasShortName());
if (!isBothClassesInOneTopClass(cls.getClassInfo(), classInfo)) {
addImport(classInfo);
}
}
private String useClassInternal(ClassInfo useCls, ClassInfo extClsInfo) {
String fullName = extClsInfo.getAliasFullName();
if (fallback || !useImports) {
@@ -434,7 +434,7 @@ public class InsnGen {
code.add(']');
}
int dim = arrayType.getArrayDimension();
for (; k < dim; k++) {
for (; k < dim - 1; k++) {
code.add("[]");
}
break;
@@ -749,7 +749,6 @@ public class InsnGen {
code.attachAnnotation(refMth);
code.add("this");
} else {
boolean forceShortName = addOuterClassInstance(insn, code, callMth);
code.add("new ");
if (refMth == null || refMth.contains(AFlag.DONT_GENERATE)) {
// use class reference if constructor method is missing (default constructor)
@@ -757,11 +756,7 @@ public class InsnGen {
} else {
code.attachAnnotation(refMth);
}
if (forceShortName) {
mgen.getClassGen().addClsShortNameForced(code, insn.getClassType());
} else {
mgen.getClassGen().addClsName(code, insn.getClassType());
}
mgen.getClassGen().addClsName(code, insn.getClassType());
GenericInfoAttr genericInfoAttr = insn.get(AType.GENERIC_INFO);
if (genericInfoAttr != null) {
code.add('<');
@@ -782,27 +777,6 @@ public class InsnGen {
generateMethodArguments(code, insn, 0, callMth);
}
private boolean addOuterClassInstance(ConstructorInsn insn, ICodeWriter code, MethodNode callMth) throws CodegenException {
if (callMth == null || !callMth.contains(AFlag.SKIP_FIRST_ARG)) {
return false;
}
ClassNode ctrCls = callMth.getDeclaringClass();
if (!ctrCls.isInner() || insn.getArgsCount() == 0) {
return false;
}
InsnArg instArg = insn.getArg(0);
if (instArg.isThis()) {
return false;
}
// instance arg should be of an outer class type
if (!instArg.getType().equals(ctrCls.getDeclaringClass().getType())) {
return false;
}
addArgDot(code, instArg);
// can't use another dot, force short name of class
return true;
}
private void inlineAnonymousConstructor(ICodeWriter code, ClassNode cls, ConstructorInsn insn) throws CodegenException {
cls.ensureProcessed();
if (this.mth.getParentClass() == cls) {
@@ -269,7 +269,7 @@ public class MethodGen {
JadxArgs args = mth.root().getArgs();
switch (args.getDecompilationMode()) {
case AUTO:
if (classGen.isFallbackMode() || mth.getRegion() == null) {
if (classGen.isFallbackMode()) {
// TODO: try simple mode first
dumpInstructions(code);
} else {
@@ -278,8 +278,6 @@ public class RegionGen extends InsnGen {
useField(code, (FieldInfo) k, null);
} else if (k instanceof Integer) {
code.add(TypeGen.literalToString((Integer) k, arg.getType(), mth, fallback));
} else if (k instanceof String) {
code.add('\"').add((String) k).add('\"');
} else {
throw new JadxRuntimeException("Unexpected key in switch: " + (k != null ? k.getClass() : null));
}
@@ -135,17 +135,10 @@ public class CodeGenUtils {
}
}
public static void addInputFileInfo(ICodeWriter code, ClassNode cls) {
if (cls.checkCommentsLevel(CommentsLevel.INFO) && cls.getClsData() != null) {
String inputFileName = cls.getClsData().getInputFileName();
public static void addInputFileInfo(ICodeWriter code, ClassNode node) {
if (node.getClsData() != null && node.checkCommentsLevel(CommentsLevel.INFO)) {
String inputFileName = node.getClsData().getInputFileName();
if (inputFileName != null) {
ClassNode declCls = cls.getDeclaringClass();
if (declCls != null
&& declCls.getClsData() != null
&& inputFileName.equals(declCls.getClsData().getInputFileName())) {
// don't add same comment for inner classes
return;
}
code.startLine("/* loaded from: ").add(inputFileName).add(" */");
}
}
@@ -0,0 +1,20 @@
package jadx.core.deobf;
import jadx.api.JadxArgs;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.AbstractVisitor;
import jadx.core.utils.exceptions.JadxException;
public class InitRenameProviders extends AbstractVisitor {
@Override
public void init(RootNode root) throws JadxException {
JadxArgs args = root.getArgs();
if (args.isDeobfuscationOn() || !args.getRenameFlags().isEmpty()) {
args.getAliasProvider().init(root);
}
if (args.isDeobfuscationOn()) {
args.getRenameCondition().init(root);
}
}
}
@@ -19,7 +19,6 @@ public class NameMapper {
private static final Set<String> RESERVED_NAMES = new HashSet<>(
Arrays.asList(
"_",
"abstract",
"assert",
"boolean",
@@ -5,39 +5,81 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.PackageNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.Utils;
public class DeobfWhitelist extends AbstractDeobfCondition {
private static final Logger LOG = LoggerFactory.getLogger(DeobfWhitelist.class);
public static final List<String> DEFAULT_LIST = Arrays.asList(
"android.support.v4.*",
"android.support.v7.*",
"android.support.v4.os.*",
"android.support.annotation.Px",
"android.support.*",
"android.os.*",
"androidx.core.os.*",
"androidx.annotation.Px");
"androidx.annotation.*");
public static final String DEFAULT_STR = Utils.listToString(DEFAULT_LIST, " ");
private final Set<String> packages = new HashSet<>();
private final Set<String> classes = new HashSet<>();
private final Set<ClassNode> classes = new HashSet<>();
private boolean reportMissingItems = false;
@Override
public void init(RootNode root) {
packages.clear();
classes.clear();
for (String whitelistItem : root.getArgs().getDeobfuscationWhitelist()) {
if (!whitelistItem.isEmpty()) {
if (whitelistItem.endsWith(".*")) {
packages.add(whitelistItem.substring(0, whitelistItem.length() - 2));
} else {
classes.add(whitelistItem);
}
List<String> excludeList = root.getArgs().getDeobfuscationWhitelist();
reportMissingItems = !excludeList.equals(DEFAULT_LIST);
for (String name : excludeList) {
if (name.isEmpty()) {
continue;
}
if (name.endsWith(".*")) {
excludePackage(root, name.substring(0, name.length() - 2));
} else {
excludeClass(root, name);
}
}
LOG.debug("Excluded from deobfuscation: {} packages, {} classes", packages.size(), classes.size());
}
private void excludeClass(RootNode root, String clsFullName) {
ClassNode cls = root.resolveClass(clsFullName);
if (cls == null) {
if (reportMissingItems) {
LOG.info("Can't exclude from deobfuscation: class '{}' not found", clsFullName);
}
return;
}
excludeClsNode(cls);
}
private void excludeClsNode(ClassNode cls) {
classes.add(cls);
cls.addInfoComment("Class excluded from deobfuscation");
}
private void excludePackage(RootNode root, String fullPkgName) {
PackageNode pkg = root.resolvePackage(fullPkgName);
if (pkg == null) {
if (reportMissingItems) {
LOG.info("Can't exclude from deobfuscation: package '{}' not found", fullPkgName);
}
return;
}
excludePkgNode(pkg);
}
private void excludePkgNode(PackageNode pkg) {
packages.add(pkg.getFullName());
pkg.getClasses().forEach(this::excludeClsNode);
pkg.getSubPackages().forEach(this::excludePkgNode);
}
@Override
@@ -50,9 +92,19 @@ public class DeobfWhitelist extends AbstractDeobfCondition {
@Override
public Action check(ClassNode cls) {
if (classes.contains(cls.getClassInfo().getFullName())) {
if (classes.contains(cls)) {
return Action.FORBID_RENAME;
}
return Action.NO_ACTION;
}
@Override
public Action check(FieldNode fld) {
return check(fld.getParentClass());
}
@Override
public Action check(MethodNode mth) {
return check(mth.getParentClass());
}
}
@@ -9,7 +9,8 @@ import jadx.core.dex.nodes.PackageNode;
import jadx.core.utils.exceptions.JadxRuntimeException;
/**
* Provides a list of all top level domains, so we can exclude them from deobfuscation.
* Provides a list of all top level domains with 3 characters and less,
* so we can exclude them from deobfuscation.
*/
public class ExcludePackageWithTLDNames extends AbstractDeobfCondition {
@@ -17,22 +18,23 @@ public class ExcludePackageWithTLDNames extends AbstractDeobfCondition {
* Lazy load TLD set
*/
private static class TldHolder {
private static final Set<String> TLD_SET = loadTldSet();
private static final Set<String> TLD_SET = loadTldFile();
}
private static Set<String> loadTldSet() {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(TldHolder.class.getResourceAsStream("tlds.txt")))) {
private static Set<String> loadTldFile() {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(TldHolder.class.getResourceAsStream("tld_3.txt")))) {
return reader.lines()
.map(String::trim)
.filter(line -> !line.startsWith("#") && !line.isEmpty())
.collect(Collectors.toSet());
} catch (Exception e) {
throw new JadxRuntimeException("Failed to load top level domain list file: tlds.txt", e);
throw new JadxRuntimeException("Failed to load top level domain list file: tld_3.txt", e);
}
}
@Override
public Action check(PackageNode pkg) {
if (pkg.isRoot() && TldHolder.TLD_SET.contains(pkg.getName())) {
if (TldHolder.TLD_SET.contains(pkg.getName())) {
return Action.FORBID_RENAME;
}
return Action.NO_ACTION;
@@ -90,6 +90,7 @@ public enum AFlag {
REQUEST_IF_REGION_OPTIMIZE, // run if region visitor again
REQUEST_CODE_SHRINK,
RERUN_SSA_TRANSFORM,
METHOD_CANDIDATE_FOR_INLINE,
USE_LINES_HINTS, // source lines info in methods can be trusted
@@ -5,7 +5,6 @@ import jadx.api.plugins.input.data.attributes.IJadxAttribute;
import jadx.core.codegen.utils.CodeComment;
import jadx.core.dex.attributes.nodes.AnonymousClassAttr;
import jadx.core.dex.attributes.nodes.ClassTypeVarsAttr;
import jadx.core.dex.attributes.nodes.CodeFeaturesAttr;
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
import jadx.core.dex.attributes.nodes.EdgeInsnAttr;
import jadx.core.dex.attributes.nodes.EnumClassAttr;
@@ -75,7 +74,6 @@ public final class AType<T extends IJadxAttribute> implements IJadxAttrType<T> {
public static final AType<MethodOverrideAttr> METHOD_OVERRIDE = new AType<>();
public static final AType<MethodTypeVarsAttr> METHOD_TYPE_VARS = new AType<>();
public static final AType<AttrList<TryCatchBlockAttr>> TRY_BLOCKS_LIST = new AType<>();
public static final AType<CodeFeaturesAttr> METHOD_CODE_FEATURES = new AType<>();
// region
public static final AType<DeclareVariablesAttr> DECLARE_VARIABLES = new AType<>();
@@ -1,56 +0,0 @@
package jadx.core.dex.attributes.nodes;
import java.util.EnumSet;
import java.util.Set;
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.nodes.MethodNode;
public class CodeFeaturesAttr implements IJadxAttribute {
public enum CodeFeature {
/**
* Code contains switch instruction
*/
SWITCH,
/**
* Code contains new-array instruction
*/
NEW_ARRAY,
}
public static boolean contains(MethodNode mth, CodeFeature feature) {
CodeFeaturesAttr codeFeaturesAttr = mth.get(AType.METHOD_CODE_FEATURES);
if (codeFeaturesAttr == null) {
return false;
}
return codeFeaturesAttr.getCodeFeatures().contains(feature);
}
public static void add(MethodNode mth, CodeFeature feature) {
CodeFeaturesAttr codeFeaturesAttr = mth.get(AType.METHOD_CODE_FEATURES);
if (codeFeaturesAttr == null) {
codeFeaturesAttr = new CodeFeaturesAttr();
mth.addAttr(codeFeaturesAttr);
}
codeFeaturesAttr.getCodeFeatures().add(feature);
}
private final Set<CodeFeature> codeFeatures = EnumSet.noneOf(CodeFeature.class);
public Set<CodeFeature> getCodeFeatures() {
return codeFeatures;
}
@Override
public AType<CodeFeaturesAttr> getAttrType() {
return AType.METHOD_CODE_FEATURES;
}
@Override
public String toAttrString() {
return "CodeFeatures{" + codeFeatures + '}';
}
}
@@ -33,7 +33,7 @@ public class JadxCommentsAttr implements IJadxAttribute {
private final Map<CommentsLevel, List<String>> comments = new EnumMap<>(CommentsLevel.class);
public void add(CommentsLevel level, String comment) {
comments.computeIfAbsent(level, l -> new ArrayList<>()).add(comment);
comments.computeIfAbsent(level, (l) -> new ArrayList<>()).add(comment);
}
public List<String> formatAndFilter(CommentsLevel level) {
@@ -14,14 +14,14 @@ public class MethodOverrideAttr extends PinnedAttribute {
/**
* All methods overridden by current method. Current method excluded, empty for base method.
*/
private final List<IMethodDetails> overrideList;
private List<IMethodDetails> overrideList;
/**
* All method nodes from override hierarchy. Current method included.
*/
private SortedSet<MethodNode> relatedMthNodes;
private final Set<IMethodDetails> baseMethods;
private Set<IMethodDetails> baseMethods;
public MethodOverrideAttr(List<IMethodDetails> overrideList, SortedSet<MethodNode> relatedMthNodes, Set<IMethodDetails> baseMethods) {
this.overrideList = overrideList;
@@ -3,6 +3,10 @@ package jadx.core.dex.instructions;
import java.util.List;
import java.util.Objects;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.plugins.input.data.ICodeReader;
import jadx.api.plugins.input.data.IMethodProto;
import jadx.api.plugins.input.data.IMethodRef;
@@ -13,8 +17,6 @@ import jadx.api.plugins.input.insns.custom.ISwitchPayload;
import jadx.core.Consts;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.CodeFeaturesAttr;
import jadx.core.dex.attributes.nodes.CodeFeaturesAttr.CodeFeature;
import jadx.core.dex.attributes.nodes.JadxError;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
@@ -33,6 +35,8 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.input.InsnDataUtils;
public class InsnDecoder {
private static final Logger LOG = LoggerFactory.getLogger(InsnDecoder.class);
private final MethodNode method;
private final RootNode root;
@@ -50,7 +54,7 @@ public class InsnDecoder {
rawInsn.decode();
insn = decode(rawInsn);
} catch (Exception e) {
method.addError("Failed to decode insn: " + rawInsn, e);
method.addError("Failed to decode insn: " + rawInsn + ", method: " + method, e);
insn = new InsnNode(InsnType.NOP, 0);
insn.addAttr(AType.JADX_ERROR, new JadxError("decode failed: " + e.getMessage(), e));
}
@@ -60,6 +64,7 @@ public class InsnDecoder {
return instructions;
}
@NotNull
protected InsnNode decode(InsnData insn) throws DecodeException {
switch (insn.getOpcode()) {
case NOP:
@@ -509,6 +514,7 @@ public class InsnDecoder {
}
}
@NotNull
private SwitchInsn makeSwitch(InsnData insn, boolean packed) {
SwitchInsn swInsn = new SwitchInsn(InsnArg.reg(insn, 0, ArgType.UNKNOWN), insn.getTarget(), packed);
ICustomPayload payload = insn.getPayload();
@@ -516,7 +522,6 @@ public class InsnDecoder {
swInsn.attachSwitchData(new SwitchData((ISwitchPayload) payload), insn.getTarget());
}
method.add(AFlag.COMPUTE_POST_DOM);
CodeFeaturesAttr.add(method, CodeFeature.SWITCH);
return swInsn;
}
@@ -540,7 +545,6 @@ public class InsnDecoder {
for (int i = 1; i < regsCount; i++) {
newArr.addArg(InsnArg.typeImmutableReg(insn, i, ArgType.INT));
}
CodeFeaturesAttr.add(method, CodeFeature.NEW_ARRAY);
return newArr;
}
@@ -43,9 +43,6 @@ public final class PhiInsn extends InsnNode {
if (blockBinds.contains(pred)) {
throw new JadxRuntimeException("Duplicate predecessors in PHI insn: " + pred + ", " + this);
}
if (pred == null) {
throw new JadxRuntimeException("Null bind block in PHI insn: " + this);
}
super.addArg(arg);
blockBinds.add(pred);
}
@@ -13,7 +13,6 @@ import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.InsnRemover;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.exceptions.JadxRuntimeException;
/**
@@ -292,20 +291,6 @@ public abstract class InsnArg extends Typed {
return false;
}
public boolean isSameCodeVar(RegisterArg arg) {
if (arg == null) {
return false;
}
if (isRegister()) {
return ((RegisterArg) this).sameCodeVar(arg);
}
return false;
}
public boolean isUseVar(RegisterArg arg) {
return InsnUtils.containsVar(this, arg);
}
protected final <T extends InsnArg> T copyCommonParams(T copy) {
copy.copyAttributesFrom(this);
copy.setParentInsn(parentInsn);
@@ -6,7 +6,6 @@ import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -293,19 +292,10 @@ public class SSAVar implements Comparable<SSAVar> {
StringBuilder sb = new StringBuilder();
sb.append('r').append(regNum).append('v').append(version);
if (!names.isEmpty()) {
String orderedNames = names.stream()
.sorted()
.collect(Collectors.joining(", ", "[", "]"));
sb.append(", names: ").append(orderedNames);
sb.append(", names: ").append(names);
}
if (!types.isEmpty()) {
String orderedTypes = types.stream()
.map(String::valueOf)
.sorted()
.collect(Collectors.joining(", ", "[", "]"));
sb.append(", types: ").append(orderedTypes);
sb.append(", types: ").append(types);
}
return sb.toString();
}
@@ -25,12 +25,8 @@ public final class ConstructorInsn extends BaseInvokeNode {
}
public ConstructorInsn(MethodNode mth, InvokeNode invoke) {
this(mth, invoke, invoke.getCallMth());
}
public ConstructorInsn(MethodNode mth, InvokeNode invoke, MethodInfo callMth) {
super(InsnType.CONSTRUCTOR, invoke.getArgsCount() - 1);
this.callMth = callMth;
this.callMth = invoke.getCallMth();
this.callType = getCallType(mth, callMth.getDeclClass(), invoke.getArg(0));
int argsCount = invoke.getArgsCount();
for (int i = 1; i < argsCount; i++) {
@@ -226,10 +226,6 @@ public final class BlockNode extends AttrNode implements IBlock, Comparable<Bloc
return contains(AFlag.RETURN);
}
public boolean isMthExitBlock() {
return contains(AFlag.MTH_EXIT_BLOCK);
}
public boolean isEmpty() {
return instructions.isEmpty();
}
@@ -37,7 +37,6 @@ import jadx.api.plugins.input.data.attributes.types.SourceFileAttr;
import jadx.api.plugins.input.data.impl.ListConsumer;
import jadx.api.usage.IUsageInfoData;
import jadx.core.Consts;
import jadx.core.Jadx;
import jadx.core.ProcessClass;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
@@ -325,10 +324,9 @@ public class ClassNode extends NotificationAttrNode
try {
unload();
args.setDecompilationMode(mode);
ProcessClass process = new ProcessClass(Jadx.getPassesList(args));
ProcessClass process = new ProcessClass(args);
process.initPasses(root);
ICodeInfo code = process.forceGenerateCode(this);
return Utils.getOrElse(code, ICodeInfo.EMPTY);
return process.generateCode(this);
} finally {
args.setDecompilationMode(baseMode);
unload();
@@ -586,11 +584,6 @@ public class ClassNode extends NotificationAttrNode
return null;
}
@Override
public ClassNode getDeclaringClass() {
return isInner() ? parentClass : null;
}
public ClassNode getParentClass() {
return parentClass;
}
@@ -864,12 +857,7 @@ public class ClassNode extends NotificationAttrNode
}
}
/**
* Low level class data access.
*
* @return null for classes generated by jadx
*/
public @Nullable IClassData getClsData() {
public IClassData getClsData() {
return clsData;
}
@@ -86,11 +86,6 @@ public class FieldNode extends NotificationAttrNode implements ICodeNode, IField
return type;
}
@Override
public ClassNode getDeclaringClass() {
return parentClass;
}
public ClassNode getParentClass() {
return parentClass;
}
@@ -5,9 +5,6 @@ import jadx.core.dex.attributes.IAttributeNode;
import jadx.core.dex.info.AccessInfo;
public interface ICodeNode extends IDexNode, IAttributeNode, IUsageInfoNode, ICodeNodeRef {
ClassNode getDeclaringClass();
AccessInfo getAccessFlags();
void setAccessFlags(AccessInfo newAccessFlags);
@@ -95,10 +95,6 @@ public class InsnNode extends LineAttrNode {
return arguments;
}
public List<InsnArg> getArgList() {
return arguments;
}
public int getArgsCount() {
return arguments.size();
}
@@ -5,7 +5,6 @@ import java.util.Collections;
import java.util.List;
import java.util.Objects;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
@@ -325,11 +324,6 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
return mthInfo.getAlias();
}
@Override
public ClassNode getDeclaringClass() {
return parentClass;
}
public ClassNode getParentClass() {
return parentClass;
}
@@ -408,10 +402,6 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
return exitBlock.getPredecessors().contains(block);
}
public void resetLoops() {
this.loops = new ArrayList<>();
}
public void registerLoop(LoopInfo loop) {
if (loops.isEmpty()) {
loops = new ArrayList<>(5);
@@ -673,13 +663,6 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
return insnsCount;
}
/**
* Returns method code with comments and annotations
*/
public String getCodeStr() {
return CodeUtils.extractMethodCode(this, getTopParentClass().getCode());
}
@Override
public boolean isVarArg() {
return accFlags.isVarArgs();
@@ -705,7 +688,6 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
return javaNode;
}
@ApiStatus.Internal
public void setJavaNode(JavaMethod javaNode) {
this.javaNode = javaNode;
}
@@ -13,7 +13,6 @@ import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.DecompilationMode;
import jadx.api.ICodeCache;
import jadx.api.ICodeWriter;
import jadx.api.JadxArgs;
@@ -49,7 +48,6 @@ import jadx.core.dex.visitors.typeinference.TypeCompare;
import jadx.core.dex.visitors.typeinference.TypeUpdate;
import jadx.core.export.GradleInfoStorage;
import jadx.core.utils.CacheStorage;
import jadx.core.utils.DebugChecks;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.PassMerge;
import jadx.core.utils.StringUtils;
@@ -66,6 +64,10 @@ public class RootNode {
private static final Logger LOG = LoggerFactory.getLogger(RootNode.class);
private final JadxArgs args;
private final List<IDexTreeVisitor> preDecompilePasses;
private final List<ICodeDataUpdateListener> codeDataUpdateListeners = new ArrayList<>();
private final ProcessClass processClasses;
private final ErrorsCounter errorsCounter = new ErrorsCounter();
private final StringUtils stringUtils;
private final ConstStorage constValues;
@@ -76,7 +78,6 @@ public class RootNode {
private final TypeUtils typeUtils;
private final AttributeStorage attributes = new AttributeStorage();
private final List<ICodeDataUpdateListener> codeDataUpdateListeners = new ArrayList<>();
private final GradleInfoStorage gradleInfoStorage = new GradleInfoStorage();
private final Map<ClassInfo, ClassNode> clsMap = new HashMap<>();
@@ -86,24 +87,21 @@ public class RootNode {
private final Map<String, PackageNode> pkgMap = new HashMap<>();
private final List<PackageNode> packages = new ArrayList<>();
private List<IDexTreeVisitor> preDecompilePasses;
private ProcessClass processClasses;
private ClspGraph clsp;
private @Nullable String appPackage;
private @Nullable ClassNode appResClass;
@Nullable
private String appPackage;
@Nullable
private ClassNode appResClass;
/**
* Optional decompiler reference
*/
private @Nullable JadxDecompiler decompiler;
private @Nullable ManifestAttributes manifestAttributes;
public RootNode(JadxArgs args) {
this.args = args;
this.preDecompilePasses = Jadx.getPreDecompilePassesList();
this.processClasses = new ProcessClass(Jadx.getPassesList(args));
this.processClasses = new ProcessClass(args);
this.stringUtils = new StringUtils(args);
this.constValues = new ConstStorage(args);
this.typeUpdate = new TypeUpdate(this);
@@ -111,15 +109,6 @@ public class RootNode {
this.typeUtils = new TypeUtils(this);
}
public void init() {
if (args.isDeobfuscationOn() || !args.getRenameFlags().isEmpty()) {
args.getAliasProvider().init(this);
}
if (args.isDeobfuscationOn()) {
args.getRenameCondition().init(this);
}
}
public void loadClasses(List<ICodeLoader> loadedInputs) {
for (ICodeLoader codeLoader : loadedInputs) {
codeLoader.visitClasses(cls -> {
@@ -213,13 +202,18 @@ public class RootNode {
if (parser != null) {
processResources(parser.getResStorage());
updateObfuscatedFiles(parser, resources);
initManifestAttributes().updateAttributes(parser);
updateManifestAttribMap(parser);
}
} catch (Exception e) {
LOG.error("Failed to parse 'resources.pb'/'.arsc' file", e);
}
}
private void updateManifestAttribMap(IResTableParser parser) {
ManifestAttributes manifestAttributes = ManifestAttributes.getInstance();
manifestAttributes.updateAttributes(parser);
}
private @Nullable ResourceFile getResourceFile(List<ResourceFile> resources) {
for (ResourceFile rf : resources) {
if (rf.getType() == ResourceType.ARSC) {
@@ -313,21 +307,10 @@ public class RootNode {
}
public void mergePasses(Map<JadxPassType, List<JadxPass>> customPasses) {
DecompilationMode mode = args.getDecompilationMode();
if (mode == DecompilationMode.FALLBACK || mode == DecompilationMode.SIMPLE) {
// for predefined modes ignore custom (and plugin) passes
return;
}
new PassMerge(preDecompilePasses)
.merge(customPasses.get(JadxPreparePass.TYPE), p -> new PreparePassWrapper((JadxPreparePass) p));
new PassMerge(processClasses.getPasses())
.merge(customPasses.get(JadxDecompilePass.TYPE), p -> new DecompilePassWrapper((JadxDecompilePass) p));
if (args.isRunDebugChecks()) {
preDecompilePasses = DebugChecks.insertPasses(preDecompilePasses);
processClasses = new ProcessClass(DebugChecks.insertPasses(processClasses.getPasses()));
}
}
public void runPreDecompileStage() {
@@ -723,13 +706,4 @@ public class RootNode {
public GradleInfoStorage getGradleInfoStorage() {
return gradleInfoStorage;
}
public synchronized ManifestAttributes initManifestAttributes() {
ManifestAttributes attrs = manifestAttributes;
if (attrs == null) {
attrs = new ManifestAttributes(args.getSecurity());
manifestAttributes = attrs;
}
return attrs;
}
}
@@ -169,24 +169,6 @@ public class SignatureParser {
throw new JadxRuntimeException("Can't parse type: " + debugString() + ", unexpected: " + ch);
}
public List<ArgType> consumeTypeList() {
List<ArgType> list = null;
while (true) {
ArgType type = consumeType();
if (type == null) {
break;
}
if (list == null) {
list = new ArrayList<>();
}
list.add(type);
}
if (list == null) {
return Collections.emptyList();
}
return list;
}
private ArgType consumeObjectType(boolean innerType) {
mark();
int ch;
@@ -51,7 +51,7 @@ public class MethodUtils {
public MethodNode resolveMethod(BaseInvokeNode invokeNode) {
IMethodDetails methodDetails = getMethodDetails(invokeNode);
if (methodDetails instanceof MethodNode) {
return (MethodNode) methodDetails;
return ((MethodNode) methodDetails);
}
return null;
}
@@ -71,11 +71,7 @@ public class CheckCode extends AbstractVisitor {
}
insnNode.getRegisterArgs(list);
for (RegisterArg arg : list) {
int regNum = arg.getRegNum();
if (regNum < 0) {
throw new JadxRuntimeException("Incorrect negative register number in instruction: " + insnNode);
}
if (regNum >= regsCount) {
if (arg.getRegNum() >= regsCount) {
throw new JadxRuntimeException("Incorrect register number in instruction: " + insnNode
+ ", expected to be less than " + regsCount);
}
@@ -32,7 +32,6 @@ import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.visitors.fixaccessmodifiers.FixAccessModifiers;
import jadx.core.dex.visitors.usage.UsageInfoVisitor;
import jadx.core.utils.BlockUtils;
import jadx.core.utils.InsnRemover;
@@ -1,18 +1,11 @@
package jadx.core.dex.visitors;
import java.util.List;
import org.jetbrains.annotations.Nullable;
import jadx.core.codegen.TypeGen;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.info.ClassInfo;
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.PhiInsn;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.RegisterArg;
@@ -25,7 +18,6 @@ import jadx.core.dex.visitors.ssa.SSATransform;
import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor;
import jadx.core.utils.BlockUtils;
import jadx.core.utils.InsnRemover;
import jadx.core.utils.exceptions.JadxRuntimeException;
@JadxVisitor(
name = "ConstructorVisitor",
@@ -42,6 +34,9 @@ public class ConstructorVisitor extends AbstractVisitor {
if (replaceInvoke(mth)) {
MoveInlineVisitor.moveInline(mth);
}
if (mth.contains(AFlag.RERUN_SSA_TRANSFORM)) {
SSATransform.rerun(mth);
}
}
private static boolean replaceInvoke(MethodNode mth) {
@@ -63,32 +58,24 @@ public class ConstructorVisitor extends AbstractVisitor {
private static boolean processInvoke(MethodNode mth, BlockNode block, int indexInBlock, InsnRemover remover) {
InvokeNode inv = (InvokeNode) block.getInstructions().get(indexInBlock);
MethodInfo callMth = inv.getCallMth();
if (!callMth.isConstructor()) {
if (!inv.getCallMth().isConstructor()) {
return false;
}
ArgType instType = searchInstanceType(inv);
if (instType != null && !instType.equals(callMth.getDeclClass().getType())) {
ClassInfo instCls = ClassInfo.fromType(mth.root(), instType);
callMth = MethodInfo.fromDetails(mth.root(), instCls, callMth.getName(),
callMth.getArgumentsTypes(), callMth.getReturnType());
}
ConstructorInsn co = new ConstructorInsn(mth, inv, callMth);
ConstructorInsn co = new ConstructorInsn(mth, inv);
if (canRemoveConstructor(mth, co)) {
remover.addAndUnbind(inv);
return false;
}
co.inheritMetadata(inv);
RegisterArg instanceArg = (RegisterArg) inv.getArg(0);
RegisterArg instanceArg = ((RegisterArg) inv.getArg(0));
instanceArg.getSVar().removeUse(instanceArg);
if (co.isNewInstance()) {
InsnNode assignInsn = instanceArg.getAssignInsn();
if (assignInsn != null) {
if (assignInsn.getType() == InsnType.CONSTRUCTOR) {
// arg already used in another constructor instruction
// insert new PHI insn to merge these branched constructors results
instanceArg = insertPhiInsn(mth, block, instanceArg, ((ConstructorInsn) assignInsn));
mth.add(AFlag.RERUN_SSA_TRANSFORM);
} else {
InsnNode newInstInsn = removeAssignChain(mth, assignInsn, remover, InsnType.NEW_INSTANCE);
if (newInstInsn != null) {
@@ -112,56 +99,6 @@ public class ConstructorVisitor extends AbstractVisitor {
return true;
}
private static @Nullable ArgType searchInstanceType(InvokeNode inv) {
InsnArg instanceArg = inv.getInstanceArg();
if (instanceArg == null || !instanceArg.isRegister()) {
return null;
}
InsnNode assignInsn = ((RegisterArg) instanceArg).getSVar().getAssignInsn();
if (assignInsn == null || assignInsn.getType() != InsnType.NEW_INSTANCE) {
return null;
}
return ((IndexInsnNode) assignInsn).getIndexAsType();
}
private static RegisterArg insertPhiInsn(MethodNode mth, BlockNode curBlock,
RegisterArg instArg, ConstructorInsn otherCtr) {
BlockNode otherBlock = BlockUtils.getBlockByInsn(mth, otherCtr);
if (otherBlock == null) {
throw new JadxRuntimeException("Block not found by insn: " + otherCtr);
}
BlockNode crossBlock = BlockUtils.getPathCross(mth, curBlock, otherBlock);
if (crossBlock == null) {
// no path cross => PHI insn not needed
// use new SSA var on usage from current path
RegisterArg newResArg = instArg.duplicateWithNewSSAVar(mth);
List<BlockNode> pathBlocks = BlockUtils.collectAllSuccessors(mth, curBlock, true);
for (RegisterArg useReg : instArg.getSVar().getUseList()) {
InsnNode parentInsn = useReg.getParentInsn();
if (parentInsn != null) {
BlockNode useBlock = BlockUtils.getBlockByInsn(mth, parentInsn, pathBlocks);
if (useBlock != null) {
parentInsn.replaceArg(useReg, newResArg.duplicate());
}
}
}
return newResArg;
}
RegisterArg newResArg = instArg.duplicateWithNewSSAVar(mth);
RegisterArg useArg = otherCtr.getResult();
RegisterArg otherResArg = useArg.duplicateWithNewSSAVar(mth);
PhiInsn phiInsn = SSATransform.addPhi(mth, crossBlock, useArg.getRegNum());
phiInsn.setResult(useArg.duplicate());
phiInsn.bindArg(newResArg.duplicate(), BlockUtils.getPrevBlockOnPath(mth, crossBlock, curBlock));
phiInsn.bindArg(otherResArg.duplicate(), BlockUtils.getPrevBlockOnPath(mth, crossBlock, otherBlock));
phiInsn.rebindArgs();
otherCtr.setResult(otherResArg.duplicate());
otherCtr.rebindArgs();
return newResArg;
}
private static boolean canRemoveConstructor(MethodNode mth, ConstructorInsn co) {
ClassNode parentClass = mth.getParentClass();
if (co.isSuper() && (co.getArgsCount() == 0 || parentClass.isEnum())) {
@@ -3,6 +3,7 @@ package jadx.core.dex.visitors;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.DebugChecks;
public class DepthTraversal {
@@ -23,6 +24,9 @@ public class DepthTraversal {
return;
}
visitor.visit(mth);
if (DebugChecks.checksEnabled) {
DebugChecks.runChecksAfterVisitor(mth, visitor);
}
} catch (StackOverflowError | Exception e) {
mth.addError(e.getClass().getSimpleName() + " in pass: " + visitor.getClass().getSimpleName(), e);
}
@@ -57,11 +57,6 @@ public class DotGraphVisitor extends AbstractVisitor {
this.rawInsn = rawInsn;
}
@Override
public String getName() {
return "DotGraphVisitor";
}
@Override
public void visit(MethodNode mth) {
if (mth.isNoCode()) {
@@ -1,12 +1,8 @@
package jadx.core.dex.visitors.fixaccessmodifiers;
import java.util.Set;
import java.util.stream.Collectors;
package jadx.core.dex.visitors;
import jadx.api.plugins.input.data.AccessFlags;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.MethodInlineAttr;
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
import jadx.core.dex.attributes.nodes.NotificationAttrNode;
import jadx.core.dex.info.AccessInfo;
@@ -14,9 +10,6 @@ import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.IMethodDetails;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.AbstractVisitor;
import jadx.core.dex.visitors.JadxVisitor;
import jadx.core.dex.visitors.ModVisitor;
import jadx.core.utils.exceptions.JadxException;
@JadxVisitor(
@@ -26,13 +19,10 @@ import jadx.core.utils.exceptions.JadxException;
)
public class FixAccessModifiers extends AbstractVisitor {
private VisibilityUtils visibilityUtils;
private boolean respectAccessModifiers;
@Override
public void init(RootNode root) {
this.visibilityUtils = new VisibilityUtils(root);
this.respectAccessModifiers = root.getArgs().isRespectBytecodeAccModifiers();
}
@@ -41,8 +31,10 @@ public class FixAccessModifiers extends AbstractVisitor {
if (respectAccessModifiers) {
return true;
}
fixClassVisibility(cls);
int newVisFlag = fixClassVisibility(cls);
if (newVisFlag != -1) {
changeVisibility(cls, newVisFlag);
}
return true;
}
@@ -51,62 +43,9 @@ public class FixAccessModifiers extends AbstractVisitor {
if (respectAccessModifiers || mth.contains(AFlag.DONT_GENERATE)) {
return;
}
fixMethodVisibility(mth);
}
private void fixClassVisibility(ClassNode cls) {
AccessInfo accessFlags = cls.getAccessFlags();
if (cls.isTopClass() && accessFlags.isPublic()) {
return;
}
if (cls.isTopClass() && (accessFlags.isPrivate() || accessFlags.isProtected())) {
changeVisibility(cls, AccessFlags.PUBLIC);
return;
}
for (ClassNode useCls : cls.getUseIn()) {
visibilityUtils.checkVisibility(cls, useCls, (node, visFlag) -> {
changeVisibility((NotificationAttrNode) node, visFlag);
});
}
for (MethodNode useMth : cls.getUseInMth()) {
MethodInlineAttr inlineAttr = useMth.get(AType.METHOD_INLINE);
boolean isInline = inlineAttr != null && !inlineAttr.notNeeded();
boolean isCandidateForInline = useMth.contains(AFlag.METHOD_CANDIDATE_FOR_INLINE);
if (isInline || isCandidateForInline) {
Set<ClassNode> usedInClss = useMth.getUseIn().stream()
.map(MethodNode::getParentClass)
.collect(Collectors.toSet());
for (ClassNode useCls : usedInClss) {
visibilityUtils.checkVisibility(cls, useCls, (node, visFlag) -> {
changeVisibility((NotificationAttrNode) node, visFlag);
});
}
}
}
}
private void fixMethodVisibility(MethodNode mth) {
AccessInfo accessFlags = mth.getAccessFlags();
MethodOverrideAttr overrideAttr = mth.get(AType.METHOD_OVERRIDE);
if (overrideAttr != null && !overrideAttr.getOverrideList().isEmpty()) {
// visibility can't be weaker
IMethodDetails parentMD = overrideAttr.getOverrideList().get(0);
AccessInfo parentAccInfo = new AccessInfo(parentMD.getRawAccessFlags(), AccessInfo.AFType.METHOD);
if (accessFlags.isVisibilityWeakerThan(parentAccInfo)) {
changeVisibility(mth, parentAccInfo.getVisibility().rawValue());
}
}
for (MethodNode useMth : mth.getUseIn()) {
visibilityUtils.checkVisibility(mth, useMth, (node, visFlag) -> {
changeVisibility((NotificationAttrNode) node, visFlag);
});
int newVisFlag = fixMethodVisibility(mth);
if (newVisFlag != -1) {
changeVisibility(mth, newVisFlag);
}
}
@@ -118,4 +57,69 @@ public class FixAccessModifiers extends AbstractVisitor {
node.addInfoComment("Access modifiers changed from: " + accessFlags.visibilityName());
}
}
private int fixClassVisibility(ClassNode cls) {
if (cls.getUseIn().isEmpty()) {
return -1;
}
AccessInfo accessFlags = cls.getAccessFlags();
if (accessFlags.isPrivate()) {
if (!cls.isInner()) {
return AccessFlags.PUBLIC;
}
// check if private inner class is used outside
ClassNode topParentClass = cls.getTopParentClass();
for (ClassNode useCls : cls.getUseIn()) {
if (useCls.getTopParentClass() != topParentClass) {
return AccessFlags.PUBLIC;
}
}
}
if (accessFlags.isPackagePrivate()) {
String pkg = cls.getPackage();
for (ClassNode useCls : cls.getUseIn()) {
if (!useCls.getPackage().equals(pkg)) {
return AccessFlags.PUBLIC;
}
}
}
if (!accessFlags.isPublic()) {
// if class is used in inlinable method => make it public
for (MethodNode useMth : cls.getUseInMth()) {
boolean canInline = useMth.contains(AFlag.METHOD_CANDIDATE_FOR_INLINE) || useMth.contains(AType.METHOD_INLINE);
if (canInline && !useMth.getUseIn().isEmpty()) {
return AccessFlags.PUBLIC;
}
}
}
return -1;
}
private static int fixMethodVisibility(MethodNode mth) {
AccessInfo accessFlags = mth.getAccessFlags();
if (accessFlags.isPublic()) {
return -1;
}
MethodOverrideAttr overrideAttr = mth.get(AType.METHOD_OVERRIDE);
if (overrideAttr != null && !overrideAttr.getOverrideList().isEmpty()) {
// visibility can't be weaker
IMethodDetails parentMD = overrideAttr.getOverrideList().get(0);
AccessInfo parentAccInfo = new AccessInfo(parentMD.getRawAccessFlags(), AccessInfo.AFType.METHOD);
if (accessFlags.isVisibilityWeakerThan(parentAccInfo)) {
return parentAccInfo.getVisibility().rawValue();
}
}
if (mth.getUseIn().isEmpty()) {
return -1;
}
ClassNode thisTopParentCls = mth.getParentClass().getTopParentClass();
for (MethodNode useMth : mth.getUseIn()) {
ClassNode useInTPCls = useMth.getParentClass().getTopParentClass();
if (!useInTPCls.equals(thisTopParentCls)) {
return AccessFlags.PUBLIC;
}
}
return -1;
}
}
@@ -59,17 +59,12 @@ public class InlineMethods extends AbstractVisitor {
}
MethodNode callMth = (MethodNode) callMthDetails;
try {
// TODO: sort inner classes process order by dependencies!
MethodInlineAttr mia = MarkMethodsForInline.process(callMth);
if (mia == null) {
// method is not yet loaded => force process
mth.addDebugComment("Class process forced to load method for inline: " + callMth);
mth.root().getProcessClasses().forceProcess(callMth.getParentClass());
// run check again
mia = MarkMethodsForInline.process(callMth);
if (mia == null) {
mth.addWarnComment("Failed to check method for inline after forced process" + callMth);
return;
}
// method not yet loaded => will retry at codegen stage
callMth.getParentClass().reloadAtCodegenStage();
return;
}
if (mia.notNeeded()) {
return;
@@ -19,9 +19,7 @@ import jadx.core.dex.instructions.args.InsnWrapArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.visitors.fixaccessmodifiers.FixAccessModifiers;
import jadx.core.utils.BlockUtils;
import jadx.core.utils.ListUtils;
import jadx.core.utils.exceptions.JadxException;
@JadxVisitor(
@@ -44,22 +42,18 @@ public class MarkMethodsForInline extends AbstractVisitor {
*/
@Nullable
public static MethodInlineAttr process(MethodNode mth) {
try {
MethodInlineAttr mia = mth.get(AType.METHOD_INLINE);
if (mia != null) {
return mia;
MethodInlineAttr mia = mth.get(AType.METHOD_INLINE);
if (mia != null) {
return mia;
}
if (mth.contains(AFlag.METHOD_CANDIDATE_FOR_INLINE)) {
if (mth.getBasicBlocks() == null) {
return null;
}
if (mth.contains(AFlag.METHOD_CANDIDATE_FOR_INLINE)) {
if (mth.getBasicBlocks() == null) {
return null;
}
MethodInlineAttr inlined = inlineMth(mth);
if (inlined != null) {
return inlined;
}
MethodInlineAttr inlined = inlineMth(mth);
if (inlined != null) {
return inlined;
}
} catch (Exception e) {
mth.addWarnComment("Method inline analysis failed", e);
}
return MethodInlineAttr.inlineNotNeeded(mth);
}
@@ -119,12 +113,9 @@ public class MarkMethodsForInline extends AbstractVisitor {
&& firstInsn.getArg(0).isSameVar(mthRegs.get(0));
case INVOKE:
if (!retInsn.getArg(0).isSameVar(firstInsn.getResult())) {
return false;
}
return ListUtils.orderedEquals(
mth.getArgRegs(), firstInsn.getArgList(),
(mthArg, insnArg) -> insnArg.isSameVar(mthArg));
return !mthRegs.isEmpty()
&& firstInsn.getArg(0).isSameVar(mthRegs.get(0))
&& retInsn.getArg(0).isSameVar(firstInsn.getResult());
default:
return false;
}
@@ -187,7 +187,7 @@ public class MethodInvokeVisitor extends AbstractVisitor {
arg.setType(castType);
arg.add(AFlag.EXPLICIT_PRIMITIVE_TYPE);
} else if (InsnUtils.isWrapped(arg, InsnType.CHECK_CAST)) {
IndexInsnNode wrapInsn = (IndexInsnNode) ((InsnWrapArg) arg).getWrapInsn();
IndexInsnNode wrapInsn = ((IndexInsnNode) ((InsnWrapArg) arg).getWrapInsn());
wrapInsn.updateIndex(castType);
} else {
if (Consts.DEBUG_TYPE_INFERENCE) {

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