Compare commits
227 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 381afa2741 | |||
| 82d4099541 | |||
| 5f659c8de7 | |||
| e054ea6683 | |||
| 0deafb768b | |||
| cd612b452c | |||
| 009939f866 | |||
| cd006ce78e | |||
| 71bf2aa59f | |||
| 714b935474 | |||
| 2a2b83a695 | |||
| acdaa95854 | |||
| 278c5f6142 | |||
| 8ca3cd3155 | |||
| 2b7d7ce2cf | |||
| a22efc2eb6 | |||
| 804c8eff91 | |||
| aec8ebe237 | |||
| 7353790ed1 | |||
| e09e8e5823 | |||
| 92773417b3 | |||
| 12dc4fde8a | |||
| d1e5186d4a | |||
| d06ba95374 | |||
| f0e6c8ea8e | |||
| c94c204da2 | |||
| 71617a1c70 | |||
| 9f684937c6 | |||
| ff6665c716 | |||
| aa8fd3c861 | |||
| e2b42804d5 | |||
| 0f6e942c5b | |||
| c0a81978bf | |||
| b76c882210 | |||
| 14cbfbc5a4 | |||
| 9b1761f71f | |||
| 73ca2e0fa4 | |||
| 4e4c7f7d7b | |||
| 33f2c3f220 | |||
| dcca0133fb | |||
| 408201b69b | |||
| e024628d46 | |||
| 6428f29373 | |||
| cfaa6ab6df | |||
| 91ee7565ac | |||
| 1bbcac2ab3 | |||
| 60b2353afe | |||
| 50cfa4c971 | |||
| 691bf8faca | |||
| 89b4ae6a6f | |||
| 605a67932f | |||
| 1774dc74e3 | |||
| 2d641bf049 | |||
| 94a06d9b6f | |||
| a485942731 | |||
| 2c1b3b2480 | |||
| f1f7c70aee | |||
| 718caf8cb1 | |||
| 545cd4ec12 | |||
| 444ea9ec7e | |||
| 13609a5c44 | |||
| d6ad21f6f9 | |||
| 593a32a689 | |||
| 7fed5534eb | |||
| 1560284831 | |||
| 558a86739f | |||
| bfd60b733a | |||
| ae26512601 | |||
| 498c2f5256 | |||
| 459f12d61f | |||
| bcd6e537e0 | |||
| 867c3413e9 | |||
| 0f808d5c60 | |||
| f5767dd865 | |||
| 631a855bac | |||
| c616b5b03b | |||
| 3e9f4a5060 | |||
| 31434186ab | |||
| e81ba1c431 | |||
| b219ab607f | |||
| cd8307f432 | |||
| a720105208 | |||
| 34692c41f2 | |||
| 8a8b945eb8 | |||
| 99569c52ac | |||
| f696dc715b | |||
| 21b8552386 | |||
| 4b1886700d | |||
| a83ca1f85b | |||
| 65553c156c | |||
| 440357d2e8 | |||
| 5e62b9077a | |||
| 6192ced214 | |||
| ae31fee8dd | |||
| e7b00cc76e | |||
| 7d29c5d766 | |||
| 15776c4ce3 | |||
| d720179deb | |||
| 0d69e0ac97 | |||
| 09e267f8bc | |||
| 705ceca42a | |||
| 58722d372e | |||
| f482b8b95c | |||
| 21e4dee0e2 | |||
| c7a12ad75b | |||
| 7cd77ae379 | |||
| d59c99ddfe | |||
| 85760cc844 | |||
| 0692464b85 | |||
| 3968222744 | |||
| c05368d92e | |||
| 404136cd72 | |||
| b1d5ed0066 | |||
| e22474e0a7 | |||
| 45b7304630 | |||
| 692536c584 | |||
| 4e3d85887c | |||
| 87852328da | |||
| 2207cd7b52 | |||
| f3cd4e38d7 | |||
| 2dce1c0ad9 | |||
| 258ecad277 | |||
| 7f5092c0d5 | |||
| a7f315f596 | |||
| 4dc4aa122b | |||
| e3f388af11 | |||
| 83196628c9 | |||
| 315c07d4f6 | |||
| 47dadf0a43 | |||
| c62039f327 | |||
| a5ea560edc | |||
| e09e933f9c | |||
| dbd00d5a8b | |||
| 2da772df8e | |||
| 4cdad0e83e | |||
| 2f780da305 | |||
| 9d8066f4b8 | |||
| 2cc49256a9 | |||
| 79ab2e11f8 | |||
| c1f4302e62 | |||
| f66ec9168c | |||
| 37aecf72cb | |||
| 3c7be5e9be | |||
| 89dbae8f8e | |||
| 5eec8f754d | |||
| 49a82c8388 | |||
| 26bad4a1cd | |||
| fa0a38d3aa | |||
| b56fd4d29a | |||
| 4520747167 | |||
| e444ecb2c7 | |||
| 1336c47d18 | |||
| 519a74e8d2 | |||
| dea7714ef3 | |||
| 74b88b407e | |||
| 57c28c61e0 | |||
| 87320348dd | |||
| fcb70e69c1 | |||
| 4859629850 | |||
| bd0d248fd0 | |||
| c24a3edb44 | |||
| d0f197ea3d | |||
| 5502d93cd5 | |||
| 073fd76aa2 | |||
| 492a3f6928 | |||
| 1ce8fa8bdd | |||
| 1bb90233b9 | |||
| 49ce92f540 | |||
| 2107da2e1a | |||
| d98321026d | |||
| 0b6fabbc71 | |||
| bb0fad2834 | |||
| 08f9722e33 | |||
| 62ca30bbc6 | |||
| 467403362d | |||
| 265a78cd23 | |||
| a0e13d0481 | |||
| 77fc6435a0 | |||
| cd7e5bf020 | |||
| 5e7388f686 | |||
| 1047e751e6 | |||
| c598871764 | |||
| 2921c66834 | |||
| 7bbb083c36 | |||
| 531650c9f2 | |||
| f3098741c3 | |||
| 9dbffef140 | |||
| c97e504686 | |||
| 0c4b807caa | |||
| 1eca2b6cb0 | |||
| 17cbb3eab0 | |||
| c72f2a2c96 | |||
| 610f531653 | |||
| 1e9b28b369 | |||
| 6d4caca6cc | |||
| 15953f832f | |||
| d346ed0570 | |||
| df520a1134 | |||
| f90fc1d5ec | |||
| 797904afb5 | |||
| 489fbb5e42 | |||
| 9dd5a9ef89 | |||
| 02213802c5 | |||
| 8365855475 | |||
| 55eb86d2d5 | |||
| 0a9b944431 | |||
| f1e229193c | |||
| 04e309aeff | |||
| 99eb31b312 | |||
| 287275d886 | |||
| af6f8b5391 | |||
| 3b9b103c3f | |||
| 0c55ab9001 | |||
| 9c88f70740 | |||
| 9ab003df4c | |||
| 7f8d03d192 | |||
| c64ffde11f | |||
| 1568008c67 | |||
| 84211576e4 | |||
| 553f5b063f | |||
| f5d1f288d0 | |||
| a2df92dd68 | |||
| 1c6e51f8b2 | |||
| ef5da49bc0 | |||
| 7545625af4 | |||
| e3055b95f6 | |||
| 78eed8629c |
@@ -13,10 +13,8 @@ assignees: ''
|
||||
- search existing issues by exception message
|
||||
|
||||
**Describe error**
|
||||
- provide full name of method or class with error
|
||||
- provide full java stacktrace
|
||||
|
||||
**Note**: no need to copy method fallback code (commented pseudocode)
|
||||
- attach or provide link to apk file (double check apk version)
|
||||
- full name of method or class with error
|
||||
- full java stacktrace (no need to copy method fallback code (commented pseudocode))
|
||||
- **IMPORTANT!** attach or provide link to apk file (double check apk version)
|
||||
|
||||
**Note**: GitHub don't allow attach files with `.apk` extension, but you can change extension by adding `.zip` at the end :)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
:exclamation: Please review the [guidelines for contributing](../CONTRIBUTING.md#Pull-Request-Process)
|
||||
:exclamation: Please review the [guidelines for contributing](https://github.com/skylot/jadx/blob/master/CONTRIBUTING.md#Pull-Request-Process)
|
||||
|
||||
### Description
|
||||
Please describe your pull request.
|
||||
Reference issue it fix.
|
||||
Reference issue it fixes.
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
name: "Validate Gradle Wrapper"
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
validation:
|
||||
name: "Validation"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: gradle/wrapper-validation-action@v1
|
||||
+3
-1
@@ -5,6 +5,7 @@
|
||||
|
||||
# IntelliJ Idea files
|
||||
.idea/
|
||||
.run/
|
||||
out/
|
||||
*.iml
|
||||
*.ipr
|
||||
@@ -24,8 +25,9 @@ node_modules/
|
||||
|
||||
jadx-output/
|
||||
*-tmp/
|
||||
**/tmp/
|
||||
*.jobf
|
||||
|
||||
*.dex
|
||||
*.class
|
||||
*.dump
|
||||
*.log
|
||||
|
||||
+5
-18
@@ -7,31 +7,18 @@ before_script:
|
||||
|
||||
stages:
|
||||
- test
|
||||
- check
|
||||
|
||||
java-8:
|
||||
stage: test
|
||||
image: openjdk:8
|
||||
script: ./gradlew clean build
|
||||
script: ./gradlew clean build dist
|
||||
|
||||
java-11:
|
||||
stage: test
|
||||
image: openjdk:11
|
||||
script: ./gradlew clean build
|
||||
script: ./gradlew clean build dist
|
||||
|
||||
java-12:
|
||||
java-latest:
|
||||
stage: test
|
||||
image: gradle:jdk12 # latest gradle and jdk12
|
||||
script: gradle clean build
|
||||
|
||||
check:
|
||||
stage: check
|
||||
image: openjdk:8
|
||||
script:
|
||||
- export JADX_LAST_TAG="$(git describe --abbrev=0 --tags)"
|
||||
- export JADX_VERSION="${JADX_LAST_TAG:1}-dev-$(git rev-parse --short HEAD)"
|
||||
- ./gradlew clean sonarqube -Dsonar.host.url="${SONAR_HOST}" -Dsonar.projectKey=jadx -Dsonar.organization="${SONAR_ORG}" -Dsonar.login="${SONAR_TOKEN}" -Dsonar.branch.name=dev || echo "Skip sonar build and upload"
|
||||
- ./gradlew clean dist
|
||||
artifacts:
|
||||
paths:
|
||||
- build/jadx*.zip
|
||||
image: openjdk:latest
|
||||
script: java -version && ./gradlew clean build dist
|
||||
|
||||
+3
-1
@@ -1,4 +1,6 @@
|
||||
branch: release
|
||||
branches:
|
||||
- release
|
||||
|
||||
verifyConditions:
|
||||
- '@semantic-release/github'
|
||||
|
||||
|
||||
@@ -28,6 +28,11 @@ script: ./gradlew clean build
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- stage: checks
|
||||
jdk: openjdk11
|
||||
if: branch = master AND repo = env(MAIN_REPO) AND type = push
|
||||
script: bash scripts/travis-checks.sh
|
||||
|
||||
- stage: deploy-unstable
|
||||
jdk: openjdk8
|
||||
if: branch = master AND repo = env(MAIN_REPO) AND type = push
|
||||
|
||||
+5
-6
@@ -10,11 +10,10 @@ Please note we have a [code of conduct](CODE_OF_CONDUCT.md), please follow it in
|
||||
- search existing issues by exception message
|
||||
|
||||
2. Describe error
|
||||
- provide full name of method or class with error
|
||||
- provide full java stacktrace
|
||||
|
||||
**Note**: no need to copy method fallback code (commented pseudocode)
|
||||
- attach or provide link to apk file (double check apk version)
|
||||
**Describe error**
|
||||
- full name of method or class with error
|
||||
- full java stacktrace (no need to copy method fallback code (commented pseudocode))
|
||||
- **IMPORTANT!:** attach or provide link to apk file (double check apk version)
|
||||
|
||||
**Note**: GitHub don't allow attach files with `.apk` extension, but you can change extension by adding `.zip` at the end :)
|
||||
|
||||
@@ -23,7 +22,7 @@ Please note we have a [code of conduct](CODE_OF_CONDUCT.md), please follow it in
|
||||
|
||||
1. Please don't submit any code style fixes, dependencies updates or other changes which are not fixing any issues.
|
||||
|
||||
1. Before open a PR please discuss the change you wish to make via issue. PR without corresponding issue will be rejected.
|
||||
1. Before start working on PR please discuss the change you wish to make via issue. PR without corresponding issue will be rejected.
|
||||
|
||||
1. Use only features and API from Java 8 or below.
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<img src="https://raw.githubusercontent.com/skylot/jadx/master/jadx-gui/src/main/resources/logos/jadx-logo.png" width="64" align="left" />
|
||||
|
||||
## JADX
|
||||
|
||||
[](https://travis-ci.org/skylot/jadx)
|
||||
[](https://travis-ci.com/skylot/jadx)
|
||||
[](https://codecov.io/gh/skylot/jadx)
|
||||
[](https://lgtm.com/projects/g/skylot/jadx/alerts/)
|
||||
[](https://sonarcloud.io/dashboard?id=jadx)
|
||||
@@ -9,7 +11,7 @@
|
||||
|
||||
**jadx** - Dex to Java decompiler
|
||||
|
||||
Command line and GUI tools for produce Java source code from Android Dex and Apk files
|
||||
Command line and GUI tools for producing Java source code from Android Dex and Apk files
|
||||
|
||||
**Main features:**
|
||||
- decompile Dalvik bytecode to java classes from APK, dex, aar and zip files
|
||||
@@ -121,5 +123,3 @@ To support this project you can:
|
||||
|
||||
---------------------------------------
|
||||
*Licensed under the Apache 2.0 License*
|
||||
|
||||
*Copyright 2019 by Skylot*
|
||||
|
||||
+33
-24
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'org.sonarqube' version '2.8'
|
||||
id 'com.github.ben-manes.versions' version '0.27.0'
|
||||
id "com.diffplug.gradle.spotless" version "3.26.0"
|
||||
id 'org.sonarqube' version '3.0'
|
||||
id 'com.github.ben-manes.versions' version '0.33.0'
|
||||
id "com.diffplug.spotless" version "5.5.1"
|
||||
}
|
||||
|
||||
ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev"
|
||||
@@ -15,6 +15,9 @@ allprojects {
|
||||
|
||||
version = jadxVersion
|
||||
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
if (!"$it".contains(':jadx-samples:')) {
|
||||
options.compilerArgs << '-Xlint' << '-Xlint:unchecked' << '-Xlint:deprecation'
|
||||
@@ -26,24 +29,25 @@ allprojects {
|
||||
}
|
||||
|
||||
jar {
|
||||
version = jadxVersion
|
||||
manifest {
|
||||
mainAttributes('jadx-version': jadxVersion)
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'org.slf4j:slf4j-api:1.7.29'
|
||||
implementation 'org.slf4j:slf4j-api:1.7.30'
|
||||
compileOnly 'org.jetbrains:annotations:20.1.0'
|
||||
|
||||
testCompile 'ch.qos.logback:logback-classic:1.2.3'
|
||||
testCompile 'org.hamcrest:hamcrest-library:2.2'
|
||||
testCompile 'org.mockito:mockito-core:3.1.0'
|
||||
testCompile 'org.assertj:assertj-core:3.14.0'
|
||||
testImplementation 'ch.qos.logback:logback-classic:1.2.3'
|
||||
testImplementation 'org.hamcrest:hamcrest-library:2.2'
|
||||
testImplementation 'org.mockito:mockito-core:3.5.10'
|
||||
testImplementation 'org.assertj:assertj-core:3.17.2'
|
||||
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.5.2'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.5.2'
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0'
|
||||
|
||||
testCompile 'org.eclipse.jdt.core.compiler:ecj:4.6.1'
|
||||
testImplementation 'org.eclipse.jdt.core.compiler:ecj:4.6.1'
|
||||
testCompileOnly 'org.jetbrains:annotations:20.1.0'
|
||||
}
|
||||
|
||||
test {
|
||||
@@ -58,7 +62,7 @@ allprojects {
|
||||
}
|
||||
|
||||
jacoco {
|
||||
toolVersion = "0.8.2"
|
||||
toolVersion = "0.8.6"
|
||||
}
|
||||
jacocoTestReport {
|
||||
reports {
|
||||
@@ -86,6 +90,7 @@ spotless {
|
||||
include 'jadx-cli/src/**/java/**/*.java'
|
||||
include 'jadx-core/src/**/java/**/*.java'
|
||||
include 'jadx-gui/src/**/java/**/*.java'
|
||||
include 'jadx-plugins/**/java/**/*.java'
|
||||
}
|
||||
|
||||
importOrderFile 'config/code-formatter/eclipse.importorder'
|
||||
@@ -99,7 +104,7 @@ spotless {
|
||||
}
|
||||
format 'misc', {
|
||||
target '**/*.gradle', '**/*.md', '**/*.xml', '**/.gitignore', '**/.properties'
|
||||
targetExclude ".gradle/**", ".idea/**"
|
||||
targetExclude ".gradle/**", ".idea/**", "*/build/**"
|
||||
|
||||
lineEndings(com.diffplug.spotless.LineEnding.UNIX)
|
||||
encoding("UTF-8")
|
||||
@@ -108,14 +113,16 @@ spotless {
|
||||
}
|
||||
}
|
||||
|
||||
dependencyUpdates.resolutionStrategy = {
|
||||
componentSelection { rules ->
|
||||
rules.all { ComponentSelection selection ->
|
||||
boolean rejected = ['alpha', 'beta', 'rc', 'cr', 'm', 'atlassian'].any { qualifier ->
|
||||
selection.candidate.version ==~ /(?i).*[.-]${qualifier}[.\d-]*/
|
||||
}
|
||||
if (rejected) {
|
||||
selection.reject('Release candidate')
|
||||
dependencyUpdates {
|
||||
resolutionStrategy {
|
||||
componentSelection { rules ->
|
||||
rules.all { ComponentSelection selection ->
|
||||
boolean rejected = ['alpha', 'beta', 'rc', 'cr', 'm', 'atlassian'].any { qualifier ->
|
||||
selection.candidate.version ==~ /(?i).*[.-]${qualifier}[.\d-]*/
|
||||
}
|
||||
if (rejected) {
|
||||
selection.reject('Release candidate')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -126,11 +133,12 @@ task copyArtifacts(type: Sync, dependsOn: ['jadx-cli:installDist', 'jadx-gui:ins
|
||||
['jadx-cli', 'jadx-gui'].each {
|
||||
from tasks.getByPath(":${it}:installDist").destinationDir
|
||||
}
|
||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
}
|
||||
|
||||
task pack(type: Zip, dependsOn: copyArtifacts) {
|
||||
destinationDir buildDir
|
||||
archiveName "jadx-${jadxVersion}.zip"
|
||||
destinationDirectory = buildDir
|
||||
archiveFileName = "jadx-${jadxVersion}.zip"
|
||||
from copyArtifacts.destinationDir
|
||||
}
|
||||
|
||||
@@ -140,6 +148,7 @@ task copyExe(type: Copy, dependsOn: 'jadx-gui:createExe') {
|
||||
destinationDir buildDir
|
||||
from tasks.getByPath('jadx-gui:createExe').outputs
|
||||
include '*.exe'
|
||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
}
|
||||
|
||||
task dist(dependsOn: [pack, copyExe]) {
|
||||
|
||||
Vendored
BIN
Binary file not shown.
+1
-1
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
@@ -82,6 +82,7 @@ esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
@@ -129,6 +130,7 @@ fi
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
|
||||
Vendored
+7
-18
@@ -29,6 +29,9 @@ if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@@ -37,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
@@ -51,7 +54,7 @@ goto fail
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
@@ -61,28 +64,14 @@ echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
|
||||
@@ -3,9 +3,14 @@ plugins {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile(project(':jadx-core'))
|
||||
compile 'com.beust:jcommander:1.78'
|
||||
compile 'ch.qos.logback:logback-classic:1.2.3'
|
||||
implementation(project(':jadx-core'))
|
||||
|
||||
runtimeOnly(project(':jadx-plugins:jadx-dex-input'))
|
||||
runtimeOnly(project(':jadx-plugins:jadx-smali-input'))
|
||||
runtimeOnly(project(':jadx-plugins:jadx-java-convert'))
|
||||
|
||||
implementation 'com.beust:jcommander:1.80'
|
||||
implementation 'ch.qos.logback:logback-classic:1.2.3'
|
||||
}
|
||||
|
||||
application {
|
||||
|
||||
@@ -7,6 +7,7 @@ import jadx.api.JadxArgs;
|
||||
import jadx.api.JadxDecompiler;
|
||||
import jadx.api.impl.NoOpCodeCache;
|
||||
import jadx.core.utils.exceptions.JadxArgsValidateException;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
|
||||
public class JadxCLI {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JadxCLI.class);
|
||||
@@ -14,35 +15,39 @@ public class JadxCLI {
|
||||
public static void main(String[] args) {
|
||||
int result = 0;
|
||||
try {
|
||||
JadxCLIArgs jadxArgs = new JadxCLIArgs();
|
||||
if (jadxArgs.processArgs(args)) {
|
||||
result = processAndSave(jadxArgs);
|
||||
}
|
||||
result = execute(args);
|
||||
} catch (JadxArgsValidateException e) {
|
||||
LOG.error("Incorrect arguments: {}", e.getMessage());
|
||||
result = 1;
|
||||
} catch (Exception e) {
|
||||
LOG.error("jadx error: {}", e.getMessage(), e);
|
||||
result = 1;
|
||||
} finally {
|
||||
FileUtils.deleteTempRootDir();
|
||||
System.exit(result);
|
||||
}
|
||||
}
|
||||
|
||||
static int processAndSave(JadxCLIArgs inputArgs) {
|
||||
JadxArgs args = inputArgs.toJadxArgs();
|
||||
args.setCodeCache(new NoOpCodeCache());
|
||||
JadxDecompiler jadx = new JadxDecompiler(args);
|
||||
try {
|
||||
jadx.load();
|
||||
} catch (JadxArgsValidateException e) {
|
||||
LOG.error("Incorrect arguments: {}", e.getMessage());
|
||||
return 1;
|
||||
public static int execute(String[] args) {
|
||||
JadxCLIArgs jadxArgs = new JadxCLIArgs();
|
||||
if (jadxArgs.processArgs(args)) {
|
||||
return processAndSave(jadxArgs.toJadxArgs());
|
||||
}
|
||||
jadx.save();
|
||||
int errorsCount = jadx.getErrorsCount();
|
||||
if (errorsCount != 0) {
|
||||
jadx.printErrorsReport();
|
||||
LOG.error("finished with errors, count: {}", errorsCount);
|
||||
} else {
|
||||
LOG.info("done");
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static int processAndSave(JadxArgs jadxArgs) {
|
||||
jadxArgs.setCodeCache(new NoOpCodeCache());
|
||||
try (JadxDecompiler jadx = new JadxDecompiler(jadxArgs)) {
|
||||
jadx.load();
|
||||
jadx.save();
|
||||
int errorsCount = jadx.getErrorsCount();
|
||||
if (errorsCount != 0) {
|
||||
jadx.printErrorsReport();
|
||||
LOG.error("finished with errors, count: {}", errorsCount);
|
||||
} else {
|
||||
LOG.info("done");
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package jadx.cli;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import com.beust.jcommander.IStringConverter;
|
||||
import com.beust.jcommander.Parameter;
|
||||
@@ -19,7 +19,7 @@ import jadx.core.utils.files.FileUtils;
|
||||
|
||||
public class JadxCLIArgs {
|
||||
|
||||
@Parameter(description = "<input file> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc)")
|
||||
@Parameter(description = "<input files> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc)")
|
||||
protected List<String> files = new ArrayList<>(1);
|
||||
|
||||
@Parameter(names = { "-d", "--output-dir" }, description = "output directory")
|
||||
@@ -85,6 +85,9 @@ public class JadxCLIArgs {
|
||||
@Parameter(names = { "--deobf-use-sourcename" }, description = "use source file name as class name alias")
|
||||
protected boolean deobfuscationUseSourceNameAsAlias = false;
|
||||
|
||||
@Parameter(names = { "--deobf-parse-kotlin-metadata" }, description = "parse kotlin metadata to class and package names")
|
||||
protected boolean deobfuscationParseKotlinMetadata = false;
|
||||
|
||||
@Parameter(
|
||||
names = { "--rename-flags" },
|
||||
description = "what to rename, comma-separated,"
|
||||
@@ -194,6 +197,7 @@ public class JadxCLIArgs {
|
||||
args.setDeobfuscationMinLength(deobfuscationMinLength);
|
||||
args.setDeobfuscationMaxLength(deobfuscationMaxLength);
|
||||
args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias);
|
||||
args.setParseKotlinMetadata(deobfuscationParseKotlinMetadata);
|
||||
args.setEscapeUnicode(escapeUnicode);
|
||||
args.setRespectBytecodeAccModifiers(respectBytecodeAccessModifiers);
|
||||
args.setExportAsGradleProject(exportAsGradleProject);
|
||||
@@ -275,6 +279,10 @@ public class JadxCLIArgs {
|
||||
return deobfuscationUseSourceNameAsAlias;
|
||||
}
|
||||
|
||||
public boolean isDeobfuscationParseKotlinMetadata() {
|
||||
return deobfuscationParseKotlinMetadata;
|
||||
}
|
||||
|
||||
public boolean isEscapeUnicode() {
|
||||
return escapeUnicode;
|
||||
}
|
||||
@@ -345,7 +353,7 @@ public class JadxCLIArgs {
|
||||
}
|
||||
|
||||
public static String enumValuesString(Enum<?>[] values) {
|
||||
return Arrays.stream(values)
|
||||
return Stream.of(values)
|
||||
.map(v -> '\'' + v.name().toLowerCase(Locale.ROOT) + '\'')
|
||||
.collect(Collectors.joining(", "));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package jadx.cli.clst;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.api.plugins.JadxPluginManager;
|
||||
import jadx.api.plugins.input.JadxInputPlugin;
|
||||
import jadx.api.plugins.input.data.ILoadResult;
|
||||
import jadx.core.clsp.ClsSet;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
|
||||
/**
|
||||
* Utility class for convert dex or jar to jadx classes set (.jcst)
|
||||
*/
|
||||
public class ConvertToClsSet {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ConvertToClsSet.class);
|
||||
|
||||
public static void usage() {
|
||||
LOG.info("<output .jcst or .jar file> <several input dex or jar files> ");
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
if (args.length < 2) {
|
||||
usage();
|
||||
System.exit(1);
|
||||
}
|
||||
List<Path> inputPaths = Stream.of(args).map(Paths::get).collect(Collectors.toList());
|
||||
Path output = inputPaths.remove(0);
|
||||
|
||||
JadxPluginManager pluginManager = new JadxPluginManager();
|
||||
List<ILoadResult> loadedInputs = new ArrayList<>();
|
||||
for (JadxInputPlugin inputPlugin : pluginManager.getInputPlugins()) {
|
||||
loadedInputs.add(inputPlugin.loadFiles(inputPaths));
|
||||
}
|
||||
|
||||
JadxArgs jadxArgs = new JadxArgs();
|
||||
jadxArgs.setRenameFlags(EnumSet.noneOf(JadxArgs.RenameEnum.class));
|
||||
RootNode root = new RootNode(jadxArgs);
|
||||
root.loadClasses(loadedInputs);
|
||||
|
||||
ClsSet set = new ClsSet(root);
|
||||
set.loadFrom(root);
|
||||
set.save(output);
|
||||
|
||||
LOG.info("Output: {}, file size: {}B", output, output.toFile().length());
|
||||
LOG.info("done");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package jadx.cli;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.LinkOption;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.PathMatcher;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
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);
|
||||
|
||||
@Test
|
||||
public void testDexInput() throws Exception {
|
||||
decompile("dex", "samples/hello.dex");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSmaliInput() throws Exception {
|
||||
decompile("smali", "samples/HelloWorld.smali");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClassInput() throws Exception {
|
||||
decompile("class", "samples/HelloWorld.class");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleInput() throws Exception {
|
||||
decompile("multi", "samples/hello.dex", "samples/HelloWorld.smali");
|
||||
}
|
||||
|
||||
private void decompile(String tmpDirName, String... inputSamples) throws URISyntaxException, IOException {
|
||||
StringBuilder args = new StringBuilder();
|
||||
Path tempDir = FileUtils.createTempDir(tmpDirName);
|
||||
args.append("-v");
|
||||
args.append(" -d ").append(tempDir.toAbsolutePath());
|
||||
|
||||
for (String inputSample : inputSamples) {
|
||||
URL resource = getClass().getClassLoader().getResource(inputSample);
|
||||
assertThat(resource).isNotNull();
|
||||
String sampleFile = resource.toURI().getRawPath();
|
||||
args.append(' ').append(sampleFile);
|
||||
}
|
||||
|
||||
int result = JadxCLI.execute(args.toString().split(" "));
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static List<Path> collectJavaFilesInDir(Path dir) throws IOException {
|
||||
PathMatcher javaMatcher = dir.getFileSystem().getPathMatcher("glob:**.java");
|
||||
return collectFilesInDir(dir, javaMatcher);
|
||||
}
|
||||
|
||||
private static List<Path> collectFilesInDir(Path dir, PathMatcher matcher) throws IOException {
|
||||
try (Stream<Path> pathStream = Files.walk(dir)) {
|
||||
return pathStream
|
||||
.filter(p -> Files.isRegularFile(p, LinkOption.NOFOLLOW_LINKS))
|
||||
.filter(matcher::matches)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void cleanup() {
|
||||
FileUtils.clearTempRootDir();
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,26 @@
|
||||
.class Lsmali/HelloWorld;
|
||||
.super Ljava/lang/Object;
|
||||
.source "HelloWorld.java"
|
||||
|
||||
.method constructor <init>()V
|
||||
.registers 1
|
||||
|
||||
.line 1
|
||||
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
|
||||
|
||||
return-void
|
||||
.end method
|
||||
|
||||
.method public static main([Ljava/lang/String;)V
|
||||
.registers 2
|
||||
|
||||
.line 3
|
||||
sget-object p0, Ljava/lang/System;->out:Ljava/io/PrintStream;
|
||||
|
||||
const-string v0, "Hello, World"
|
||||
|
||||
invoke-virtual {p0, v0}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
|
||||
|
||||
.line 4
|
||||
return-void
|
||||
.end method
|
||||
Binary file not shown.
+21
-17
@@ -1,18 +1,22 @@
|
||||
dependencies {
|
||||
runtime files('clsp-data/android-29-clst.jar')
|
||||
runtime files('clsp-data/android-29-res.jar')
|
||||
|
||||
compile files('lib/dx-1.16.jar') // TODO: dx don't support java version > 9 (53)
|
||||
|
||||
compile 'org.ow2.asm:asm:7.2'
|
||||
compile 'org.jetbrains:annotations:18.0.0'
|
||||
compile 'com.google.code.gson:gson:2.8.6'
|
||||
|
||||
compile 'org.smali:baksmali:2.3.4'
|
||||
compile('org.smali:smali:2.3.4') {
|
||||
exclude group: 'com.google.guava'
|
||||
}
|
||||
compile 'com.google.guava:guava:28.1-jre'
|
||||
|
||||
testCompile 'org.apache.commons:commons-lang3:3.9'
|
||||
plugins {
|
||||
id 'java-library'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
runtimeOnly files('clsp-data/android-29-clst.jar')
|
||||
runtimeOnly files('clsp-data/android-29-res.jar')
|
||||
|
||||
api(project(':jadx-plugins:jadx-plugins-api'))
|
||||
|
||||
implementation 'com.google.code.gson:gson:2.8.6'
|
||||
|
||||
testImplementation 'org.apache.commons:commons-lang3:3.11'
|
||||
|
||||
testRuntimeOnly(project(':jadx-plugins:jadx-dex-input'))
|
||||
testRuntimeOnly(project(':jadx-plugins:jadx-smali-input'))
|
||||
testRuntimeOnly(project(':jadx-plugins:jadx-java-convert'))
|
||||
}
|
||||
|
||||
test {
|
||||
exclude '**/tmp/*'
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -6,6 +6,8 @@ public interface ICodeCache {
|
||||
|
||||
void add(String clsFullName, ICodeInfo codeInfo);
|
||||
|
||||
void remove(String clsFullName);
|
||||
|
||||
@Nullable
|
||||
ICodeInfo get(String clsFullName);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,12 @@ package jadx.api;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import jadx.api.impl.SimpleCodeInfo;
|
||||
|
||||
public interface ICodeInfo {
|
||||
|
||||
ICodeInfo EMPTY = new SimpleCodeInfo("");
|
||||
|
||||
String getCodeStr();
|
||||
|
||||
Map<Integer, Integer> getLineMapping();
|
||||
|
||||
@@ -49,6 +49,7 @@ public class JadxArgs {
|
||||
private boolean deobfuscationOn = false;
|
||||
private boolean deobfuscationForceSave = false;
|
||||
private boolean useSourceNameAsClassAlias = false;
|
||||
private boolean parseKotlinMetadata = false;
|
||||
|
||||
private int deobfuscationMinLength = 0;
|
||||
private int deobfuscationMaxLength = Integer.MAX_VALUE;
|
||||
@@ -230,6 +231,14 @@ public class JadxArgs {
|
||||
this.useSourceNameAsClassAlias = useSourceNameAsClassAlias;
|
||||
}
|
||||
|
||||
public boolean isParseKotlinMetadata() {
|
||||
return parseKotlinMetadata;
|
||||
}
|
||||
|
||||
public void setParseKotlinMetadata(boolean parseKotlinMetadata) {
|
||||
this.parseKotlinMetadata = parseKotlinMetadata;
|
||||
}
|
||||
|
||||
public int getDeobfuscationMinLength() {
|
||||
return deobfuscationMinLength;
|
||||
}
|
||||
@@ -318,6 +327,14 @@ public class JadxArgs {
|
||||
}
|
||||
}
|
||||
|
||||
public void setRenameFlags(Set<RenameEnum> renameFlags) {
|
||||
this.renameFlags = renameFlags;
|
||||
}
|
||||
|
||||
public Set<RenameEnum> getRenameFlags() {
|
||||
return renameFlags;
|
||||
}
|
||||
|
||||
public OutputFormatEnum getOutputFormat() {
|
||||
return outputFormat;
|
||||
}
|
||||
@@ -355,6 +372,7 @@ public class JadxArgs {
|
||||
+ ", deobfuscationOn=" + deobfuscationOn
|
||||
+ ", deobfuscationForceSave=" + deobfuscationForceSave
|
||||
+ ", useSourceNameAsClassAlias=" + useSourceNameAsClassAlias
|
||||
+ ", parseKotlinMetadata=" + parseKotlinMetadata
|
||||
+ ", deobfuscationMinLength=" + deobfuscationMinLength
|
||||
+ ", deobfuscationMaxLength=" + deobfuscationMaxLength
|
||||
+ ", escapeUnicode=" + escapeUnicode
|
||||
|
||||
@@ -27,14 +27,11 @@ public class JadxArgsValidator {
|
||||
if (inputFiles.isEmpty()) {
|
||||
throw new JadxArgsValidateException("Please specify input file");
|
||||
}
|
||||
if (inputFiles.size() > 1) {
|
||||
for (File inputFile : inputFiles) {
|
||||
String fileName = inputFile.getName();
|
||||
if (fileName.startsWith("--")) {
|
||||
throw new JadxArgsValidateException("Unknown argument: " + fileName);
|
||||
}
|
||||
for (File inputFile : inputFiles) {
|
||||
String fileName = inputFile.getName();
|
||||
if (fileName.startsWith("--")) {
|
||||
throw new JadxArgsValidateException("Unknown argument: " + fileName);
|
||||
}
|
||||
throw new JadxArgsValidateException("Only one input file supported");
|
||||
}
|
||||
for (File file : inputFiles) {
|
||||
checkFile(file);
|
||||
|
||||
@@ -1,22 +1,32 @@
|
||||
package jadx.api;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.plugins.JadxPlugin;
|
||||
import jadx.api.plugins.JadxPluginManager;
|
||||
import jadx.api.plugins.input.JadxInputPlugin;
|
||||
import jadx.api.plugins.input.data.ILoadResult;
|
||||
import jadx.core.Jadx;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.nodes.LineAttrNode;
|
||||
@@ -26,8 +36,8 @@ import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.dex.visitors.SaveCode;
|
||||
import jadx.core.export.ExportGradleProject;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.core.utils.files.InputFile;
|
||||
import jadx.core.xmlgen.BinaryXMLParser;
|
||||
import jadx.core.xmlgen.ResourcesSaver;
|
||||
|
||||
@@ -39,10 +49,10 @@ import jadx.core.xmlgen.ResourcesSaver;
|
||||
* JadxArgs args = new JadxArgs();
|
||||
* args.getInputFiles().add(new File("test.apk"));
|
||||
* args.setOutDir(new File("jadx-test-output"));
|
||||
*
|
||||
* JadxDecompiler jadx = new JadxDecompiler(args);
|
||||
* jadx.load();
|
||||
* jadx.save();
|
||||
* try (JadxDecompiler jadx = new JadxDecompiler(args)) {
|
||||
* jadx.load();
|
||||
* jadx.save();
|
||||
* }
|
||||
* </code>
|
||||
* </pre>
|
||||
* <p>
|
||||
@@ -56,11 +66,12 @@ import jadx.core.xmlgen.ResourcesSaver;
|
||||
* </code>
|
||||
* </pre>
|
||||
*/
|
||||
public final class JadxDecompiler {
|
||||
public final class JadxDecompiler implements Closeable {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JadxDecompiler.class);
|
||||
|
||||
private JadxArgs args;
|
||||
private List<InputFile> inputFiles;
|
||||
private final JadxArgs args;
|
||||
private final JadxPluginManager pluginManager = new JadxPluginManager();
|
||||
private final List<ILoadResult> loadedInputs = new ArrayList<>();
|
||||
|
||||
private RootNode root;
|
||||
private List<JavaClass> classes;
|
||||
@@ -68,9 +79,9 @@ public final class JadxDecompiler {
|
||||
|
||||
private BinaryXMLParser xmlParser;
|
||||
|
||||
private Map<ClassNode, JavaClass> classesMap = new ConcurrentHashMap<>();
|
||||
private Map<MethodNode, JavaMethod> methodsMap = new ConcurrentHashMap<>();
|
||||
private Map<FieldNode, JavaField> fieldsMap = new ConcurrentHashMap<>();
|
||||
private final Map<ClassNode, JavaClass> classesMap = new ConcurrentHashMap<>();
|
||||
private final Map<MethodNode, JavaMethod> methodsMap = new ConcurrentHashMap<>();
|
||||
private final Map<FieldNode, JavaField> fieldsMap = new ConcurrentHashMap<>();
|
||||
|
||||
public JadxDecompiler() {
|
||||
this(new JadxArgs());
|
||||
@@ -84,16 +95,27 @@ public final class JadxDecompiler {
|
||||
reset();
|
||||
JadxArgsValidator.validate(args);
|
||||
LOG.info("loading ...");
|
||||
|
||||
inputFiles = loadFiles(args.getInputFiles());
|
||||
loadInputFiles();
|
||||
|
||||
root = new RootNode(args);
|
||||
root.load(inputFiles);
|
||||
root.loadClasses(loadedInputs);
|
||||
root.initClassPath();
|
||||
root.loadResources(getResources());
|
||||
root.runPreDecompileStage();
|
||||
root.initPasses();
|
||||
}
|
||||
|
||||
private void loadInputFiles() {
|
||||
loadedInputs.clear();
|
||||
List<Path> inputPaths = Utils.collectionMap(args.getInputFiles(), File::toPath);
|
||||
for (JadxInputPlugin inputPlugin : pluginManager.getInputPlugins()) {
|
||||
ILoadResult loadResult = inputPlugin.loadFiles(inputPaths);
|
||||
if (loadResult != null && !loadResult.isEmpty()) {
|
||||
loadedInputs.add(loadResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void reset() {
|
||||
root = null;
|
||||
classes = null;
|
||||
@@ -103,27 +125,34 @@ public final class JadxDecompiler {
|
||||
classesMap.clear();
|
||||
methodsMap.clear();
|
||||
fieldsMap.clear();
|
||||
|
||||
closeInputs();
|
||||
}
|
||||
|
||||
private void closeInputs() {
|
||||
loadedInputs.forEach(load -> {
|
||||
try {
|
||||
load.close();
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to close input", e);
|
||||
}
|
||||
});
|
||||
loadedInputs.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
reset();
|
||||
}
|
||||
|
||||
public void registerPlugin(JadxPlugin plugin) {
|
||||
pluginManager.register(plugin);
|
||||
}
|
||||
|
||||
public static String getVersion() {
|
||||
return Jadx.getVersion();
|
||||
}
|
||||
|
||||
private List<InputFile> loadFiles(List<File> files) {
|
||||
if (files.isEmpty()) {
|
||||
throw new JadxRuntimeException("Empty file list");
|
||||
}
|
||||
List<InputFile> filesList = new ArrayList<>();
|
||||
for (File file : files) {
|
||||
try {
|
||||
InputFile.addFilesFrom(file, filesList, args.isSkipSources());
|
||||
} catch (Exception e) {
|
||||
throw new JadxRuntimeException("Error load file: " + file, e);
|
||||
}
|
||||
}
|
||||
return filesList;
|
||||
}
|
||||
|
||||
public void save() {
|
||||
save(!args.isSkipSources(), !args.isSkipResources());
|
||||
}
|
||||
@@ -182,13 +211,19 @@ public final class JadxDecompiler {
|
||||
}
|
||||
|
||||
private void appendResourcesSave(ExecutorService executor, File outDir) {
|
||||
Set<String> inputFileNames = args.getInputFiles().stream().map(File::getAbsolutePath).collect(Collectors.toSet());
|
||||
for (ResourceFile resourceFile : getResources()) {
|
||||
if (resourceFile.getType() != ResourceType.ARSC
|
||||
&& inputFileNames.contains(resourceFile.getOriginalName())) {
|
||||
// ignore resource made from input file
|
||||
continue;
|
||||
}
|
||||
executor.execute(new ResourcesSaver(outDir, resourceFile));
|
||||
}
|
||||
}
|
||||
|
||||
private void appendSourcesSave(ExecutorService executor, File outDir) {
|
||||
final Predicate<String> classFilter = args.getClassFilter();
|
||||
Predicate<String> classFilter = args.getClassFilter();
|
||||
for (JavaClass cls : getClasses()) {
|
||||
if (cls.getClassNode().contains(AFlag.DONT_GENERATE)) {
|
||||
continue;
|
||||
@@ -232,7 +267,7 @@ public final class JadxDecompiler {
|
||||
if (root == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
resources = new ResourcesLoader(this).load(inputFiles);
|
||||
resources = new ResourcesLoader(this).load();
|
||||
}
|
||||
return resources;
|
||||
}
|
||||
@@ -281,7 +316,10 @@ public final class JadxDecompiler {
|
||||
root.getErrorsCounter().printReport();
|
||||
}
|
||||
|
||||
RootNode getRoot() {
|
||||
/**
|
||||
* Internal API. Not Stable!
|
||||
*/
|
||||
public RootNode getRoot() {
|
||||
return root;
|
||||
}
|
||||
|
||||
@@ -397,6 +435,13 @@ public final class JadxDecompiler {
|
||||
throw new JadxRuntimeException("Unexpected node type: " + obj);
|
||||
}
|
||||
|
||||
List<JavaNode> convertNodes(Collection<?> nodesList) {
|
||||
return nodesList.stream()
|
||||
.map(this::convertNode)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public JavaNode getJavaNodeAtPosition(ICodeInfo codeInfo, int line, int offset) {
|
||||
Map<CodePosition, Object> map = codeInfo.getAnnotations();
|
||||
|
||||
@@ -57,6 +57,11 @@ public final class JavaClass implements JavaNode {
|
||||
cls.decompile();
|
||||
}
|
||||
|
||||
public synchronized void reload() {
|
||||
listsLoaded = false;
|
||||
cls.reloadCode();
|
||||
}
|
||||
|
||||
public synchronized String getSmali() {
|
||||
return cls.getSmali();
|
||||
}
|
||||
@@ -66,11 +71,14 @@ public final class JavaClass implements JavaNode {
|
||||
listsLoaded = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal API. Not Stable!
|
||||
*/
|
||||
public ClassNode getClassNode() {
|
||||
return cls;
|
||||
}
|
||||
|
||||
private void loadLists() {
|
||||
private synchronized void loadLists() {
|
||||
if (listsLoaded) {
|
||||
return;
|
||||
}
|
||||
@@ -116,7 +124,7 @@ public final class JavaClass implements JavaNode {
|
||||
}
|
||||
}
|
||||
|
||||
private JadxDecompiler getRootDecompiler() {
|
||||
protected JadxDecompiler getRootDecompiler() {
|
||||
if (parent != null) {
|
||||
return parent.getRootDecompiler();
|
||||
}
|
||||
@@ -148,6 +156,11 @@ public final class JavaClass implements JavaNode {
|
||||
return resultMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<JavaNode> getUseIn() {
|
||||
return getRootDecompiler().convertNodes(cls.getUseIn());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Deprecated
|
||||
public JavaNode getJavaNodeAtPosition(int line, int offset) {
|
||||
@@ -174,6 +187,10 @@ public final class JavaClass implements JavaNode {
|
||||
return cls.getFullName();
|
||||
}
|
||||
|
||||
public String getRawName() {
|
||||
return cls.getRawName();
|
||||
}
|
||||
|
||||
public String getPackage() {
|
||||
return cls.getPackage();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package jadx.api;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
@@ -39,14 +41,23 @@ public final class JavaField implements JavaNode {
|
||||
}
|
||||
|
||||
public ArgType getType() {
|
||||
return ArgType.tryToResolveClassAlias(field.dex(), field.getType());
|
||||
return ArgType.tryToResolveClassAlias(field.root(), field.getType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDecompiledLine() {
|
||||
return field.getDecompiledLine();
|
||||
}
|
||||
|
||||
FieldNode getFieldNode() {
|
||||
@Override
|
||||
public List<JavaNode> getUseIn() {
|
||||
return getDeclaringClass().getRootDecompiler().convertNodes(field.getUseIn());
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal API. Not Stable!
|
||||
*/
|
||||
public FieldNode getFieldNode() {
|
||||
return field;
|
||||
}
|
||||
|
||||
|
||||
@@ -47,19 +47,18 @@ public final class JavaMethod implements JavaNode {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<ArgType> arguments = mth.getArgTypes();
|
||||
if (arguments == null) {
|
||||
arguments = infoArgTypes;
|
||||
}
|
||||
return Utils.collectionMap(arguments,
|
||||
type -> ArgType.tryToResolveClassAlias(mth.dex(), type));
|
||||
type -> ArgType.tryToResolveClassAlias(mth.root(), type));
|
||||
}
|
||||
|
||||
public ArgType getReturnType() {
|
||||
ArgType retType = mth.getReturnType();
|
||||
if (retType == null) {
|
||||
retType = mth.getMethodInfo().getReturnType();
|
||||
}
|
||||
return ArgType.tryToResolveClassAlias(mth.dex(), retType);
|
||||
return ArgType.tryToResolveClassAlias(mth.root(), retType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<JavaNode> getUseIn() {
|
||||
return getDeclaringClass().getRootDecompiler().convertNodes(mth.getUseIn());
|
||||
}
|
||||
|
||||
public boolean isConstructor() {
|
||||
@@ -70,11 +69,15 @@ public final class JavaMethod implements JavaNode {
|
||||
return mth.getMethodInfo().isClassInit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDecompiledLine() {
|
||||
return mth.getDecompiledLine();
|
||||
}
|
||||
|
||||
MethodNode getMethodNode() {
|
||||
/**
|
||||
* Internal API. Not Stable!
|
||||
*/
|
||||
public MethodNode getMethodNode() {
|
||||
return mth;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package jadx.api;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface JavaNode {
|
||||
|
||||
String getName();
|
||||
@@ -11,4 +13,6 @@ public interface JavaNode {
|
||||
JavaClass getTopParentClass();
|
||||
|
||||
int getDecompiledLine();
|
||||
|
||||
List<JavaNode> getUseIn();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package jadx.api;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -43,6 +44,11 @@ public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<JavaNode> getUseIn() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull JavaPackage o) {
|
||||
return name.compareTo(o.name);
|
||||
|
||||
@@ -2,8 +2,9 @@ package jadx.api;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import jadx.core.utils.files.ZipSecurity;
|
||||
import jadx.api.plugins.utils.ZipSecurity;
|
||||
import jadx.core.xmlgen.ResContainer;
|
||||
import jadx.core.xmlgen.entry.ResourceEntry;
|
||||
|
||||
public class ResourceFile {
|
||||
|
||||
@@ -34,6 +35,7 @@ public class ResourceFile {
|
||||
private final String name;
|
||||
private final ResourceType type;
|
||||
private ZipRef zipRef;
|
||||
private String deobfName;
|
||||
|
||||
public static ResourceFile createResourceFile(JadxDecompiler decompiler, String name, ResourceType type) {
|
||||
if (!ZipSecurity.isValidZipEntryName(name)) {
|
||||
@@ -48,10 +50,14 @@ public class ResourceFile {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
public String getOriginalName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getDeobfName() {
|
||||
return deobfName != null ? deobfName : name;
|
||||
}
|
||||
|
||||
public ResourceType getType() {
|
||||
return type;
|
||||
}
|
||||
@@ -64,6 +70,15 @@ public class ResourceFile {
|
||||
this.zipRef = zipRef;
|
||||
}
|
||||
|
||||
public void setAlias(ResourceEntry ri) {
|
||||
int index = name.lastIndexOf('.');
|
||||
deobfName = String.format("res/%s%s/%s%s",
|
||||
ri.getTypeName(),
|
||||
ri.getConfig(),
|
||||
ri.getKeyName(),
|
||||
index == -1 ? "" : name.substring(index));
|
||||
}
|
||||
|
||||
public ZipRef getZipRef() {
|
||||
return zipRef;
|
||||
}
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
package jadx.api;
|
||||
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
import jadx.core.xmlgen.ResContainer;
|
||||
|
||||
public class ResourceFileContent extends ResourceFile {
|
||||
private final CodeWriter content;
|
||||
private final ICodeInfo content;
|
||||
|
||||
public ResourceFileContent(String name, ResourceType type, CodeWriter content) {
|
||||
public ResourceFileContent(String name, ResourceType type, ICodeInfo content) {
|
||||
super(null, name, type);
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResContainer loadContent() {
|
||||
return ResContainer.textResource(getName(), content);
|
||||
return ResContainer.textResource(getDeobfName(), content);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
package jadx.api;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public enum ResourceType {
|
||||
CODE(".dex", ".jar", ".class"),
|
||||
MANIFEST("AndroidManifest.xml"),
|
||||
XML(".xml"),
|
||||
ARSC(".arsc"),
|
||||
FONT(".ttf", ".otf"),
|
||||
IMG(".png", ".gif", ".jpg"),
|
||||
MEDIA(".mp3", ".wav"),
|
||||
LIB(".so"),
|
||||
MANIFEST,
|
||||
UNKNOWN;
|
||||
|
||||
private final String[] exts;
|
||||
@@ -21,14 +27,31 @@ public enum ResourceType {
|
||||
return exts;
|
||||
}
|
||||
|
||||
public static ResourceType getFileType(String fileName) {
|
||||
private static final Map<String, ResourceType> EXT_MAP = new HashMap<>();
|
||||
|
||||
static {
|
||||
for (ResourceType type : ResourceType.values()) {
|
||||
for (String ext : type.getExts()) {
|
||||
if (fileName.toLowerCase().endsWith(ext)) {
|
||||
return type;
|
||||
ResourceType prev = EXT_MAP.put(ext, type);
|
||||
if (prev != null) {
|
||||
throw new JadxRuntimeException("Duplicate extension in ResourceType: " + ext);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static ResourceType getFileType(String fileName) {
|
||||
int dot = fileName.lastIndexOf('.');
|
||||
if (dot != -1) {
|
||||
String ext = fileName.substring(dot).toLowerCase(Locale.ROOT);
|
||||
ResourceType resType = EXT_MAP.get(ext);
|
||||
if (resType != null) {
|
||||
if (resType == XML && fileName.equals("AndroidManifest.xml")) {
|
||||
return MANIFEST;
|
||||
}
|
||||
return resType;
|
||||
}
|
||||
}
|
||||
return UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
@@ -16,12 +15,13 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.ResourceFile.ZipRef;
|
||||
import jadx.api.impl.SimpleCodeInfo;
|
||||
import jadx.api.plugins.utils.ZipSecurity;
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.android.Res9patchStreamDecoder;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
import jadx.core.utils.files.InputFile;
|
||||
import jadx.core.utils.files.ZipSecurity;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
import jadx.core.xmlgen.ResContainer;
|
||||
import jadx.core.xmlgen.ResTableParser;
|
||||
|
||||
@@ -38,10 +38,11 @@ public final class ResourcesLoader {
|
||||
this.jadxRef = jadxRef;
|
||||
}
|
||||
|
||||
List<ResourceFile> load(List<InputFile> inputFiles) {
|
||||
List<ResourceFile> load() {
|
||||
List<File> inputFiles = jadxRef.getArgs().getInputFiles();
|
||||
List<ResourceFile> list = new ArrayList<>(inputFiles.size());
|
||||
for (InputFile file : inputFiles) {
|
||||
loadFile(list, file.getFile());
|
||||
for (File file : inputFiles) {
|
||||
loadFile(list, file);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
@@ -54,7 +55,7 @@ public final class ResourcesLoader {
|
||||
try {
|
||||
ZipRef zipRef = rf.getZipRef();
|
||||
if (zipRef == null) {
|
||||
File file = new File(rf.getName());
|
||||
File file = new File(rf.getOriginalName());
|
||||
try (InputStream inputStream = new BufferedInputStream(new FileInputStream(file))) {
|
||||
return decoder.decode(file.length(), inputStream);
|
||||
}
|
||||
@@ -67,13 +68,13 @@ public final class ResourcesLoader {
|
||||
if (!ZipSecurity.isValidZipEntry(entry)) {
|
||||
return null;
|
||||
}
|
||||
try (InputStream inputStream = new BufferedInputStream(zipFile.getInputStream(entry))) {
|
||||
try (InputStream inputStream = ZipSecurity.getInputStreamForEntry(zipFile, entry)) {
|
||||
return decoder.decode(entry.getSize(), inputStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new JadxException("Error decode: " + rf.getName(), e);
|
||||
throw new JadxException("Error decode: " + rf.getDeobfName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +86,7 @@ public final class ResourcesLoader {
|
||||
CodeWriter cw = new CodeWriter();
|
||||
cw.add("Error decode ").add(rf.getType().toString().toLowerCase());
|
||||
Utils.appendStackTrace(cw, e.getCause());
|
||||
return ResContainer.textResource(rf.getName(), cw);
|
||||
return ResContainer.textResource(rf.getDeobfName(), cw.finish());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,8 +95,8 @@ public final class ResourcesLoader {
|
||||
switch (rf.getType()) {
|
||||
case MANIFEST:
|
||||
case XML:
|
||||
CodeWriter content = jadxRef.getXmlParser().parse(inputStream);
|
||||
return ResContainer.textResource(rf.getName(), content);
|
||||
ICodeInfo content = jadxRef.getXmlParser().parse(inputStream);
|
||||
return ResContainer.textResource(rf.getDeobfName(), content);
|
||||
|
||||
case ARSC:
|
||||
return new ResTableParser(jadxRef.getRoot()).decodeFiles(inputStream);
|
||||
@@ -109,12 +110,12 @@ public final class ResourcesLoader {
|
||||
}
|
||||
|
||||
private static ResContainer decodeImage(ResourceFile rf, InputStream inputStream) {
|
||||
String name = rf.getName();
|
||||
String name = rf.getOriginalName();
|
||||
if (name.endsWith(".9.png")) {
|
||||
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
|
||||
Res9patchStreamDecoder decoder = new Res9patchStreamDecoder();
|
||||
decoder.decode(inputStream, os);
|
||||
return ResContainer.decodedData(rf.getName(), os.toByteArray());
|
||||
return ResContainer.decodedData(rf.getDeobfName(), os.toByteArray());
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to decode 9-patch png image, path: {}", name, e);
|
||||
}
|
||||
@@ -126,16 +127,9 @@ public final class ResourcesLoader {
|
||||
if (file == null) {
|
||||
return;
|
||||
}
|
||||
try (ZipFile zip = new ZipFile(file)) {
|
||||
Enumeration<? extends ZipEntry> entries = zip.entries();
|
||||
while (entries.hasMoreElements()) {
|
||||
ZipEntry entry = entries.nextElement();
|
||||
if (ZipSecurity.isValidZipEntry(entry)) {
|
||||
addEntry(list, file, entry);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.debug("Not a zip file: {}", file.getAbsolutePath());
|
||||
if (FileUtils.isZipFile(file)) {
|
||||
ZipSecurity.visitZipEntries(file, (zipFile, entry) -> addEntry(list, file, entry));
|
||||
} else {
|
||||
addResourceFile(list, file);
|
||||
}
|
||||
}
|
||||
@@ -162,9 +156,9 @@ public final class ResourcesLoader {
|
||||
}
|
||||
}
|
||||
|
||||
public static CodeWriter loadToCodeWriter(InputStream is) throws IOException {
|
||||
public static ICodeInfo loadToCodeWriter(InputStream is) throws IOException {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(READ_BUFFER_SIZE);
|
||||
copyStream(is, baos);
|
||||
return new CodeWriter(baos.toString("UTF-8"));
|
||||
return new SimpleCodeInfo(baos.toString("UTF-8"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,8 +17,18 @@ public class InMemoryCodeCache implements ICodeCache {
|
||||
storage.put(clsFullName, codeInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(String clsFullName) {
|
||||
storage.remove(clsFullName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ICodeInfo get(String clsFullName) {
|
||||
return storage.get(clsFullName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "InMemoryCodeCache";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,8 +12,18 @@ public class NoOpCodeCache implements ICodeCache {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(String clsFullName) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ICodeInfo get(String clsFullName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "NoOpCodeCache";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package jadx.api.impl;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import jadx.api.CodePosition;
|
||||
import jadx.api.ICodeInfo;
|
||||
|
||||
public class SimpleCodeInfo implements ICodeInfo {
|
||||
|
||||
private final String code;
|
||||
private final Map<Integer, Integer> lineMapping;
|
||||
private final Map<CodePosition, Object> annotations;
|
||||
|
||||
public SimpleCodeInfo(String code) {
|
||||
this(code, Collections.emptyMap(), Collections.emptyMap());
|
||||
}
|
||||
|
||||
public SimpleCodeInfo(ICodeInfo codeInfo) {
|
||||
this(codeInfo.getCodeStr(), codeInfo.getLineMapping(), codeInfo.getAnnotations());
|
||||
}
|
||||
|
||||
public SimpleCodeInfo(String code, Map<Integer, Integer> lineMapping, Map<CodePosition, Object> annotations) {
|
||||
this.code = code;
|
||||
this.lineMapping = lineMapping;
|
||||
this.annotations = annotations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCodeStr() {
|
||||
return code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Integer, Integer> getLineMapping() {
|
||||
return lineMapping;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<CodePosition, Object> getAnnotations() {
|
||||
return annotations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return code;
|
||||
}
|
||||
}
|
||||
@@ -2,20 +2,25 @@ package jadx.core;
|
||||
|
||||
public class Consts {
|
||||
public static final boolean DEBUG = false;
|
||||
public static final boolean DEBUG_WITH_ERRORS = false; // TODO: fix errors
|
||||
public static final boolean DEBUG_USAGE = false;
|
||||
public static final boolean DEBUG_TYPE_INFERENCE = false;
|
||||
public static final boolean DEBUG_OVERLOADED_CASTS = false;
|
||||
|
||||
public static final String CLASS_OBJECT = "java.lang.Object";
|
||||
public static final String CLASS_STRING = "java.lang.String";
|
||||
public static final String CLASS_CLASS = "java.lang.Class";
|
||||
public static final String CLASS_THROWABLE = "java.lang.Throwable";
|
||||
public static final String CLASS_EXCEPTION = "java.lang.Exception";
|
||||
public static final String CLASS_ENUM = "java.lang.Enum";
|
||||
|
||||
public static final String CLASS_STRING_BUILDER = "java.lang.StringBuilder";
|
||||
|
||||
public static final String DALVIK_ANNOTATION_PKG = "dalvik.annotation.";
|
||||
public static final String DALVIK_SIGNATURE = "dalvik.annotation.Signature";
|
||||
public static final String DALVIK_INNER_CLASS = "dalvik.annotation.InnerClass";
|
||||
public static final String DALVIK_THROWS = "dalvik.annotation.Throws";
|
||||
public static final String DALVIK_ANNOTATION_DEFAULT = "dalvik.annotation.AnnotationDefault";
|
||||
public static final String DALVIK_ANNOTATION_PKG = "Ldalvik/annotation/";
|
||||
public static final String DALVIK_SIGNATURE = "Ldalvik/annotation/Signature;";
|
||||
public static final String DALVIK_INNER_CLASS = "Ldalvik/annotation/InnerClass;";
|
||||
public static final String DALVIK_THROWS = "Ldalvik/annotation/Throws;";
|
||||
public static final String DALVIK_ANNOTATION_DEFAULT = "Ldalvik/annotation/AnnotationDefault;";
|
||||
|
||||
public static final String DEFAULT_PACKAGE_NAME = "defpackage";
|
||||
public static final String ANONYMOUS_CLASS_PREFIX = "AnonymousClass";
|
||||
|
||||
@@ -11,32 +11,41 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.core.dex.visitors.AttachMethodDetails;
|
||||
import jadx.core.dex.visitors.AttachTryCatchVisitor;
|
||||
import jadx.core.dex.visitors.ClassModifier;
|
||||
import jadx.core.dex.visitors.ConstInlineVisitor;
|
||||
import jadx.core.dex.visitors.ConstructorVisitor;
|
||||
import jadx.core.dex.visitors.DeboxingVisitor;
|
||||
import jadx.core.dex.visitors.DependencyCollector;
|
||||
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.GenericTypesVisitor;
|
||||
import jadx.core.dex.visitors.IDexTreeVisitor;
|
||||
import jadx.core.dex.visitors.InitCodeVariables;
|
||||
import jadx.core.dex.visitors.InlineMethods;
|
||||
import jadx.core.dex.visitors.MarkFinallyVisitor;
|
||||
import jadx.core.dex.visitors.MethodInlineVisitor;
|
||||
import jadx.core.dex.visitors.MarkMethodsForInline;
|
||||
import jadx.core.dex.visitors.MethodInvokeVisitor;
|
||||
import jadx.core.dex.visitors.ModVisitor;
|
||||
import jadx.core.dex.visitors.MoveInlineVisitor;
|
||||
import jadx.core.dex.visitors.OverrideMethodVisitor;
|
||||
import jadx.core.dex.visitors.PrepareForCodeGen;
|
||||
import jadx.core.dex.visitors.ProcessAnonymous;
|
||||
import jadx.core.dex.visitors.ProcessInstructionsVisitor;
|
||||
import jadx.core.dex.visitors.ReSugarCode;
|
||||
import jadx.core.dex.visitors.RenameVisitor;
|
||||
import jadx.core.dex.visitors.ShadowFieldVisitor;
|
||||
import jadx.core.dex.visitors.SignatureProcessor;
|
||||
import jadx.core.dex.visitors.SimplifyVisitor;
|
||||
import jadx.core.dex.visitors.blocksmaker.BlockExceptionHandler;
|
||||
import jadx.core.dex.visitors.blocksmaker.BlockFinish;
|
||||
import jadx.core.dex.visitors.blocksmaker.BlockProcessor;
|
||||
import jadx.core.dex.visitors.blocksmaker.BlockSplitter;
|
||||
import jadx.core.dex.visitors.debuginfo.DebugInfoApplyVisitor;
|
||||
import jadx.core.dex.visitors.debuginfo.DebugInfoParseVisitor;
|
||||
import jadx.core.dex.visitors.debuginfo.DebugInfoAttachVisitor;
|
||||
import jadx.core.dex.visitors.regions.CheckRegions;
|
||||
import jadx.core.dex.visitors.regions.CleanRegions;
|
||||
import jadx.core.dex.visitors.regions.IfRegionVisitor;
|
||||
@@ -47,6 +56,7 @@ import jadx.core.dex.visitors.regions.variables.ProcessVariables;
|
||||
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
|
||||
import jadx.core.dex.visitors.ssa.SSATransform;
|
||||
import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor;
|
||||
import jadx.core.dex.visitors.usage.UsageInfoVisitor;
|
||||
|
||||
public class Jadx {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Jadx.class);
|
||||
@@ -60,67 +70,90 @@ public class Jadx {
|
||||
}
|
||||
}
|
||||
|
||||
public static List<IDexTreeVisitor> getPassesList(JadxArgs args) {
|
||||
public static List<IDexTreeVisitor> getFallbackPassesList() {
|
||||
List<IDexTreeVisitor> passes = new ArrayList<>(3);
|
||||
passes.add(new AttachTryCatchVisitor());
|
||||
passes.add(new ProcessInstructionsVisitor());
|
||||
passes.add(new FallbackModeVisitor());
|
||||
return passes;
|
||||
}
|
||||
|
||||
public static List<IDexTreeVisitor> getPreDecompilePassesList() {
|
||||
List<IDexTreeVisitor> passes = new ArrayList<>();
|
||||
passes.add(new SignatureProcessor());
|
||||
passes.add(new RenameVisitor());
|
||||
passes.add(new UsageInfoVisitor());
|
||||
return passes;
|
||||
}
|
||||
|
||||
public static List<IDexTreeVisitor> getPassesList(JadxArgs args) {
|
||||
if (args.isFallbackMode()) {
|
||||
passes.add(new FallbackModeVisitor());
|
||||
} else {
|
||||
if (args.isDebugInfo()) {
|
||||
passes.add(new DebugInfoParseVisitor());
|
||||
}
|
||||
return getFallbackPassesList();
|
||||
}
|
||||
|
||||
passes.add(new BlockSplitter());
|
||||
if (args.isRawCFGOutput()) {
|
||||
passes.add(DotGraphVisitor.dumpRaw());
|
||||
}
|
||||
List<IDexTreeVisitor> passes = new ArrayList<>();
|
||||
if (args.isDebugInfo()) {
|
||||
passes.add(new DebugInfoAttachVisitor());
|
||||
}
|
||||
passes.add(new AttachTryCatchVisitor());
|
||||
passes.add(new ProcessInstructionsVisitor());
|
||||
|
||||
passes.add(new BlockProcessor());
|
||||
passes.add(new BlockExceptionHandler());
|
||||
passes.add(new BlockFinish());
|
||||
passes.add(new BlockSplitter());
|
||||
if (args.isRawCFGOutput()) {
|
||||
passes.add(DotGraphVisitor.dumpRaw());
|
||||
}
|
||||
passes.add(new BlockProcessor());
|
||||
passes.add(new BlockExceptionHandler());
|
||||
passes.add(new BlockFinish());
|
||||
|
||||
passes.add(new SSATransform());
|
||||
passes.add(new ConstructorVisitor());
|
||||
passes.add(new InitCodeVariables());
|
||||
passes.add(new MarkFinallyVisitor());
|
||||
passes.add(new ConstInlineVisitor());
|
||||
passes.add(new TypeInferenceVisitor());
|
||||
if (args.isDebugInfo()) {
|
||||
passes.add(new DebugInfoApplyVisitor());
|
||||
}
|
||||
passes.add(new AttachMethodDetails());
|
||||
passes.add(new OverrideMethodVisitor());
|
||||
|
||||
passes.add(new DeboxingVisitor());
|
||||
passes.add(new ModVisitor());
|
||||
passes.add(new CodeShrinkVisitor());
|
||||
passes.add(new ReSugarCode());
|
||||
if (args.isCfgOutput()) {
|
||||
passes.add(DotGraphVisitor.dump());
|
||||
}
|
||||
passes.add(new SSATransform());
|
||||
passes.add(new MoveInlineVisitor());
|
||||
passes.add(new ConstructorVisitor());
|
||||
passes.add(new InitCodeVariables());
|
||||
passes.add(new MarkFinallyVisitor());
|
||||
passes.add(new ConstInlineVisitor());
|
||||
passes.add(new TypeInferenceVisitor());
|
||||
if (args.isDebugInfo()) {
|
||||
passes.add(new DebugInfoApplyVisitor());
|
||||
}
|
||||
|
||||
passes.add(new RegionMakerVisitor());
|
||||
passes.add(new IfRegionVisitor());
|
||||
passes.add(new ReturnVisitor());
|
||||
passes.add(new CleanRegions());
|
||||
passes.add(new InlineMethods());
|
||||
passes.add(new GenericTypesVisitor());
|
||||
passes.add(new ShadowFieldVisitor());
|
||||
passes.add(new DeboxingVisitor());
|
||||
passes.add(new ModVisitor());
|
||||
passes.add(new CodeShrinkVisitor());
|
||||
passes.add(new ReSugarCode());
|
||||
if (args.isCfgOutput()) {
|
||||
passes.add(DotGraphVisitor.dump());
|
||||
}
|
||||
|
||||
passes.add(new CodeShrinkVisitor());
|
||||
passes.add(new SimplifyVisitor());
|
||||
passes.add(new CheckRegions());
|
||||
passes.add(new RegionMakerVisitor());
|
||||
passes.add(new IfRegionVisitor());
|
||||
passes.add(new ReturnVisitor());
|
||||
passes.add(new CleanRegions());
|
||||
|
||||
passes.add(new ExtractFieldInit());
|
||||
passes.add(new FixAccessModifiers());
|
||||
passes.add(new ProcessAnonymous());
|
||||
passes.add(new ClassModifier());
|
||||
passes.add(new MethodInlineVisitor());
|
||||
passes.add(new EnumVisitor());
|
||||
passes.add(new LoopRegionVisitor());
|
||||
passes.add(new CodeShrinkVisitor());
|
||||
passes.add(new MethodInvokeVisitor());
|
||||
passes.add(new SimplifyVisitor());
|
||||
passes.add(new CheckRegions());
|
||||
|
||||
passes.add(new ProcessVariables());
|
||||
passes.add(new PrepareForCodeGen());
|
||||
if (args.isCfgOutput()) {
|
||||
passes.add(DotGraphVisitor.dumpRegions());
|
||||
}
|
||||
passes.add(new EnumVisitor());
|
||||
passes.add(new ExtractFieldInit());
|
||||
passes.add(new FixAccessModifiers());
|
||||
passes.add(new ProcessAnonymous());
|
||||
passes.add(new ClassModifier());
|
||||
passes.add(new LoopRegionVisitor());
|
||||
|
||||
passes.add(new DependencyCollector());
|
||||
passes.add(new RenameVisitor());
|
||||
passes.add(new MarkMethodsForInline());
|
||||
|
||||
passes.add(new ProcessVariables());
|
||||
passes.add(new PrepareForCodeGen());
|
||||
if (args.isCfgOutput()) {
|
||||
passes.add(DotGraphVisitor.dumpRegions());
|
||||
}
|
||||
return passes;
|
||||
}
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
package jadx.core;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.core.codegen.CodeGen;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.LoadStage;
|
||||
import jadx.core.dex.visitors.DepthTraversal;
|
||||
import jadx.core.dex.visitors.IDexTreeVisitor;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import static jadx.core.dex.nodes.ProcessState.GENERATED_AND_UNLOADED;
|
||||
import static jadx.core.dex.nodes.ProcessState.LOADED;
|
||||
import static jadx.core.dex.nodes.ProcessState.NOT_LOADED;
|
||||
import static jadx.core.dex.nodes.ProcessState.PROCESS_COMPLETE;
|
||||
@@ -20,18 +23,33 @@ public final class ProcessClass {
|
||||
private ProcessClass() {
|
||||
}
|
||||
|
||||
public static void process(ClassNode cls) {
|
||||
ClassNode topParentClass = cls.getTopParentClass();
|
||||
if (topParentClass != cls) {
|
||||
process(topParentClass);
|
||||
return;
|
||||
}
|
||||
if (cls.getState().isProcessed()) {
|
||||
@Nullable
|
||||
private static ICodeInfo process(ClassNode cls, boolean codegen) {
|
||||
if (!codegen && cls.getState() == PROCESS_COMPLETE) {
|
||||
// nothing to do
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
synchronized (cls.getClassInfo()) {
|
||||
try {
|
||||
if (cls.contains(AFlag.CLASS_DEEP_RELOAD)) {
|
||||
cls.remove(AFlag.CLASS_DEEP_RELOAD);
|
||||
cls.unload();
|
||||
cls.deepUnload();
|
||||
cls.root().runPreDecompileStageForClass(cls);
|
||||
}
|
||||
if (codegen) {
|
||||
if (cls.getState() == GENERATED_AND_UNLOADED) {
|
||||
// allow to run code generation again
|
||||
cls.setState(NOT_LOADED);
|
||||
}
|
||||
cls.setLoadStage(LoadStage.CODEGEN_STAGE);
|
||||
if (cls.contains(AFlag.RELOAD_AT_CODEGEN_STAGE)) {
|
||||
cls.remove(AFlag.RELOAD_AT_CODEGEN_STAGE);
|
||||
cls.unload();
|
||||
}
|
||||
} else {
|
||||
cls.setLoadStage(LoadStage.PROCESS_STAGE);
|
||||
}
|
||||
if (cls.getState() == NOT_LOADED) {
|
||||
cls.load();
|
||||
}
|
||||
@@ -42,9 +60,18 @@ public final class ProcessClass {
|
||||
}
|
||||
cls.setState(PROCESS_COMPLETE);
|
||||
}
|
||||
if (codegen) {
|
||||
ICodeInfo code = CodeGen.generate(cls);
|
||||
if (!cls.contains(AFlag.DONT_UNLOAD_CLASS)) {
|
||||
cls.unload();
|
||||
cls.setState(GENERATED_AND_UNLOADED);
|
||||
}
|
||||
return code;
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
ErrorsCounter.classError(cls, e.getClass().getSimpleName(), e);
|
||||
cls.addError("Class process error: " + e.getClass().getSimpleName(), e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,11 +82,13 @@ public final class ProcessClass {
|
||||
return generateCode(topParentClass);
|
||||
}
|
||||
try {
|
||||
process(cls);
|
||||
cls.getDependencies().forEach(ProcessClass::process);
|
||||
|
||||
ICodeInfo code = CodeGen.generate(cls);
|
||||
cls.unload();
|
||||
for (ClassNode depCls : cls.getDependencies()) {
|
||||
process(depCls, false);
|
||||
}
|
||||
ICodeInfo code = process(cls, true);
|
||||
if (code == null) {
|
||||
throw new JadxRuntimeException("Codegen failed");
|
||||
}
|
||||
return code;
|
||||
} catch (Throwable e) {
|
||||
throw new JadxRuntimeException("Failed to generate code for class: " + cls.getFullName(), e);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package jadx.core.clsp;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
@@ -16,23 +17,29 @@ import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.plugins.utils.ZipSecurity;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.GenericInfo;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.exceptions.DecodeException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
import jadx.core.utils.files.ZipSecurity;
|
||||
|
||||
import static jadx.core.utils.Utils.notEmpty;
|
||||
|
||||
/**
|
||||
* Classes list for import into classpath graph
|
||||
@@ -45,130 +52,139 @@ public class ClsSet {
|
||||
private static final String CLST_PKG_PATH = ClsSet.class.getPackage().getName().replace('.', '/');
|
||||
|
||||
private static final String JADX_CLS_SET_HEADER = "jadx-cst";
|
||||
private static final int VERSION = 2;
|
||||
private static final int VERSION = 3;
|
||||
|
||||
private static final String STRING_CHARSET = "US-ASCII";
|
||||
|
||||
private static final NClass[] EMPTY_NCLASS_ARRAY = new NClass[0];
|
||||
private static final ArgType[] EMPTY_ARGTYPE_ARRAY = new ArgType[0];
|
||||
|
||||
private enum TypeEnum {
|
||||
WILDCARD, GENERIC, GENERIC_TYPE, OBJECT, ARRAY, PRIMITIVE
|
||||
private final RootNode root;
|
||||
|
||||
public ClsSet(RootNode root) {
|
||||
this.root = root;
|
||||
}
|
||||
|
||||
private NClass[] classes;
|
||||
private enum TypeEnum {
|
||||
WILDCARD,
|
||||
GENERIC,
|
||||
GENERIC_TYPE_VARIABLE,
|
||||
OUTER_GENERIC,
|
||||
OBJECT,
|
||||
ARRAY,
|
||||
PRIMITIVE
|
||||
}
|
||||
|
||||
private ClspClass[] classes;
|
||||
|
||||
public void loadFromClstFile() throws IOException, DecodeException {
|
||||
long startTime = System.currentTimeMillis();
|
||||
try (InputStream input = getClass().getResourceAsStream(CLST_FILENAME)) {
|
||||
if (input == null) {
|
||||
throw new JadxRuntimeException("Can't load classpath file: " + CLST_FILENAME);
|
||||
}
|
||||
load(input);
|
||||
}
|
||||
if (LOG.isDebugEnabled()) {
|
||||
long time = System.currentTimeMillis() - startTime;
|
||||
int methodsCount = Stream.of(classes).mapToInt(clspClass -> clspClass.getMethodsMap().size()).sum();
|
||||
LOG.debug("Clst file loaded in {}ms, classes: {}, methods: {}", time, classes.length, methodsCount);
|
||||
}
|
||||
}
|
||||
|
||||
public void loadFrom(RootNode root) {
|
||||
List<ClassNode> list = root.getClasses(true);
|
||||
Map<String, NClass> names = new HashMap<>(list.size());
|
||||
Map<String, ClspClass> names = new HashMap<>(list.size());
|
||||
int k = 0;
|
||||
for (ClassNode cls : list) {
|
||||
String clsRawName = cls.getRawName();
|
||||
if (cls.getAccessFlags().isPublic()) {
|
||||
cls.load();
|
||||
NClass nClass = new NClass(clsRawName, k);
|
||||
if (names.put(clsRawName, nClass) != null) {
|
||||
throw new JadxRuntimeException("Duplicate class: " + clsRawName);
|
||||
}
|
||||
k++;
|
||||
nClass.setGenerics(cls.getGenerics());
|
||||
nClass.setMethods(getMethodsDetails(cls));
|
||||
} else {
|
||||
names.put(clsRawName, null);
|
||||
ArgType clsType = cls.getClassInfo().getType();
|
||||
String clsRawName = clsType.getObject();
|
||||
cls.load();
|
||||
ClspClass nClass = new ClspClass(clsType, k);
|
||||
if (names.put(clsRawName, nClass) != null) {
|
||||
throw new JadxRuntimeException("Duplicate class: " + clsRawName);
|
||||
}
|
||||
k++;
|
||||
nClass.setTypeParameters(cls.getGenericTypeParameters());
|
||||
nClass.setMethods(getMethodsDetails(cls));
|
||||
}
|
||||
classes = new NClass[k];
|
||||
classes = new ClspClass[k];
|
||||
k = 0;
|
||||
for (ClassNode cls : list) {
|
||||
if (cls.getAccessFlags().isPublic()) {
|
||||
NClass nClass = getCls(cls.getRawName(), names);
|
||||
if (nClass == null) {
|
||||
throw new JadxRuntimeException("Missing class: " + cls);
|
||||
}
|
||||
nClass.setParents(makeParentsArray(cls, names));
|
||||
classes[k] = nClass;
|
||||
k++;
|
||||
ClspClass nClass = getCls(cls, names);
|
||||
if (nClass == null) {
|
||||
throw new JadxRuntimeException("Missing class: " + cls);
|
||||
}
|
||||
nClass.setParents(makeParentsArray(cls));
|
||||
classes[k] = nClass;
|
||||
k++;
|
||||
}
|
||||
}
|
||||
|
||||
private List<NMethod> getMethodsDetails(ClassNode cls) {
|
||||
List<NMethod> methods = new ArrayList<>();
|
||||
for (MethodNode m : cls.getMethods()) {
|
||||
AccessInfo accessFlags = m.getAccessFlags();
|
||||
if (accessFlags.isPublic() || accessFlags.isProtected()) {
|
||||
processMethodDetails(methods, m, accessFlags);
|
||||
}
|
||||
private List<ClspMethod> getMethodsDetails(ClassNode cls) {
|
||||
List<MethodNode> methodsList = cls.getMethods();
|
||||
List<ClspMethod> methods = new ArrayList<>(methodsList.size());
|
||||
for (MethodNode mth : methodsList) {
|
||||
processMethodDetails(mth, methods);
|
||||
}
|
||||
return methods;
|
||||
}
|
||||
|
||||
private void processMethodDetails(List<NMethod> methods, MethodNode mth, AccessInfo accessFlags) {
|
||||
List<ArgType> args = mth.getArgTypes();
|
||||
boolean genericArg = false;
|
||||
ArgType[] genericArgs;
|
||||
if (args.isEmpty()) {
|
||||
genericArgs = null;
|
||||
} else {
|
||||
int argsCount = args.size();
|
||||
genericArgs = new ArgType[argsCount];
|
||||
for (int i = 0; i < argsCount; i++) {
|
||||
ArgType argType = args.get(i);
|
||||
if (argType.isGeneric() || argType.isGenericType()) {
|
||||
genericArgs[i] = argType;
|
||||
genericArg = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
ArgType retType = mth.getReturnType();
|
||||
if (!retType.isGeneric() && !retType.isGenericType()) {
|
||||
retType = null;
|
||||
private void processMethodDetails(MethodNode mth, List<ClspMethod> methods) {
|
||||
AccessInfo accessFlags = mth.getAccessFlags();
|
||||
if (accessFlags.isPrivate()) {
|
||||
return;
|
||||
}
|
||||
ArgType genericRetType = mth.getReturnType();
|
||||
boolean varArgs = accessFlags.isVarArgs();
|
||||
if (genericArg || retType != null || varArgs) {
|
||||
methods.add(new NMethod(mth.getMethodInfo().getShortId(), genericArgs, retType, varArgs));
|
||||
List<ArgType> throwList = mth.getThrows();
|
||||
List<ArgType> typeParameters = mth.getTypeParameters();
|
||||
// add only methods with additional info
|
||||
if (varArgs
|
||||
|| notEmpty(throwList)
|
||||
|| notEmpty(typeParameters)
|
||||
|| genericRetType.containsGeneric()
|
||||
|| mth.containsGenericArgs()
|
||||
|| mth.isArgsOverloaded()) {
|
||||
ClspMethod clspMethod = new ClspMethod(mth.getMethodInfo(),
|
||||
mth.getArgTypes(), genericRetType,
|
||||
typeParameters, varArgs, throwList);
|
||||
methods.add(clspMethod);
|
||||
}
|
||||
}
|
||||
|
||||
public static NClass[] makeParentsArray(ClassNode cls, Map<String, NClass> names) {
|
||||
List<NClass> parents = new ArrayList<>(1 + cls.getInterfaces().size());
|
||||
public static ArgType[] makeParentsArray(ClassNode cls) {
|
||||
ArgType superClass = cls.getSuperClass();
|
||||
if (superClass != null) {
|
||||
NClass c = getCls(superClass.getObject(), names);
|
||||
if (c != null) {
|
||||
parents.add(c);
|
||||
}
|
||||
if (superClass == null) {
|
||||
// cls is java.lang.Object
|
||||
return EMPTY_ARGTYPE_ARRAY;
|
||||
}
|
||||
ArgType[] parents = new ArgType[1 + cls.getInterfaces().size()];
|
||||
parents[0] = superClass;
|
||||
int k = 1;
|
||||
for (ArgType iface : cls.getInterfaces()) {
|
||||
NClass c = getCls(iface.getObject(), names);
|
||||
if (c != null) {
|
||||
parents.add(c);
|
||||
}
|
||||
parents[k] = iface;
|
||||
k++;
|
||||
}
|
||||
int size = parents.size();
|
||||
if (size == 0) {
|
||||
return EMPTY_NCLASS_ARRAY;
|
||||
}
|
||||
return parents.toArray(new NClass[size]);
|
||||
return parents;
|
||||
}
|
||||
|
||||
private static NClass getCls(String fullName, Map<String, NClass> names) {
|
||||
NClass cls = names.get(fullName);
|
||||
private static ClspClass getCls(ClassNode cls, Map<String, ClspClass> names) {
|
||||
return getCls(cls.getRawName(), names);
|
||||
}
|
||||
|
||||
private static ClspClass getCls(ArgType clsType, Map<String, ClspClass> names) {
|
||||
return getCls(clsType.getObject(), names);
|
||||
}
|
||||
|
||||
private static ClspClass getCls(String fullName, Map<String, ClspClass> names) {
|
||||
ClspClass cls = names.get(fullName);
|
||||
if (cls == null) {
|
||||
LOG.debug("Class not found: {}", fullName);
|
||||
}
|
||||
return cls;
|
||||
}
|
||||
|
||||
void save(Path path) throws IOException {
|
||||
public void save(Path path) throws IOException {
|
||||
FileUtils.makeDirsForFile(path);
|
||||
String outputName = path.getFileName().toString();
|
||||
if (outputName.endsWith(CLST_EXTENSION)) {
|
||||
@@ -182,97 +198,106 @@ public class ClsSet {
|
||||
try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(path));
|
||||
ZipInputStream in = new ZipInputStream(Files.newInputStream(temp))) {
|
||||
String clst = CLST_PKG_PATH + '/' + CLST_FILENAME;
|
||||
out.putNextEntry(new ZipEntry(clst));
|
||||
save(out);
|
||||
boolean clstReplaced = false;
|
||||
ZipEntry entry = in.getNextEntry();
|
||||
while (entry != null) {
|
||||
if (!entry.getName().equals(clst)) {
|
||||
out.putNextEntry(new ZipEntry(entry.getName()));
|
||||
String entryName = entry.getName();
|
||||
ZipEntry copyEntry = new ZipEntry(entryName);
|
||||
copyEntry.setLastModifiedTime(entry.getLastModifiedTime()); // preserve modified time
|
||||
out.putNextEntry(copyEntry);
|
||||
if (entryName.equals(clst)) {
|
||||
save(out);
|
||||
clstReplaced = true;
|
||||
} else {
|
||||
FileUtils.copyStream(in, out);
|
||||
}
|
||||
entry = in.getNextEntry();
|
||||
}
|
||||
if (!clstReplaced) {
|
||||
out.putNextEntry(new ZipEntry(clst));
|
||||
save(out);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new JadxRuntimeException("Unknown file format: " + outputName);
|
||||
}
|
||||
}
|
||||
|
||||
public void save(OutputStream output) throws IOException {
|
||||
private void save(OutputStream output) throws IOException {
|
||||
DataOutputStream out = new DataOutputStream(output);
|
||||
out.writeBytes(JADX_CLS_SET_HEADER);
|
||||
out.writeByte(VERSION);
|
||||
|
||||
LOG.info("Classes count: {}", classes.length);
|
||||
Map<String, NClass> names = new HashMap<>(classes.length);
|
||||
Map<String, ClspClass> names = new HashMap<>(classes.length);
|
||||
out.writeInt(classes.length);
|
||||
for (NClass cls : classes) {
|
||||
writeString(out, cls.getName());
|
||||
names.put(cls.getName(), cls);
|
||||
for (ClspClass cls : classes) {
|
||||
String clsName = cls.getName();
|
||||
writeString(out, clsName);
|
||||
names.put(clsName, cls);
|
||||
}
|
||||
for (NClass cls : classes) {
|
||||
NClass[] parents = cls.getParents();
|
||||
out.writeByte(parents.length);
|
||||
for (NClass parent : parents) {
|
||||
out.writeInt(parent.getId());
|
||||
}
|
||||
writeGenerics(out, cls, names);
|
||||
List<NMethod> methods = cls.getMethodsList();
|
||||
out.writeByte(methods.size());
|
||||
for (NMethod method : methods) {
|
||||
for (ClspClass cls : classes) {
|
||||
writeArgTypesArray(out, cls.getParents(), names);
|
||||
writeArgTypesList(out, cls.getTypeParameters(), names);
|
||||
List<ClspMethod> methods = cls.getSortedMethodsList();
|
||||
out.writeShort(methods.size());
|
||||
for (ClspMethod method : methods) {
|
||||
writeMethod(out, method, names);
|
||||
}
|
||||
}
|
||||
int methodsCount = Stream.of(classes).mapToInt(c -> c.getMethodsMap().size()).sum();
|
||||
LOG.info("Classes: {}, methods: {}, file size: {}B", classes.length, methodsCount, out.size());
|
||||
}
|
||||
|
||||
private static void writeGenerics(DataOutputStream out, NClass cls, Map<String, NClass> names) throws IOException {
|
||||
List<GenericInfo> genericsList = cls.getGenerics();
|
||||
out.writeByte(genericsList.size());
|
||||
for (GenericInfo genericInfo : genericsList) {
|
||||
writeArgType(out, genericInfo.getGenericType(), names);
|
||||
List<ArgType> extendsList = genericInfo.getExtendsList();
|
||||
out.writeByte(extendsList.size());
|
||||
for (ArgType type : extendsList) {
|
||||
private static void writeMethod(DataOutputStream out, ClspMethod method, Map<String, ClspClass> names) throws IOException {
|
||||
MethodInfo methodInfo = method.getMethodInfo();
|
||||
writeString(out, methodInfo.getName());
|
||||
writeArgTypesList(out, methodInfo.getArgumentsTypes(), names);
|
||||
writeArgType(out, methodInfo.getReturnType(), names);
|
||||
|
||||
writeArgTypesList(out, method.containsGenericArgs() ? method.getArgTypes() : Collections.emptyList(), names);
|
||||
writeArgType(out, method.getReturnType(), names);
|
||||
writeArgTypesList(out, method.getTypeParameters(), names);
|
||||
out.writeBoolean(method.isVarArg());
|
||||
writeArgTypesList(out, method.getThrows(), names);
|
||||
}
|
||||
|
||||
private static void writeArgTypesList(DataOutputStream out, List<ArgType> list, Map<String, ClspClass> names) throws IOException {
|
||||
int size = list.size();
|
||||
writeUnsignedByte(out, size);
|
||||
if (size != 0) {
|
||||
for (ArgType type : list) {
|
||||
writeArgType(out, type, names);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeMethod(DataOutputStream out, NMethod method, Map<String, NClass> names) throws IOException {
|
||||
writeLongString(out, method.getShortId());
|
||||
|
||||
ArgType[] argTypes = method.getGenericArgs();
|
||||
if (argTypes == null) {
|
||||
out.writeByte(0);
|
||||
} else {
|
||||
int argCount = 0;
|
||||
for (ArgType arg : argTypes) {
|
||||
if (arg != null) {
|
||||
argCount++;
|
||||
}
|
||||
}
|
||||
out.writeByte(argCount);
|
||||
// last argument first
|
||||
for (int i = argTypes.length - 1; i >= 0; i--) {
|
||||
ArgType argType = argTypes[i];
|
||||
if (argType != null) {
|
||||
out.writeByte(i);
|
||||
writeArgType(out, argType, names);
|
||||
}
|
||||
private static void writeArgTypesArray(DataOutputStream out, @Nullable ArgType[] arr, Map<String, ClspClass> names) throws IOException {
|
||||
if (arr == null) {
|
||||
out.writeByte(-1);
|
||||
return;
|
||||
}
|
||||
int size = arr.length;
|
||||
out.writeByte(size);
|
||||
if (size != 0) {
|
||||
for (ArgType type : arr) {
|
||||
writeArgType(out, type, names);
|
||||
}
|
||||
}
|
||||
if (method.getReturnType() == null) {
|
||||
out.writeBoolean(false);
|
||||
} else {
|
||||
out.writeBoolean(true);
|
||||
writeArgType(out, method.getReturnType(), names);
|
||||
}
|
||||
out.writeBoolean(method.isVarArgs());
|
||||
}
|
||||
|
||||
private static void writeArgType(DataOutputStream out, ArgType argType, Map<String, NClass> names) throws IOException {
|
||||
if (argType.getWildcardType() != null) {
|
||||
private static void writeArgType(DataOutputStream out, ArgType argType, Map<String, ClspClass> names) throws IOException {
|
||||
if (argType == null) {
|
||||
out.writeByte(-1);
|
||||
return;
|
||||
}
|
||||
if (argType.isPrimitive()) {
|
||||
out.writeByte(TypeEnum.PRIMITIVE.ordinal());
|
||||
out.writeByte(argType.getPrimitiveType().getShortName().charAt(0));
|
||||
} else if (argType.getOuterType() != null) {
|
||||
out.writeByte(TypeEnum.OUTER_GENERIC.ordinal());
|
||||
writeArgType(out, argType.getOuterType(), names);
|
||||
writeArgType(out, argType.getInnerType(), names);
|
||||
} else if (argType.getWildcardType() != null) {
|
||||
out.writeByte(TypeEnum.WILDCARD.ordinal());
|
||||
ArgType.WildcardBound bound = argType.getWildcardBound();
|
||||
out.writeByte(bound.getNum());
|
||||
@@ -281,28 +306,18 @@ public class ClsSet {
|
||||
}
|
||||
} else if (argType.isGeneric()) {
|
||||
out.writeByte(TypeEnum.GENERIC.ordinal());
|
||||
out.writeInt(names.get(argType.getObject()).getId());
|
||||
ArgType[] types = argType.getGenericTypes();
|
||||
if (types == null) {
|
||||
out.writeByte(0);
|
||||
} else {
|
||||
out.writeByte(types.length);
|
||||
for (ArgType type : types) {
|
||||
writeArgType(out, type, names);
|
||||
}
|
||||
}
|
||||
out.writeInt(getCls(argType, names).getId());
|
||||
writeArgTypesList(out, argType.getGenericTypes(), names);
|
||||
} else if (argType.isGenericType()) {
|
||||
out.writeByte(TypeEnum.GENERIC_TYPE.ordinal());
|
||||
out.writeByte(TypeEnum.GENERIC_TYPE_VARIABLE.ordinal());
|
||||
writeString(out, argType.getObject());
|
||||
writeArgTypesList(out, argType.getExtendTypes(), names);
|
||||
} else if (argType.isObject()) {
|
||||
out.writeByte(TypeEnum.OBJECT.ordinal());
|
||||
out.writeInt(names.get(argType.getObject()).getId());
|
||||
out.writeInt(getCls(argType, names).getId());
|
||||
} else if (argType.isArray()) {
|
||||
out.writeByte(TypeEnum.ARRAY.ordinal());
|
||||
writeArgType(out, argType.getArrayElement(), names);
|
||||
} else if (argType.isPrimitive()) {
|
||||
out.writeByte(TypeEnum.PRIMITIVE.ordinal());
|
||||
out.writeByte(argType.getPrimitiveType().getShortName().charAt(0));
|
||||
} else {
|
||||
throw new JadxRuntimeException("Cannot save type: " + argType);
|
||||
}
|
||||
@@ -310,27 +325,27 @@ public class ClsSet {
|
||||
|
||||
private void load(File input) throws IOException, DecodeException {
|
||||
String name = input.getName();
|
||||
try (InputStream inputStream = new FileInputStream(input)) {
|
||||
if (name.endsWith(CLST_EXTENSION)) {
|
||||
if (name.endsWith(CLST_EXTENSION)) {
|
||||
try (InputStream inputStream = new FileInputStream(input)) {
|
||||
load(inputStream);
|
||||
} else if (name.endsWith(".jar")) {
|
||||
try (ZipInputStream in = new ZipInputStream(inputStream)) {
|
||||
ZipEntry entry = in.getNextEntry();
|
||||
while (entry != null) {
|
||||
if (entry.getName().endsWith(CLST_EXTENSION) && ZipSecurity.isValidZipEntry(entry)) {
|
||||
load(in);
|
||||
}
|
||||
entry = in.getNextEntry();
|
||||
}
|
||||
} else if (name.endsWith(".jar")) {
|
||||
ZipSecurity.readZipEntries(input, (entry, in) -> {
|
||||
if (entry.getName().endsWith(CLST_EXTENSION)) {
|
||||
try {
|
||||
load(in);
|
||||
} catch (Exception e) {
|
||||
throw new JadxRuntimeException("Failed to load jadx class set");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new JadxRuntimeException("Unknown file format: " + name);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
throw new JadxRuntimeException("Unknown file format: " + name);
|
||||
}
|
||||
}
|
||||
|
||||
private void load(InputStream input) throws IOException, DecodeException {
|
||||
try (DataInputStream in = new DataInputStream(input)) {
|
||||
try (DataInputStream in = new DataInputStream(new BufferedInputStream(input))) {
|
||||
byte[] header = new byte[JADX_CLS_SET_HEADER.length()];
|
||||
int readHeaderLength = in.read(header);
|
||||
int version = in.readByte();
|
||||
@@ -339,103 +354,113 @@ public class ClsSet {
|
||||
|| version != VERSION) {
|
||||
throw new DecodeException("Wrong jadx class set header");
|
||||
}
|
||||
int count = in.readInt();
|
||||
classes = new NClass[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
int clsCount = in.readInt();
|
||||
classes = new ClspClass[clsCount];
|
||||
for (int i = 0; i < clsCount; i++) {
|
||||
String name = readString(in);
|
||||
classes[i] = new NClass(name, i);
|
||||
classes[i] = new ClspClass(ArgType.object(name), i);
|
||||
}
|
||||
for (int i = 0; i < count; i++) {
|
||||
int pCount = in.readByte();
|
||||
NClass[] parents = new NClass[pCount];
|
||||
for (int j = 0; j < pCount; j++) {
|
||||
parents[j] = classes[in.readInt()];
|
||||
}
|
||||
NClass nClass = classes[i];
|
||||
nClass.setParents(parents);
|
||||
nClass.setGenerics(readGenerics(in));
|
||||
nClass.setMethods(readClsMethods(in));
|
||||
for (int i = 0; i < clsCount; i++) {
|
||||
ClspClass nClass = classes[i];
|
||||
ClassInfo clsInfo = ClassInfo.fromType(root, nClass.getClsType());
|
||||
nClass.setParents(readArgTypesArray(in));
|
||||
nClass.setTypeParameters(readArgTypesList(in));
|
||||
nClass.setMethods(readClsMethods(in, clsInfo));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<GenericInfo> readGenerics(DataInputStream in) throws IOException {
|
||||
int count = in.readByte();
|
||||
if (count == 0) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<GenericInfo> list = new ArrayList<>(count);
|
||||
for (int i = 0; i < count; i++) {
|
||||
ArgType genericType = readArgType(in);
|
||||
List<ArgType> extendsList;
|
||||
byte extCount = in.readByte();
|
||||
if (extCount == 0) {
|
||||
extendsList = Collections.emptyList();
|
||||
} else {
|
||||
extendsList = new ArrayList<>(extCount);
|
||||
for (int j = 0; j < extCount; j++) {
|
||||
extendsList.add(readArgType(in));
|
||||
}
|
||||
}
|
||||
list.add(new GenericInfo(genericType, extendsList));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private List<NMethod> readClsMethods(DataInputStream in) throws IOException {
|
||||
int mCount = in.readByte();
|
||||
List<NMethod> methods = new ArrayList<>(mCount);
|
||||
private List<ClspMethod> readClsMethods(DataInputStream in, ClassInfo clsInfo) throws IOException {
|
||||
int mCount = in.readShort();
|
||||
List<ClspMethod> methods = new ArrayList<>(mCount);
|
||||
for (int j = 0; j < mCount; j++) {
|
||||
methods.add(readMethod(in));
|
||||
methods.add(readMethod(in, clsInfo));
|
||||
}
|
||||
return methods;
|
||||
}
|
||||
|
||||
private NMethod readMethod(DataInputStream in) throws IOException {
|
||||
String shortId = readLongString(in);
|
||||
int argCount = in.readByte();
|
||||
ArgType[] argTypes = null;
|
||||
for (int i = 0; i < argCount; i++) {
|
||||
int index = in.readByte();
|
||||
ArgType argType = readArgType(in);
|
||||
if (argTypes == null) {
|
||||
argTypes = new ArgType[index + 1];
|
||||
}
|
||||
argTypes[index] = argType;
|
||||
private ClspMethod readMethod(DataInputStream in, ClassInfo clsInfo) throws IOException {
|
||||
String name = readString(in);
|
||||
List<ArgType> argTypes = readArgTypesList(in);
|
||||
ArgType retType = readArgType(in);
|
||||
List<ArgType> genericArgTypes = readArgTypesList(in);
|
||||
if (genericArgTypes.isEmpty() || Objects.equals(genericArgTypes, argTypes)) {
|
||||
genericArgTypes = argTypes;
|
||||
}
|
||||
ArgType retType = in.readBoolean() ? readArgType(in) : null;
|
||||
ArgType genericRetType = readArgType(in);
|
||||
if (Objects.equals(genericRetType, retType)) {
|
||||
genericRetType = retType;
|
||||
}
|
||||
List<ArgType> typeParameters = readArgTypesList(in);
|
||||
boolean varArgs = in.readBoolean();
|
||||
return new NMethod(shortId, argTypes, retType, varArgs);
|
||||
List<ArgType> throwList = readArgTypesList(in);
|
||||
MethodInfo methodInfo = MethodInfo.fromDetails(root, clsInfo, name, argTypes, retType);
|
||||
return new ClspMethod(methodInfo,
|
||||
genericArgTypes, genericRetType,
|
||||
typeParameters, varArgs, throwList);
|
||||
}
|
||||
|
||||
private List<ArgType> readArgTypesList(DataInputStream in) throws IOException {
|
||||
int count = in.readByte();
|
||||
if (count == 0) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<ArgType> list = new ArrayList<>(count);
|
||||
for (int i = 0; i < count; i++) {
|
||||
list.add(readArgType(in));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private ArgType[] readArgTypesArray(DataInputStream in) throws IOException {
|
||||
int count = in.readByte();
|
||||
if (count == -1) {
|
||||
return null;
|
||||
}
|
||||
if (count == 0) {
|
||||
return EMPTY_ARGTYPE_ARRAY;
|
||||
}
|
||||
ArgType[] arr = new ArgType[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
arr[i] = readArgType(in);
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
private ArgType readArgType(DataInputStream in) throws IOException {
|
||||
int ordinal = in.readByte();
|
||||
if (ordinal == -1) {
|
||||
return null;
|
||||
}
|
||||
if (ordinal >= TypeEnum.values().length) {
|
||||
throw new JadxRuntimeException("Incorrect ordinal for type enum: " + ordinal);
|
||||
}
|
||||
switch (TypeEnum.values()[ordinal]) {
|
||||
case WILDCARD:
|
||||
int bounds = in.readByte();
|
||||
return bounds == 0
|
||||
? ArgType.wildcard()
|
||||
: ArgType.wildcard(readArgType(in), ArgType.WildcardBound.getByNum(bounds));
|
||||
ArgType.WildcardBound bound = ArgType.WildcardBound.getByNum(in.readByte());
|
||||
if (bound == ArgType.WildcardBound.UNBOUND) {
|
||||
return ArgType.WILDCARD;
|
||||
}
|
||||
ArgType objType = readArgType(in);
|
||||
return ArgType.wildcard(objType, bound);
|
||||
|
||||
case OUTER_GENERIC:
|
||||
ArgType outerType = readArgType(in);
|
||||
ArgType innerType = readArgType(in);
|
||||
return ArgType.outerGeneric(outerType, innerType);
|
||||
|
||||
case GENERIC:
|
||||
String obj = classes[in.readInt()].getName();
|
||||
int typeLength = in.readByte();
|
||||
ArgType[] generics;
|
||||
if (typeLength == 0) {
|
||||
generics = null;
|
||||
} else {
|
||||
generics = new ArgType[typeLength];
|
||||
for (int i = 0; i < typeLength; i++) {
|
||||
generics[i] = readArgType(in);
|
||||
}
|
||||
}
|
||||
return ArgType.generic(obj, generics);
|
||||
ArgType clsType = classes[in.readInt()].getClsType();
|
||||
return ArgType.generic(clsType, readArgTypesList(in));
|
||||
|
||||
case GENERIC_TYPE:
|
||||
return ArgType.genericType(readString(in));
|
||||
case GENERIC_TYPE_VARIABLE:
|
||||
String typeVar = readString(in);
|
||||
List<ArgType> extendTypes = readArgTypesList(in);
|
||||
return ArgType.genericType(typeVar, extendTypes);
|
||||
|
||||
case OBJECT:
|
||||
return ArgType.object(classes[in.readInt()].getName());
|
||||
return classes[in.readInt()].getClsType();
|
||||
|
||||
case ARRAY:
|
||||
return ArgType.array(readArgType(in));
|
||||
@@ -451,23 +476,16 @@ public class ClsSet {
|
||||
|
||||
private static void writeString(DataOutputStream out, String name) throws IOException {
|
||||
byte[] bytes = name.getBytes(STRING_CHARSET);
|
||||
out.writeByte(bytes.length);
|
||||
out.write(bytes);
|
||||
}
|
||||
|
||||
private static void writeLongString(DataOutputStream out, String name) throws IOException {
|
||||
byte[] bytes = name.getBytes(STRING_CHARSET);
|
||||
out.writeShort(bytes.length);
|
||||
int len = bytes.length;
|
||||
if (len >= 0xFF) {
|
||||
throw new JadxRuntimeException("String is too long: " + name);
|
||||
}
|
||||
writeUnsignedByte(out, bytes.length);
|
||||
out.write(bytes);
|
||||
}
|
||||
|
||||
private static String readString(DataInputStream in) throws IOException {
|
||||
int len = in.readByte();
|
||||
return readString(in, len);
|
||||
}
|
||||
|
||||
private static String readLongString(DataInputStream in) throws IOException {
|
||||
int len = in.readShort();
|
||||
int len = readUnsignedByte(in);
|
||||
return readString(in, len);
|
||||
}
|
||||
|
||||
@@ -485,12 +503,23 @@ public class ClsSet {
|
||||
return new String(bytes, STRING_CHARSET);
|
||||
}
|
||||
|
||||
private static void writeUnsignedByte(DataOutputStream out, int value) throws IOException {
|
||||
if (value < 0 || value >= 0xFF) {
|
||||
throw new JadxRuntimeException("Unsigned byte value is too big: " + value);
|
||||
}
|
||||
out.writeByte(value);
|
||||
}
|
||||
|
||||
private static int readUnsignedByte(DataInputStream in) throws IOException {
|
||||
return ((int) in.readByte()) & 0xFF;
|
||||
}
|
||||
|
||||
public int getClassesCount() {
|
||||
return classes.length;
|
||||
}
|
||||
|
||||
public void addToMap(Map<String, NClass> nameMap) {
|
||||
for (NClass cls : classes) {
|
||||
public void addToMap(Map<String, ClspClass> nameMap) {
|
||||
for (ClspClass cls : classes) {
|
||||
nameMap.put(cls.getName(), cls);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
package jadx.core.clsp;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
|
||||
/**
|
||||
* Class node in classpath graph
|
||||
*/
|
||||
public class ClspClass {
|
||||
|
||||
private final ArgType clsType;
|
||||
private final int id;
|
||||
private ArgType[] parents;
|
||||
private Map<String, ClspMethod> methodsMap = Collections.emptyMap();
|
||||
private List<ArgType> typeParameters = Collections.emptyList();
|
||||
|
||||
public ClspClass(ArgType clsType, int id) {
|
||||
this.clsType = clsType;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return clsType.getObject();
|
||||
}
|
||||
|
||||
public ArgType getClsType() {
|
||||
return clsType;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public ArgType[] getParents() {
|
||||
return parents;
|
||||
}
|
||||
|
||||
public void setParents(ArgType[] parents) {
|
||||
this.parents = parents;
|
||||
}
|
||||
|
||||
public Map<String, ClspMethod> getMethodsMap() {
|
||||
return methodsMap;
|
||||
}
|
||||
|
||||
public List<ClspMethod> getSortedMethodsList() {
|
||||
List<ClspMethod> list = new ArrayList<>(methodsMap.size());
|
||||
list.addAll(methodsMap.values());
|
||||
Collections.sort(list);
|
||||
return list;
|
||||
}
|
||||
|
||||
public void setMethodsMap(Map<String, ClspMethod> methodsMap) {
|
||||
this.methodsMap = Objects.requireNonNull(methodsMap);
|
||||
}
|
||||
|
||||
public void setMethods(List<ClspMethod> methods) {
|
||||
Map<String, ClspMethod> map = new HashMap<>(methods.size());
|
||||
for (ClspMethod mth : methods) {
|
||||
map.put(mth.getMethodInfo().getShortId(), mth);
|
||||
}
|
||||
setMethodsMap(map);
|
||||
}
|
||||
|
||||
public List<ArgType> getTypeParameters() {
|
||||
return typeParameters;
|
||||
}
|
||||
|
||||
public void setTypeParameters(List<ArgType> typeParameters) {
|
||||
this.typeParameters = typeParameters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return clsType.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
ClspClass nClass = (ClspClass) o;
|
||||
return clsType.equals(nClass.clsType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return clsType.toString();
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -17,6 +18,8 @@ import org.slf4j.LoggerFactory;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.IMethodDetails;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.exceptions.DecodeException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
@@ -26,13 +29,18 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
public class ClspGraph {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ClspGraph.class);
|
||||
|
||||
private final Map<String, Set<String>> ancestorCache = Collections.synchronizedMap(new WeakHashMap<>());
|
||||
private Map<String, NClass> nameMap;
|
||||
private final RootNode root;
|
||||
private final Map<String, Set<String>> superTypesCache = Collections.synchronizedMap(new WeakHashMap<>());
|
||||
private Map<String, ClspClass> nameMap;
|
||||
|
||||
private final Set<String> missingClasses = new HashSet<>();
|
||||
|
||||
public ClspGraph(RootNode rootNode) {
|
||||
this.root = rootNode;
|
||||
}
|
||||
|
||||
public void load() throws IOException, DecodeException {
|
||||
ClsSet set = new ClsSet();
|
||||
ClsSet set = new ClsSet(root);
|
||||
set.loadFromClstFile();
|
||||
addClasspath(set);
|
||||
}
|
||||
@@ -50,14 +58,8 @@ public class ClspGraph {
|
||||
if (nameMap == null) {
|
||||
throw new JadxRuntimeException("Classpath must be loaded first");
|
||||
}
|
||||
int size = classes.size();
|
||||
NClass[] nClasses = new NClass[size];
|
||||
int k = 0;
|
||||
for (ClassNode cls : classes) {
|
||||
nClasses[k++] = addClass(cls);
|
||||
}
|
||||
for (int i = 0; i < size; i++) {
|
||||
nClasses[i].setParents(ClsSet.makeParentsArray(classes.get(i), nameMap));
|
||||
addClass(cls);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,31 +67,51 @@ public class ClspGraph {
|
||||
return nameMap.containsKey(fullName);
|
||||
}
|
||||
|
||||
public NClass getClsDetails(ArgType type) {
|
||||
public ClspClass getClsDetails(ArgType type) {
|
||||
return nameMap.get(type.getObject());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public NMethod getMethodDetails(MethodInfo methodInfo) {
|
||||
NClass cls = nameMap.get(methodInfo.getDeclClass().getRawName());
|
||||
public IMethodDetails getMethodDetails(MethodInfo methodInfo) {
|
||||
ClspClass cls = nameMap.get(methodInfo.getDeclClass().getRawName());
|
||||
if (cls == null) {
|
||||
return null;
|
||||
}
|
||||
ClspMethod clspMethod = getMethodFromClass(cls, methodInfo);
|
||||
if (clspMethod != null) {
|
||||
return clspMethod;
|
||||
}
|
||||
// deep search
|
||||
for (ArgType parent : cls.getParents()) {
|
||||
ClspClass clspParent = getClspClass(parent);
|
||||
if (clspParent != null) {
|
||||
ClspMethod methodFromParent = getMethodFromClass(clspParent, methodInfo);
|
||||
if (methodFromParent != null) {
|
||||
return methodFromParent;
|
||||
}
|
||||
}
|
||||
}
|
||||
// all other methods in known ClspClass are 'simple'
|
||||
return new SimpleMethodDetails(methodInfo);
|
||||
}
|
||||
|
||||
private ClspMethod getMethodFromClass(ClspClass cls, MethodInfo methodInfo) {
|
||||
return cls.getMethodsMap().get(methodInfo.getShortId());
|
||||
}
|
||||
|
||||
private NClass addClass(ClassNode cls) {
|
||||
String rawName = cls.getRawName();
|
||||
NClass nClass = new NClass(rawName, -1);
|
||||
nameMap.put(rawName, nClass);
|
||||
return nClass;
|
||||
private void addClass(ClassNode cls) {
|
||||
ArgType clsType = cls.getClassInfo().getType();
|
||||
String rawName = clsType.getObject();
|
||||
ClspClass clspClass = new ClspClass(clsType, -1);
|
||||
clspClass.setParents(ClsSet.makeParentsArray(cls));
|
||||
nameMap.put(rawName, clspClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@code clsName} instanceof {@code implClsName}
|
||||
*/
|
||||
public boolean isImplements(String clsName, String implClsName) {
|
||||
Set<String> anc = getAncestors(clsName);
|
||||
Set<String> anc = getSuperTypes(clsName);
|
||||
return anc.contains(implClsName);
|
||||
}
|
||||
|
||||
@@ -107,7 +129,7 @@ public class ClspGraph {
|
||||
if (clsName.equals(implClsName)) {
|
||||
return clsName;
|
||||
}
|
||||
NClass cls = nameMap.get(implClsName);
|
||||
ClspClass cls = nameMap.get(implClsName);
|
||||
if (cls == null) {
|
||||
missingClasses.add(clsName);
|
||||
return null;
|
||||
@@ -115,52 +137,79 @@ public class ClspGraph {
|
||||
if (isImplements(clsName, implClsName)) {
|
||||
return implClsName;
|
||||
}
|
||||
Set<String> anc = getAncestors(clsName);
|
||||
Set<String> anc = getSuperTypes(clsName);
|
||||
return searchCommonParent(anc, cls);
|
||||
}
|
||||
|
||||
private String searchCommonParent(Set<String> anc, NClass cls) {
|
||||
for (NClass p : cls.getParents()) {
|
||||
String name = p.getName();
|
||||
private String searchCommonParent(Set<String> anc, ClspClass cls) {
|
||||
for (ArgType p : cls.getParents()) {
|
||||
String name = p.getObject();
|
||||
if (anc.contains(name)) {
|
||||
return name;
|
||||
}
|
||||
String r = searchCommonParent(anc, p);
|
||||
if (r != null) {
|
||||
return r;
|
||||
ClspClass nCls = getClspClass(p);
|
||||
if (nCls != null) {
|
||||
String r = searchCommonParent(anc, nCls);
|
||||
if (r != null) {
|
||||
return r;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Set<String> getAncestors(String clsName) {
|
||||
Set<String> result = ancestorCache.get(clsName);
|
||||
if (result != null) {
|
||||
return result;
|
||||
public Set<String> getSuperTypes(String clsName) {
|
||||
Set<String> fromCache = superTypesCache.get(clsName);
|
||||
if (fromCache != null) {
|
||||
return fromCache;
|
||||
}
|
||||
NClass cls = nameMap.get(clsName);
|
||||
ClspClass cls = nameMap.get(clsName);
|
||||
if (cls == null) {
|
||||
missingClasses.add(clsName);
|
||||
return Collections.emptySet();
|
||||
}
|
||||
result = new HashSet<>();
|
||||
addAncestorsNames(cls, result);
|
||||
Set<String> result = new HashSet<>();
|
||||
addSuperTypes(cls, result);
|
||||
return putInSuperTypesCache(clsName, result);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Set<String> putInSuperTypesCache(String clsName, Set<String> result) {
|
||||
if (result.isEmpty()) {
|
||||
result = Collections.emptySet();
|
||||
Set<String> empty = Collections.emptySet();
|
||||
superTypesCache.put(clsName, result);
|
||||
return empty;
|
||||
}
|
||||
ancestorCache.put(clsName, result);
|
||||
superTypesCache.put(clsName, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private void addAncestorsNames(NClass cls, Set<String> result) {
|
||||
boolean isNew = result.add(cls.getName());
|
||||
if (isNew) {
|
||||
for (NClass p : cls.getParents()) {
|
||||
addAncestorsNames(p, result);
|
||||
private void addSuperTypes(ClspClass cls, Set<String> result) {
|
||||
for (ArgType parentType : cls.getParents()) {
|
||||
if (parentType == null) {
|
||||
continue;
|
||||
}
|
||||
ClspClass parentCls = getClspClass(parentType);
|
||||
if (parentCls != null) {
|
||||
boolean isNew = result.add(parentCls.getName());
|
||||
if (isNew) {
|
||||
addSuperTypes(parentCls, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private ClspClass getClspClass(ArgType clsType) {
|
||||
ClspClass clspClass = nameMap.get(clsType.getObject());
|
||||
if (clspClass == null) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("External class not found: {}", clsType.getObject());
|
||||
}
|
||||
}
|
||||
return clspClass;
|
||||
}
|
||||
|
||||
public void printMissingClasses() {
|
||||
int count = missingClasses.size();
|
||||
if (count == 0) {
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
package jadx.core.clsp;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.IMethodDetails;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
/**
|
||||
* Method node in classpath graph.
|
||||
*/
|
||||
public class ClspMethod implements IMethodDetails, Comparable<ClspMethod> {
|
||||
|
||||
private final MethodInfo methodInfo;
|
||||
private final List<ArgType> argTypes;
|
||||
private final ArgType returnType;
|
||||
private final List<ArgType> typeParameters;
|
||||
private final List<ArgType> throwList;
|
||||
private final boolean varArg;
|
||||
|
||||
public ClspMethod(MethodInfo methodInfo,
|
||||
List<ArgType> argTypes, ArgType returnType,
|
||||
List<ArgType> typeParameters,
|
||||
boolean varArgs, List<ArgType> throwList) {
|
||||
this.methodInfo = methodInfo;
|
||||
this.argTypes = argTypes;
|
||||
this.returnType = returnType;
|
||||
this.typeParameters = typeParameters;
|
||||
this.throwList = throwList;
|
||||
this.varArg = varArgs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodInfo getMethodInfo() {
|
||||
return methodInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArgType getReturnType() {
|
||||
return returnType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ArgType> getArgTypes() {
|
||||
return argTypes;
|
||||
}
|
||||
|
||||
public boolean containsGenericArgs() {
|
||||
return !Objects.equals(argTypes, methodInfo.getArgumentsTypes());
|
||||
}
|
||||
|
||||
public int getArgsCount() {
|
||||
return argTypes.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ArgType> getTypeParameters() {
|
||||
return typeParameters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ArgType> getThrows() {
|
||||
return throwList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVarArg() {
|
||||
return varArg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof ClspMethod)) {
|
||||
return false;
|
||||
}
|
||||
ClspMethod other = (ClspMethod) o;
|
||||
return methodInfo.equals(other.methodInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return methodInfo.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull ClspMethod other) {
|
||||
return this.methodInfo.compareTo(other.methodInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("ClspMth{");
|
||||
if (Utils.notEmpty(getTypeParameters())) {
|
||||
sb.append('<');
|
||||
sb.append(Utils.listToString(getTypeParameters()));
|
||||
sb.append("> ");
|
||||
}
|
||||
sb.append(getMethodInfo().getFullName());
|
||||
sb.append('(');
|
||||
sb.append(Utils.listToString(getArgTypes()));
|
||||
sb.append("):");
|
||||
sb.append(getReturnType());
|
||||
if (isVarArg()) {
|
||||
sb.append(" VARARG");
|
||||
}
|
||||
List<ArgType> throwsList = getThrows();
|
||||
if (Utils.notEmpty(throwsList)) {
|
||||
sb.append(" throws ").append(Utils.listToString(throwsList));
|
||||
}
|
||||
sb.append('}');
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
package jadx.core.clsp;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.exceptions.DecodeException;
|
||||
import jadx.core.utils.files.InputFile;
|
||||
|
||||
/**
|
||||
* Utility class for convert dex or jar to jadx classes set (.jcst)
|
||||
*/
|
||||
public class ConvertToClsSet {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ConvertToClsSet.class);
|
||||
|
||||
public static void usage() {
|
||||
LOG.info("<output .jcst or .jar file> <several input dex or jar files> ");
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException, DecodeException {
|
||||
if (args.length < 2) {
|
||||
usage();
|
||||
System.exit(1);
|
||||
}
|
||||
Path output = Paths.get(args[0]);
|
||||
|
||||
List<InputFile> inputFiles = new ArrayList<>(args.length - 1);
|
||||
for (int i = 1; i < args.length; i++) {
|
||||
File f = new File(args[i]);
|
||||
if (f.isDirectory()) {
|
||||
addFilesFromDirectory(f, inputFiles);
|
||||
} else {
|
||||
InputFile.addFilesFrom(f, inputFiles, false);
|
||||
}
|
||||
}
|
||||
for (InputFile inputFile : inputFiles) {
|
||||
LOG.info("Loaded: {}", inputFile.getFile());
|
||||
}
|
||||
|
||||
RootNode root = new RootNode(new JadxArgs());
|
||||
root.load(inputFiles);
|
||||
|
||||
ClsSet set = new ClsSet();
|
||||
set.loadFrom(root);
|
||||
set.save(output);
|
||||
LOG.info("Output: {}", output);
|
||||
LOG.info("done");
|
||||
}
|
||||
|
||||
private static void addFilesFromDirectory(File dir, List<InputFile> inputFiles) {
|
||||
File[] files = dir.listFiles();
|
||||
if (files == null) {
|
||||
return;
|
||||
}
|
||||
for (File file : files) {
|
||||
if (file.isDirectory()) {
|
||||
addFilesFromDirectory(file, inputFiles);
|
||||
} else {
|
||||
try {
|
||||
InputFile.addFilesFrom(file, inputFiles, false);
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Skip file: {}, load error: {}", file, e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
package jadx.core.clsp;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import jadx.core.dex.nodes.GenericInfo;
|
||||
|
||||
/**
|
||||
* Class node in classpath graph
|
||||
*/
|
||||
public class NClass {
|
||||
|
||||
private final String name;
|
||||
private final int id;
|
||||
private NClass[] parents;
|
||||
private Map<String, NMethod> methodsMap = Collections.emptyMap();
|
||||
private List<GenericInfo> generics = Collections.emptyList();
|
||||
|
||||
public NClass(String name, int id) {
|
||||
this.name = name;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public NClass[] getParents() {
|
||||
return parents;
|
||||
}
|
||||
|
||||
public void setParents(NClass[] parents) {
|
||||
this.parents = parents;
|
||||
}
|
||||
|
||||
public Map<String, NMethod> getMethodsMap() {
|
||||
return methodsMap;
|
||||
}
|
||||
|
||||
public List<NMethod> getMethodsList() {
|
||||
List<NMethod> list = new ArrayList<>(methodsMap.size());
|
||||
list.addAll(methodsMap.values());
|
||||
Collections.sort(list);
|
||||
return list;
|
||||
}
|
||||
|
||||
public void setMethodsMap(Map<String, NMethod> methodsMap) {
|
||||
this.methodsMap = Objects.requireNonNull(methodsMap);
|
||||
}
|
||||
|
||||
public void setMethods(List<NMethod> methods) {
|
||||
Map<String, NMethod> map = new HashMap<>(methods.size());
|
||||
for (NMethod mth : methods) {
|
||||
map.put(mth.getShortId(), mth);
|
||||
}
|
||||
setMethodsMap(map);
|
||||
}
|
||||
|
||||
public List<GenericInfo> getGenerics() {
|
||||
return generics;
|
||||
}
|
||||
|
||||
public void setGenerics(List<GenericInfo> generics) {
|
||||
this.generics = generics;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return name.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
NClass nClass = (NClass) o;
|
||||
return name.equals(nClass.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
package jadx.core.clsp;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
|
||||
/**
|
||||
* Generic method node in classpath graph.
|
||||
*/
|
||||
public class NMethod implements Comparable<NMethod> {
|
||||
|
||||
private final String shortId;
|
||||
|
||||
/**
|
||||
* Array contains only generic args, others set to 'null', size can be less than total args count
|
||||
*/
|
||||
@Nullable
|
||||
private final ArgType[] genericArgs;
|
||||
|
||||
@Nullable
|
||||
private final ArgType retType;
|
||||
|
||||
private final boolean varArgs;
|
||||
|
||||
public NMethod(String shortId, @Nullable ArgType[] genericArgs, @Nullable ArgType retType, boolean varArgs) {
|
||||
this.shortId = shortId;
|
||||
this.genericArgs = genericArgs;
|
||||
this.retType = retType;
|
||||
this.varArgs = varArgs;
|
||||
}
|
||||
|
||||
public String getShortId() {
|
||||
return shortId;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ArgType[] getGenericArgs() {
|
||||
return genericArgs;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ArgType getGenericArg(int i) {
|
||||
ArgType[] args = this.genericArgs;
|
||||
if (args != null && i < args.length) {
|
||||
return args[i];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ArgType getReturnType() {
|
||||
return retType;
|
||||
}
|
||||
|
||||
public boolean isVarArgs() {
|
||||
return varArgs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof NMethod)) {
|
||||
return false;
|
||||
}
|
||||
NMethod other = (NMethod) o;
|
||||
return shortId.equals(other.shortId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return shortId.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull NMethod other) {
|
||||
return this.shortId.compareTo(other.shortId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "NMethod{'" + shortId + '\''
|
||||
+ ", argTypes=" + Arrays.toString(genericArgs)
|
||||
+ ", retType=" + retType
|
||||
+ ", varArgs=" + varArgs
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package jadx.core.clsp;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.IMethodDetails;
|
||||
|
||||
public class SimpleMethodDetails implements IMethodDetails {
|
||||
|
||||
private final MethodInfo methodInfo;
|
||||
|
||||
public SimpleMethodDetails(MethodInfo methodInfo) {
|
||||
this.methodInfo = methodInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodInfo getMethodInfo() {
|
||||
return methodInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArgType getReturnType() {
|
||||
return methodInfo.getReturnType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ArgType> getArgTypes() {
|
||||
return methodInfo.getArgumentsTypes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ArgType> getTypeParameters() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ArgType> getThrows() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVarArg() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SimpleMethodDetails{" + methodInfo + '}';
|
||||
}
|
||||
}
|
||||
@@ -7,10 +7,12 @@ import java.util.Map.Entry;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.plugins.input.data.IFieldData;
|
||||
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||
import jadx.api.plugins.input.data.annotations.IAnnotation;
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.IAttributeNode;
|
||||
import jadx.core.dex.attributes.annotations.Annotation;
|
||||
import jadx.core.dex.attributes.annotations.AnnotationsList;
|
||||
import jadx.core.dex.attributes.annotations.MethodParameters;
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
@@ -18,6 +20,7 @@ import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.StringUtils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
@@ -52,7 +55,7 @@ public class AnnotationGen {
|
||||
if (aList == null || aList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (Annotation a : aList.getAll()) {
|
||||
for (IAnnotation a : aList.getAll()) {
|
||||
formatAnnotation(code, a);
|
||||
code.add(' ');
|
||||
}
|
||||
@@ -63,7 +66,7 @@ public class AnnotationGen {
|
||||
if (aList == null || aList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (Annotation a : aList.getAll()) {
|
||||
for (IAnnotation a : aList.getAll()) {
|
||||
String aCls = a.getAnnotationClass();
|
||||
if (!aCls.startsWith(Consts.DALVIK_ANNOTATION_PKG)) {
|
||||
code.startLine();
|
||||
@@ -72,20 +75,20 @@ public class AnnotationGen {
|
||||
}
|
||||
}
|
||||
|
||||
private void formatAnnotation(CodeWriter code, Annotation a) {
|
||||
private void formatAnnotation(CodeWriter code, IAnnotation a) {
|
||||
code.add('@');
|
||||
ClassNode annCls = cls.dex().resolveClass(a.getType());
|
||||
ClassNode annCls = cls.root().resolveClass(a.getAnnotationClass());
|
||||
if (annCls != null) {
|
||||
classGen.useClass(code, annCls);
|
||||
} else {
|
||||
classGen.useType(code, a.getType());
|
||||
classGen.useClass(code, a.getAnnotationClass());
|
||||
}
|
||||
|
||||
Map<String, Object> vl = a.getValues();
|
||||
Map<String, EncodedValue> vl = a.getValues();
|
||||
if (!vl.isEmpty()) {
|
||||
code.add('(');
|
||||
for (Iterator<Entry<String, Object>> it = vl.entrySet().iterator(); it.hasNext();) {
|
||||
Entry<String, Object> e = it.next();
|
||||
for (Iterator<Entry<String, EncodedValue>> it = vl.entrySet().iterator(); it.hasNext();) {
|
||||
Entry<String, EncodedValue> e = it.next();
|
||||
String paramName = getParamName(annCls, e.getKey());
|
||||
if (paramName.equals("value") && vl.size() == 1) {
|
||||
// don't add "value = " if no other parameters
|
||||
@@ -93,7 +96,7 @@ public class AnnotationGen {
|
||||
code.add(paramName);
|
||||
code.add(" = ");
|
||||
}
|
||||
encodeValue(code, e.getValue());
|
||||
encodeValue(cls.root(), code, e.getValue());
|
||||
if (it.hasNext()) {
|
||||
code.add(", ");
|
||||
}
|
||||
@@ -113,13 +116,11 @@ public class AnnotationGen {
|
||||
return paramName;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void addThrows(MethodNode mth, CodeWriter code) {
|
||||
Annotation an = mth.getAnnotation(Consts.DALVIK_THROWS);
|
||||
if (an != null) {
|
||||
Object exs = an.getDefaultValue();
|
||||
List<ArgType> throwList = mth.getThrows();
|
||||
if (!throwList.isEmpty()) {
|
||||
code.add(" throws ");
|
||||
for (Iterator<ArgType> it = ((List<ArgType>) exs).iterator(); it.hasNext();) {
|
||||
for (Iterator<ArgType> it = throwList.iterator(); it.hasNext();) {
|
||||
ArgType ex = it.next();
|
||||
classGen.useType(code, ex);
|
||||
if (it.hasNext()) {
|
||||
@@ -129,66 +130,97 @@ public class AnnotationGen {
|
||||
}
|
||||
}
|
||||
|
||||
public Object getAnnotationDefaultValue(String name) {
|
||||
Annotation an = cls.getAnnotation(Consts.DALVIK_ANNOTATION_DEFAULT);
|
||||
public EncodedValue getAnnotationDefaultValue(String name) {
|
||||
IAnnotation an = cls.getAnnotation(Consts.DALVIK_ANNOTATION_DEFAULT);
|
||||
if (an != null) {
|
||||
Annotation defAnnotation = (Annotation) an.getDefaultValue();
|
||||
return defAnnotation.getValues().get(name);
|
||||
EncodedValue defValue = an.getDefaultValue();
|
||||
if (defValue != null) {
|
||||
IAnnotation defAnnotation = (IAnnotation) defValue.getValue();
|
||||
return defAnnotation.getValues().get(name);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO: refactor this boilerplate code
|
||||
public void encodeValue(CodeWriter code, Object val) {
|
||||
if (val == null) {
|
||||
public void encodeValue(RootNode root, CodeWriter code, EncodedValue encodedValue) {
|
||||
if (encodedValue == null) {
|
||||
code.add("null");
|
||||
return;
|
||||
}
|
||||
if (val instanceof String) {
|
||||
code.add(getStringUtils().unescapeString((String) val));
|
||||
} else if (val instanceof Integer) {
|
||||
code.add(TypeGen.formatInteger((Integer) val, false));
|
||||
} else if (val instanceof Character) {
|
||||
code.add(getStringUtils().unescapeChar((Character) val));
|
||||
} else if (val instanceof Boolean) {
|
||||
code.add(Boolean.TRUE.equals(val) ? "true" : "false");
|
||||
} else if (val instanceof Float) {
|
||||
code.add(TypeGen.formatFloat((Float) val));
|
||||
} else if (val instanceof Double) {
|
||||
code.add(TypeGen.formatDouble((Double) val));
|
||||
} else if (val instanceof Long) {
|
||||
code.add(TypeGen.formatLong((Long) val, false));
|
||||
} else if (val instanceof Short) {
|
||||
code.add(TypeGen.formatShort((Short) val, false));
|
||||
} else if (val instanceof Byte) {
|
||||
code.add(TypeGen.formatByte((Byte) val, false));
|
||||
} else if (val instanceof ArgType) {
|
||||
classGen.useType(code, (ArgType) val);
|
||||
code.add(".class");
|
||||
} else if (val instanceof FieldInfo) {
|
||||
// must be a static field
|
||||
FieldInfo field = (FieldInfo) val;
|
||||
InsnGen.makeStaticFieldAccess(code, field, classGen);
|
||||
} else if (val instanceof Iterable) {
|
||||
code.add('{');
|
||||
Iterator<?> it = ((Iterable<?>) val).iterator();
|
||||
while (it.hasNext()) {
|
||||
Object obj = it.next();
|
||||
encodeValue(code, obj);
|
||||
if (it.hasNext()) {
|
||||
code.add(", ");
|
||||
Object value = encodedValue.getValue();
|
||||
switch (encodedValue.getType()) {
|
||||
case ENCODED_NULL:
|
||||
code.add("null");
|
||||
break;
|
||||
case ENCODED_BOOLEAN:
|
||||
code.add(Boolean.TRUE.equals(value) ? "true" : "false");
|
||||
break;
|
||||
case ENCODED_BYTE:
|
||||
code.add(TypeGen.formatByte((Byte) value, false));
|
||||
break;
|
||||
case ENCODED_SHORT:
|
||||
code.add(TypeGen.formatShort((Short) value, false));
|
||||
break;
|
||||
case ENCODED_CHAR:
|
||||
code.add(getStringUtils().unescapeChar((Character) value));
|
||||
break;
|
||||
case ENCODED_INT:
|
||||
code.add(TypeGen.formatInteger((Integer) value, false));
|
||||
break;
|
||||
case ENCODED_LONG:
|
||||
code.add(TypeGen.formatLong((Long) value, false));
|
||||
break;
|
||||
case ENCODED_FLOAT:
|
||||
code.add(TypeGen.formatFloat((Float) value));
|
||||
break;
|
||||
case ENCODED_DOUBLE:
|
||||
code.add(TypeGen.formatDouble((Double) value));
|
||||
break;
|
||||
case ENCODED_STRING:
|
||||
code.add(getStringUtils().unescapeString((String) value));
|
||||
break;
|
||||
case ENCODED_TYPE:
|
||||
classGen.useType(code, ArgType.parse((String) value));
|
||||
code.add(".class");
|
||||
break;
|
||||
case ENCODED_ENUM:
|
||||
case ENCODED_FIELD:
|
||||
// must be a static field
|
||||
if (value instanceof IFieldData) {
|
||||
FieldInfo field = FieldInfo.fromData(root, (IFieldData) value);
|
||||
InsnGen.makeStaticFieldAccess(code, field, classGen);
|
||||
} else if (value instanceof FieldInfo) {
|
||||
InsnGen.makeStaticFieldAccess(code, (FieldInfo) value, classGen);
|
||||
} else {
|
||||
throw new JadxRuntimeException("Unexpected field type class: " + value.getClass());
|
||||
}
|
||||
}
|
||||
code.add('}');
|
||||
} else if (val instanceof Annotation) {
|
||||
formatAnnotation(code, (Annotation) val);
|
||||
} else {
|
||||
// TODO: also can be method values
|
||||
throw new JadxRuntimeException("Can't decode value: " + val + " (" + val.getClass() + ')');
|
||||
break;
|
||||
case ENCODED_METHOD:
|
||||
// TODO
|
||||
break;
|
||||
case ENCODED_ARRAY:
|
||||
code.add('{');
|
||||
Iterator<?> it = ((Iterable<?>) value).iterator();
|
||||
while (it.hasNext()) {
|
||||
EncodedValue v = (EncodedValue) it.next();
|
||||
encodeValue(cls.root(), code, v);
|
||||
if (it.hasNext()) {
|
||||
code.add(", ");
|
||||
}
|
||||
}
|
||||
code.add('}');
|
||||
break;
|
||||
case ENCODED_ANNOTATION:
|
||||
formatAnnotation(code, (IAnnotation) value);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new JadxRuntimeException("Can't decode value: " + encodedValue.getType() + " (" + encodedValue + ')');
|
||||
}
|
||||
}
|
||||
|
||||
private StringUtils getStringUtils() {
|
||||
return cls.dex().root().getStringUtils();
|
||||
return cls.root().getStringUtils();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package jadx.core.codegen;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
@@ -8,31 +9,36 @@ import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import com.android.dx.rop.code.AccessFlags;
|
||||
import com.google.common.collect.Streams;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.api.plugins.input.data.AccessFlags;
|
||||
import jadx.api.plugins.input.data.annotations.EncodedType;
|
||||
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.AttrNode;
|
||||
import jadx.core.dex.attributes.FieldInitAttr;
|
||||
import jadx.core.dex.attributes.FieldInitAttr.InitType;
|
||||
import jadx.core.dex.attributes.nodes.EnumClassAttr;
|
||||
import jadx.core.dex.attributes.nodes.EnumClassAttr.EnumField;
|
||||
import jadx.core.dex.attributes.nodes.JadxError;
|
||||
import jadx.core.dex.attributes.nodes.LineAttrNode;
|
||||
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.PrimitiveType;
|
||||
import jadx.core.dex.instructions.mods.ConstructorInsn;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.GenericInfo;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.parser.FieldInitAttr;
|
||||
import jadx.core.dex.nodes.parser.FieldInitAttr.InitType;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.CodeGenUtils;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.utils.Utils;
|
||||
@@ -51,6 +57,8 @@ public class ClassGen {
|
||||
private final Set<ClassInfo> imports = new HashSet<>();
|
||||
private int clsDeclLine;
|
||||
|
||||
private boolean bodyGenStarted;
|
||||
|
||||
public ClassGen(ClassNode cls, JadxArgs jadxArgs) {
|
||||
this(cls, null, jadxArgs.isUseImports(), jadxArgs.isFallbackMode(), jadxArgs.isShowInconsistentCode());
|
||||
}
|
||||
@@ -73,7 +81,7 @@ public class ClassGen {
|
||||
return cls;
|
||||
}
|
||||
|
||||
public CodeWriter makeClass() throws CodegenException {
|
||||
public ICodeInfo makeClass() throws CodegenException {
|
||||
CodeWriter clsBody = new CodeWriter();
|
||||
addClassCode(clsBody);
|
||||
|
||||
@@ -106,6 +114,9 @@ public class ClassGen {
|
||||
if (cls.contains(AFlag.DONT_GENERATE)) {
|
||||
return;
|
||||
}
|
||||
if (Consts.DEBUG_USAGE) {
|
||||
addClassUsageInfo(code, cls);
|
||||
}
|
||||
CodeGenUtils.addComments(code, cls);
|
||||
insertDecompilationProblems(code, cls);
|
||||
addClassDeclaration(code);
|
||||
@@ -115,17 +126,17 @@ public class ClassGen {
|
||||
public void addClassDeclaration(CodeWriter clsCode) {
|
||||
AccessInfo af = cls.getAccessFlags();
|
||||
if (af.isInterface()) {
|
||||
af = af.remove(AccessFlags.ACC_ABSTRACT)
|
||||
.remove(AccessFlags.ACC_STATIC);
|
||||
af = af.remove(AccessFlags.ABSTRACT)
|
||||
.remove(AccessFlags.STATIC);
|
||||
} else if (af.isEnum()) {
|
||||
af = af.remove(AccessFlags.ACC_FINAL)
|
||||
.remove(AccessFlags.ACC_ABSTRACT)
|
||||
.remove(AccessFlags.ACC_STATIC);
|
||||
af = af.remove(AccessFlags.FINAL)
|
||||
.remove(AccessFlags.ABSTRACT)
|
||||
.remove(AccessFlags.STATIC);
|
||||
}
|
||||
|
||||
// 'static' and 'private' modifier not allowed for top classes (not inner)
|
||||
if (!cls.getClassInfo().isInner()) {
|
||||
af = af.remove(AccessFlags.ACC_STATIC).remove(AccessFlags.ACC_PRIVATE);
|
||||
af = af.remove(AccessFlags.STATIC).remove(AccessFlags.PRIVATE);
|
||||
}
|
||||
|
||||
annotationGen.addForClass(clsCode);
|
||||
@@ -146,7 +157,7 @@ public class ClassGen {
|
||||
clsCode.attachDefinition(cls);
|
||||
clsCode.add(cls.getClassInfo().getAliasShortName());
|
||||
|
||||
addGenericMap(clsCode, cls.getGenerics(), true);
|
||||
addGenericTypeParameters(clsCode, cls.getGenericTypeParameters(), true);
|
||||
clsCode.add(' ');
|
||||
|
||||
ArgType sup = cls.getSuperClass();
|
||||
@@ -177,23 +188,22 @@ public class ClassGen {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean addGenericMap(CodeWriter code, List<GenericInfo> generics, boolean classDeclaration) {
|
||||
public boolean addGenericTypeParameters(CodeWriter code, List<ArgType> generics, boolean classDeclaration) {
|
||||
if (generics == null || generics.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
code.add('<');
|
||||
int i = 0;
|
||||
for (GenericInfo genericInfo : generics) {
|
||||
for (ArgType genericInfo : generics) {
|
||||
if (i != 0) {
|
||||
code.add(", ");
|
||||
}
|
||||
ArgType type = genericInfo.getGenericType();
|
||||
if (type.isGenericType()) {
|
||||
code.add(type.getObject());
|
||||
if (genericInfo.isGenericType()) {
|
||||
code.add(genericInfo.getObject());
|
||||
} else {
|
||||
useClass(code, type);
|
||||
useClass(code, genericInfo);
|
||||
}
|
||||
List<ArgType> list = genericInfo.getExtendsList();
|
||||
List<ArgType> list = genericInfo.getExtendTypes();
|
||||
if (list != null && !list.isEmpty()) {
|
||||
code.add(" extends ");
|
||||
for (Iterator<ArgType> it = list.iterator(); it.hasNext();) {
|
||||
@@ -220,9 +230,22 @@ public class ClassGen {
|
||||
}
|
||||
|
||||
public void addClassBody(CodeWriter clsCode) throws CodegenException {
|
||||
addClassBody(clsCode, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param printClassName allows to print the original class name as comment (e.g. for inlined
|
||||
* classes)
|
||||
*/
|
||||
public void addClassBody(CodeWriter clsCode, boolean printClassName) throws CodegenException {
|
||||
clsCode.add('{');
|
||||
setBodyGenStarted(true);
|
||||
clsDeclLine = clsCode.getLine();
|
||||
clsCode.incIndent();
|
||||
if (printClassName) {
|
||||
clsCode.startLine();
|
||||
clsCode.add("/* class " + cls.getFullName() + " */");
|
||||
}
|
||||
addFields(clsCode);
|
||||
addInnerClsAndMethods(clsCode);
|
||||
clsCode.decIndent();
|
||||
@@ -230,7 +253,8 @@ public class ClassGen {
|
||||
}
|
||||
|
||||
private void addInnerClsAndMethods(CodeWriter clsCode) {
|
||||
Streams.concat(cls.getInnerClasses().stream(), cls.getMethods().stream())
|
||||
Stream.of(cls.getInnerClasses(), cls.getMethods())
|
||||
.flatMap(Collection::stream)
|
||||
.filter(node -> !node.contains(AFlag.DONT_GENERATE))
|
||||
.sorted(Comparator.comparingInt(LineAttrNode::getSourceLine))
|
||||
.forEach(node -> {
|
||||
@@ -249,7 +273,7 @@ public class ClassGen {
|
||||
inClGen.addClassCode(code);
|
||||
imports.addAll(inClGen.getImports());
|
||||
} catch (Exception e) {
|
||||
ErrorsCounter.classError(innerCls, "Inner class code generation error", e);
|
||||
innerCls.addError("Inner class code generation error", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,7 +298,7 @@ public class ClassGen {
|
||||
throw new JadxRuntimeException("Method generation error", e);
|
||||
}
|
||||
code.newLine().add("/*");
|
||||
code.newLine().addMultiLine(ErrorsCounter.methodError(mth, "Method generation error", e));
|
||||
code.newLine().addMultiLine(ErrorsCounter.error(mth, "Method generation error", e));
|
||||
Utils.appendStackTrace(code, e);
|
||||
code.newLine().add("*/");
|
||||
code.setIndent(savedIndent);
|
||||
@@ -293,7 +317,7 @@ public class ClassGen {
|
||||
|
||||
public void addMethodCode(CodeWriter code, MethodNode mth) throws CodegenException {
|
||||
CodeGenUtils.addComments(code, mth);
|
||||
if (mth.getAccessFlags().isAbstract() || mth.getAccessFlags().isNative()) {
|
||||
if (mth.isNoCode()) {
|
||||
MethodGen mthGen = new MethodGen(this, mth);
|
||||
mthGen.addDefinition(code);
|
||||
code.add(';');
|
||||
@@ -353,6 +377,9 @@ public class ClassGen {
|
||||
if (f.contains(AFlag.DONT_GENERATE)) {
|
||||
return;
|
||||
}
|
||||
if (Consts.DEBUG_USAGE) {
|
||||
addFieldUsageInfo(code, f);
|
||||
}
|
||||
CodeGenUtils.addComments(code, f);
|
||||
annotationGen.addForField(code, f);
|
||||
|
||||
@@ -368,15 +395,16 @@ public class ClassGen {
|
||||
FieldInitAttr fv = f.get(AType.FIELD_INIT);
|
||||
if (fv != null) {
|
||||
code.add(" = ");
|
||||
if (fv.getValue() == null) {
|
||||
code.add(TypeGen.literalToString(0, f.getType(), cls, fallback));
|
||||
} else {
|
||||
if (fv.getValueType() == InitType.CONST) {
|
||||
annotationGen.encodeValue(code, fv.getValue());
|
||||
} else if (fv.getValueType() == InitType.INSN) {
|
||||
InsnGen insnGen = makeInsnGen(fv.getInsnMth());
|
||||
addInsnBody(insnGen, code, fv.getInsn());
|
||||
if (fv.getValueType() == InitType.CONST) {
|
||||
EncodedValue encodedValue = fv.getEncodedValue();
|
||||
if (encodedValue.getType() == EncodedType.ENCODED_NULL) {
|
||||
code.add(TypeGen.literalToString(0, f.getType(), cls, fallback));
|
||||
} else {
|
||||
annotationGen.encodeValue(cls.root(), code, encodedValue);
|
||||
}
|
||||
} else if (fv.getValueType() == InitType.INSN) {
|
||||
InsnGen insnGen = makeInsnGen(fv.getInsnMth());
|
||||
addInsnBody(insnGen, code, fv.getInsn());
|
||||
}
|
||||
}
|
||||
code.add(';');
|
||||
@@ -401,12 +429,13 @@ public class ClassGen {
|
||||
EnumField f = it.next();
|
||||
code.startLine(f.getField().getAlias());
|
||||
ConstructorInsn constrInsn = f.getConstrInsn();
|
||||
if (constrInsn.getArgsCount() > f.getStartArg()) {
|
||||
MethodNode callMth = cls.root().resolveMethod(constrInsn.getCallMth());
|
||||
int skipCount = getEnumCtrSkipArgsCount(callMth);
|
||||
if (constrInsn.getArgsCount() > skipCount) {
|
||||
if (igen == null) {
|
||||
igen = makeInsnGen(enumFields.getStaticMethod());
|
||||
}
|
||||
MethodNode callMth = cls.dex().resolveMethod(constrInsn.getCallMth());
|
||||
igen.generateMethodArguments(code, constrInsn, f.getStartArg(), callMth);
|
||||
igen.generateMethodArguments(code, constrInsn, 0, callMth);
|
||||
}
|
||||
if (f.getCls() != null) {
|
||||
code.add(' ');
|
||||
@@ -427,6 +456,16 @@ public class ClassGen {
|
||||
}
|
||||
}
|
||||
|
||||
private int getEnumCtrSkipArgsCount(@Nullable MethodNode callMth) {
|
||||
if (callMth != null) {
|
||||
SkipMethodArgsAttr skipArgsAttr = callMth.get(AType.SKIP_MTH_ARGS);
|
||||
if (skipArgsAttr != null) {
|
||||
return skipArgsAttr.getSkipCount();
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private InsnGen makeInsnGen(MethodNode mth) {
|
||||
MethodGen mthGen = new MethodGen(this, mth);
|
||||
return new InsnGen(mthGen, false);
|
||||
@@ -436,7 +475,7 @@ public class ClassGen {
|
||||
try {
|
||||
insnGen.makeInsn(insn, code, InsnGen.Flags.BODY_ONLY_NOWRAP);
|
||||
} catch (Exception e) {
|
||||
ErrorsCounter.classError(cls, "Failed to generate init code", e);
|
||||
cls.addError("Failed to generate init code", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -458,25 +497,30 @@ public class ClassGen {
|
||||
}
|
||||
}
|
||||
|
||||
public void useClass(CodeWriter code, String rawCls) {
|
||||
useClass(code, ArgType.object(rawCls));
|
||||
}
|
||||
|
||||
public void useClass(CodeWriter code, ArgType type) {
|
||||
ArgType outerType = type.getOuterType();
|
||||
if (outerType != null) {
|
||||
useClass(code, outerType);
|
||||
code.add('.');
|
||||
useClass(code, type.getInnerType());
|
||||
// import not needed, force use short name
|
||||
useClassShortName(code, type.getObject());
|
||||
return;
|
||||
}
|
||||
|
||||
useClass(code, ClassInfo.fromType(cls.root(), type));
|
||||
ArgType[] generics = type.getGenericTypes();
|
||||
List<ArgType> generics = type.getGenericTypes();
|
||||
if (generics != null) {
|
||||
code.add('<');
|
||||
int len = generics.length;
|
||||
int len = generics.size();
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (i != 0) {
|
||||
code.add(", ");
|
||||
}
|
||||
ArgType gt = generics[i];
|
||||
ArgType gt = generics.get(i);
|
||||
ArgType wt = gt.getWildcardType();
|
||||
if (wt != null) {
|
||||
ArgType.WildcardBound bound = gt.getWildcardBound();
|
||||
@@ -492,8 +536,17 @@ public class ClassGen {
|
||||
}
|
||||
}
|
||||
|
||||
private void useClassShortName(CodeWriter code, String object) {
|
||||
ClassInfo classInfo = ClassInfo.fromName(cls.root(), object);
|
||||
ClassNode classNode = cls.root().resolveClass(classInfo);
|
||||
if (classNode != null) {
|
||||
code.attachAnnotation(classNode);
|
||||
}
|
||||
code.add(classInfo.getAliasShortName());
|
||||
}
|
||||
|
||||
public void useClass(CodeWriter code, ClassInfo classInfo) {
|
||||
ClassNode classNode = cls.dex().resolveClass(classInfo);
|
||||
ClassNode classNode = cls.root().resolveClass(classInfo);
|
||||
if (classNode != null) {
|
||||
useClass(code, classNode);
|
||||
} else {
|
||||
@@ -533,12 +586,7 @@ public class ClassGen {
|
||||
if (extClsInfo.getPackage().equals(useCls.getPackage()) && !extClsInfo.isInner()) {
|
||||
return shortName;
|
||||
}
|
||||
// don't add import if class not public (must be accessed using inheritance)
|
||||
ClassNode classNode = cls.dex().resolveClass(extClsInfo);
|
||||
if (classNode != null && !classNode.getAccessFlags().isPublic()) {
|
||||
return shortName;
|
||||
}
|
||||
if (searchCollision(cls.dex(), useCls, extClsInfo)) {
|
||||
if (searchCollision(cls.root(), useCls, extClsInfo)) {
|
||||
return fullName;
|
||||
}
|
||||
// ignore classes from default package
|
||||
@@ -617,7 +665,7 @@ public class ClassGen {
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean searchCollision(DexNode dex, ClassInfo useCls, ClassInfo searchCls) {
|
||||
private static boolean searchCollision(RootNode root, ClassInfo useCls, ClassInfo searchCls) {
|
||||
if (useCls == null) {
|
||||
return false;
|
||||
}
|
||||
@@ -625,7 +673,7 @@ public class ClassGen {
|
||||
if (useCls.getAliasShortName().equals(shortName)) {
|
||||
return true;
|
||||
}
|
||||
ClassNode classNode = dex.resolveClass(useCls);
|
||||
ClassNode classNode = root.resolveClass(useCls);
|
||||
if (classNode != null) {
|
||||
for (ClassNode inner : classNode.getInnerClasses()) {
|
||||
if (inner.getShortName().equals(shortName)
|
||||
@@ -634,7 +682,7 @@ public class ClassGen {
|
||||
}
|
||||
}
|
||||
}
|
||||
return searchCollision(dex, useCls.getParentClass(), searchCls);
|
||||
return searchCollision(root, useCls.getParentClass(), searchCls);
|
||||
}
|
||||
|
||||
private void insertRenameInfo(CodeWriter code, ClassNode cls) {
|
||||
@@ -644,6 +692,40 @@ public class ClassGen {
|
||||
}
|
||||
}
|
||||
|
||||
private static void addClassUsageInfo(CodeWriter code, ClassNode cls) {
|
||||
List<ClassNode> deps = cls.getDependencies();
|
||||
code.startLine("// deps - ").add(Integer.toString(deps.size()));
|
||||
for (ClassNode depCls : deps) {
|
||||
code.startLine("// ").add(depCls.getClassInfo().getFullName());
|
||||
}
|
||||
List<ClassNode> useIn = cls.getUseIn();
|
||||
code.startLine("// use in - ").add(Integer.toString(useIn.size()));
|
||||
for (ClassNode useCls : useIn) {
|
||||
code.startLine("// ").add(useCls.getClassInfo().getFullName());
|
||||
}
|
||||
List<MethodNode> useInMths = cls.getUseInMth();
|
||||
code.startLine("// use in methods - ").add(Integer.toString(useInMths.size()));
|
||||
for (MethodNode useMth : useInMths) {
|
||||
code.startLine("// ").add(useMth.toString());
|
||||
}
|
||||
}
|
||||
|
||||
static void addMthUsageInfo(CodeWriter code, MethodNode mth) {
|
||||
List<MethodNode> useInMths = mth.getUseIn();
|
||||
code.startLine("// use in methods - ").add(Integer.toString(useInMths.size()));
|
||||
for (MethodNode useMth : useInMths) {
|
||||
code.startLine("// ").add(useMth.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private static void addFieldUsageInfo(CodeWriter code, FieldNode fieldNode) {
|
||||
List<MethodNode> useInMths = fieldNode.getUseIn();
|
||||
code.startLine("// use in methods - ").add(Integer.toString(useInMths.size()));
|
||||
for (MethodNode useMth : useInMths) {
|
||||
code.startLine("// ").add(useMth.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public ClassGen getParentGen() {
|
||||
return parentGen == null ? this : parentGen;
|
||||
}
|
||||
@@ -655,4 +737,12 @@ public class ClassGen {
|
||||
public boolean isFallbackMode() {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
public boolean isBodyGenStarted() {
|
||||
return bodyGenStarted;
|
||||
}
|
||||
|
||||
public void setBodyGenStarted(boolean bodyGenStarted) {
|
||||
this.bodyGenStarted = bodyGenStarted;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import java.util.concurrent.Callable;
|
||||
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.api.impl.SimpleCodeInfo;
|
||||
import jadx.core.codegen.json.JsonCodeGen;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
@@ -13,7 +14,7 @@ public class CodeGen {
|
||||
|
||||
public static ICodeInfo generate(ClassNode cls) {
|
||||
if (cls.contains(AFlag.DONT_GENERATE)) {
|
||||
return CodeWriter.EMPTY;
|
||||
return ICodeInfo.EMPTY;
|
||||
}
|
||||
JadxArgs args = cls.root().getArgs();
|
||||
switch (args.getOutputFormat()) {
|
||||
@@ -36,7 +37,7 @@ public class CodeGen {
|
||||
private static ICodeInfo generateJson(ClassNode cls) {
|
||||
JsonCodeGen codeGen = new JsonCodeGen(cls);
|
||||
String clsJson = wrapCodeGen(cls, codeGen::process);
|
||||
return new CodeWriter(clsJson);
|
||||
return new SimpleCodeInfo(clsJson);
|
||||
}
|
||||
|
||||
private static <R> R wrapCodeGen(ClassNode cls, Callable<R> codeGenFunc) {
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package jadx.core.codegen;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@@ -13,12 +11,12 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.CodePosition;
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.api.impl.SimpleCodeInfo;
|
||||
import jadx.core.dex.attributes.nodes.LineAttrNode;
|
||||
import jadx.core.utils.StringUtils;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
import jadx.core.utils.files.ZipSecurity;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
public class CodeWriter implements ICodeInfo {
|
||||
public class CodeWriter {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CodeWriter.class);
|
||||
|
||||
public static final String NL = System.getProperty("line.separator");
|
||||
@@ -35,8 +33,6 @@ public class CodeWriter implements ICodeInfo {
|
||||
INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR,
|
||||
};
|
||||
|
||||
public static final CodeWriter EMPTY = new CodeWriter().finish();
|
||||
|
||||
private StringBuilder buf;
|
||||
@Nullable
|
||||
private String code;
|
||||
@@ -58,12 +54,6 @@ public class CodeWriter implements ICodeInfo {
|
||||
}
|
||||
}
|
||||
|
||||
// create filled instance (just string wrapper)
|
||||
public CodeWriter(String code) {
|
||||
this.buf = null;
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public CodeWriter startLine() {
|
||||
addLine();
|
||||
addLineIndent();
|
||||
@@ -169,11 +159,7 @@ public class CodeWriter implements ICodeInfo {
|
||||
if (curIndent < INDENT_CACHE.length) {
|
||||
this.indentStr = INDENT_CACHE[curIndent];
|
||||
} else {
|
||||
StringBuilder s = new StringBuilder(curIndent * INDENT_STR.length());
|
||||
for (int i = 0; i < curIndent; i++) {
|
||||
s.append(INDENT_STR);
|
||||
}
|
||||
this.indentStr = s.toString();
|
||||
this.indentStr = Utils.strRepeat(INDENT_STR, curIndent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,11 +230,6 @@ public class CodeWriter implements ICodeInfo {
|
||||
return annotations.put(pos, obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<CodePosition, Object> getAnnotations() {
|
||||
return annotations;
|
||||
}
|
||||
|
||||
public void attachSourceLine(int sourceLine) {
|
||||
if (sourceLine == 0) {
|
||||
return;
|
||||
@@ -263,27 +244,12 @@ public class CodeWriter implements ICodeInfo {
|
||||
lineMap.put(decompiledLine, sourceLine);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Integer, Integer> getLineMapping() {
|
||||
return lineMap;
|
||||
}
|
||||
|
||||
public CodeWriter finish() {
|
||||
public ICodeInfo finish() {
|
||||
removeFirstEmptyLine();
|
||||
buf.trimToSize();
|
||||
processDefinitionAnnotations();
|
||||
code = buf.toString();
|
||||
buf = null;
|
||||
|
||||
annotations.entrySet().removeIf(entry -> {
|
||||
Object v = entry.getValue();
|
||||
if (v instanceof DefinitionWrapper) {
|
||||
LineAttrNode l = ((DefinitionWrapper) v).getNode();
|
||||
l.setDecompiledLine(entry.getKey().getLine());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
return this;
|
||||
return new SimpleCodeInfo(code, lineMap, annotations);
|
||||
}
|
||||
|
||||
private void removeFirstEmptyLine() {
|
||||
@@ -293,46 +259,33 @@ public class CodeWriter implements ICodeInfo {
|
||||
}
|
||||
}
|
||||
|
||||
private void processDefinitionAnnotations() {
|
||||
if (!annotations.isEmpty()) {
|
||||
annotations.entrySet().removeIf(entry -> {
|
||||
Object v = entry.getValue();
|
||||
if (v instanceof DefinitionWrapper) {
|
||||
LineAttrNode l = ((DefinitionWrapper) v).getNode();
|
||||
l.setDecompiledLine(entry.getKey().getLine());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public int bufLength() {
|
||||
return buf.length();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCodeStr() {
|
||||
if (code == null) {
|
||||
throw new NullPointerException("Code not set");
|
||||
finish();
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return buf == null ? code : buf.toString();
|
||||
}
|
||||
|
||||
public void save(File dir, String subDir, String fileName) {
|
||||
if (!ZipSecurity.isValidZipEntryName(subDir) || !ZipSecurity.isValidZipEntryName(fileName)) {
|
||||
return;
|
||||
}
|
||||
save(dir, new File(subDir, fileName).getPath());
|
||||
}
|
||||
|
||||
public void save(File dir, String fileName) {
|
||||
if (!ZipSecurity.isValidZipEntryName(fileName)) {
|
||||
return;
|
||||
}
|
||||
save(new File(dir, fileName));
|
||||
}
|
||||
|
||||
public void save(File file) {
|
||||
if (code == null) {
|
||||
finish();
|
||||
}
|
||||
File outFile = FileUtils.prepareFile(file);
|
||||
try (PrintWriter out = new PrintWriter(outFile, "UTF-8")) {
|
||||
out.println(code);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Save file error", e);
|
||||
}
|
||||
return code != null ? code : buf.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.regions.conditions.Compare;
|
||||
import jadx.core.dex.regions.conditions.IfCondition;
|
||||
import jadx.core.dex.regions.conditions.IfCondition.Mode;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
@@ -123,7 +122,7 @@ public class ConditionGen extends InsnGen {
|
||||
wrap(code, firstArg);
|
||||
return;
|
||||
}
|
||||
ErrorsCounter.methodWarn(mth, "Unsupported boolean condition " + op.getSymbol());
|
||||
mth.addWarn("Unsupported boolean condition " + op.getSymbol());
|
||||
}
|
||||
|
||||
addArg(code, firstArg, isArgWrapNeeded(firstArg));
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package jadx.core.codegen;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
@@ -10,23 +9,22 @@ import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.deobf.NameMapper;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
|
||||
import jadx.core.dex.attributes.nodes.GenericInfoAttr;
|
||||
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
||||
import jadx.core.dex.attributes.nodes.MethodInlineAttr;
|
||||
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.ArithNode;
|
||||
import jadx.core.dex.instructions.ArithOp;
|
||||
import jadx.core.dex.instructions.CallMthInterface;
|
||||
import jadx.core.dex.instructions.BaseInvokeNode;
|
||||
import jadx.core.dex.instructions.ConstClassNode;
|
||||
import jadx.core.dex.instructions.ConstStringNode;
|
||||
import jadx.core.dex.instructions.FillArrayNode;
|
||||
import jadx.core.dex.instructions.FillArrayInsn;
|
||||
import jadx.core.dex.instructions.FilledNewArrayNode;
|
||||
import jadx.core.dex.instructions.GotoNode;
|
||||
import jadx.core.dex.instructions.IfNode;
|
||||
@@ -35,14 +33,13 @@ import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.InvokeNode;
|
||||
import jadx.core.dex.instructions.InvokeType;
|
||||
import jadx.core.dex.instructions.NewArrayNode;
|
||||
import jadx.core.dex.instructions.SwitchNode;
|
||||
import jadx.core.dex.instructions.SwitchInsn;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.CodeVar;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.InsnWrapArg;
|
||||
import jadx.core.dex.instructions.args.LiteralArg;
|
||||
import jadx.core.dex.instructions.args.Named;
|
||||
import jadx.core.dex.instructions.args.NamedArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
import jadx.core.dex.instructions.mods.ConstructorInsn;
|
||||
@@ -53,7 +50,6 @@ import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.RegionUtils;
|
||||
import jadx.core.utils.TypeUtils;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
@@ -152,7 +148,7 @@ public class InsnGen {
|
||||
|
||||
private void instanceField(CodeWriter code, FieldInfo field, InsnArg arg) throws CodegenException {
|
||||
ClassNode pCls = mth.getParentClass();
|
||||
FieldNode fieldNode = pCls.dex().root().deepResolveField(field);
|
||||
FieldNode fieldNode = pCls.root().deepResolveField(field);
|
||||
if (fieldNode != null) {
|
||||
FieldReplaceAttr replace = fieldNode.get(AType.FIELD_REPLACE);
|
||||
if (replace != null) {
|
||||
@@ -183,14 +179,14 @@ public class InsnGen {
|
||||
ClassInfo declClass = field.getDeclClass();
|
||||
// TODO
|
||||
boolean fieldFromThisClass = clsGen.getClassNode().getClassInfo().equals(declClass);
|
||||
if (!fieldFromThisClass) {
|
||||
if (!fieldFromThisClass || !clsGen.isBodyGenStarted()) {
|
||||
// Android specific resources class handler
|
||||
if (!handleAppResField(code, clsGen, declClass)) {
|
||||
clsGen.useClass(code, declClass);
|
||||
}
|
||||
code.add('.');
|
||||
}
|
||||
FieldNode fieldNode = clsGen.getClassNode().dex().root().deepResolveField(field);
|
||||
FieldNode fieldNode = clsGen.getClassNode().root().deepResolveField(field);
|
||||
if (fieldNode != null) {
|
||||
code.attachAnnotation(fieldNode);
|
||||
}
|
||||
@@ -226,6 +222,9 @@ public class InsnGen {
|
||||
private static final Set<Flags> BODY_ONLY_NOWRAP_FLAGS = EnumSet.of(Flags.BODY_ONLY_NOWRAP);
|
||||
|
||||
protected void makeInsn(InsnNode insn, CodeWriter code, Flags flag) throws CodegenException {
|
||||
if (insn.getType() == InsnType.REGION_ARG) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (flag == Flags.BODY_ONLY || flag == Flags.BODY_ONLY_NOWRAP) {
|
||||
makeInsnBody(code, insn, flag == Flags.BODY_ONLY ? BODY_ONLY_FLAG : BODY_ONLY_NOWRAP_FLAGS);
|
||||
@@ -261,7 +260,7 @@ public class InsnGen {
|
||||
switch (insn.getType()) {
|
||||
case CONST_STR:
|
||||
String str = ((ConstStringNode) insn).getString();
|
||||
code.add(mth.dex().root().getStringUtils().unescapeString(str));
|
||||
code.add(mth.root().getStringUtils().unescapeString(str));
|
||||
break;
|
||||
|
||||
case CONST_CLASS:
|
||||
@@ -392,7 +391,7 @@ public class InsnGen {
|
||||
break;
|
||||
|
||||
case FILL_ARRAY:
|
||||
FillArrayNode arrayNode = (FillArrayNode) insn;
|
||||
FillArrayInsn arrayNode = (FillArrayInsn) insn;
|
||||
if (fallback) {
|
||||
String arrStr = arrayNode.dataToString();
|
||||
addArg(code, insn.getArg(0));
|
||||
@@ -467,7 +466,9 @@ public class InsnGen {
|
||||
case MONITOR_EXIT:
|
||||
if (isFallback()) {
|
||||
code.add("monitor-exit(");
|
||||
addArg(code, insn.getArg(0));
|
||||
if (insn.getArgsCount() == 1) {
|
||||
addArg(code, insn.getArg(0));
|
||||
}
|
||||
code.add(')');
|
||||
}
|
||||
break;
|
||||
@@ -504,15 +505,16 @@ public class InsnGen {
|
||||
|
||||
case SWITCH:
|
||||
fallbackOnlyInsn(insn);
|
||||
SwitchNode sw = (SwitchNode) insn;
|
||||
SwitchInsn sw = (SwitchInsn) insn;
|
||||
code.add("switch(");
|
||||
addArg(code, insn.getArg(0));
|
||||
code.add(") {");
|
||||
code.incIndent();
|
||||
for (int i = 0; i < sw.getCasesCount(); i++) {
|
||||
String key = sw.getKeys()[i].toString();
|
||||
code.startLine("case ").add(key).add(": goto ");
|
||||
code.add(MethodGen.getLabelName(sw.getTargets()[i])).add(';');
|
||||
int[] keys = sw.getKeys();
|
||||
int[] targets = sw.getTargets();
|
||||
for (int i = 0; i < keys.length; i++) {
|
||||
code.startLine("case ").add(Integer.toString(keys[i])).add(": goto ");
|
||||
code.add(MethodGen.getLabelName(targets[i])).add(';');
|
||||
}
|
||||
code.startLine("default: goto ");
|
||||
code.add(MethodGen.getLabelName(sw.getDefaultCaseOffset())).add(';');
|
||||
@@ -536,6 +538,21 @@ public class InsnGen {
|
||||
code.add(')');
|
||||
break;
|
||||
|
||||
case MOVE_RESULT:
|
||||
fallbackOnlyInsn(insn);
|
||||
code.add("move-result");
|
||||
break;
|
||||
|
||||
case FILL_ARRAY_DATA:
|
||||
fallbackOnlyInsn(insn);
|
||||
code.add("fill-array " + insn.toString());
|
||||
break;
|
||||
|
||||
case SWITCH_DATA:
|
||||
fallbackOnlyInsn(insn);
|
||||
code.add(insn.toString());
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new CodegenException(mth, "Unknown instruction: " + insn.getType());
|
||||
}
|
||||
@@ -545,11 +562,19 @@ public class InsnGen {
|
||||
* In most cases must be combined with new array instructions.
|
||||
* Use one by one array fill (can be replaced with System.arrayCopy)
|
||||
*/
|
||||
private void fillArray(CodeWriter code, FillArrayNode arrayNode) throws CodegenException {
|
||||
private void fillArray(CodeWriter code, FillArrayInsn arrayNode) throws CodegenException {
|
||||
code.add("// fill-array-data instruction");
|
||||
code.startLine();
|
||||
List<LiteralArg> args = arrayNode.getLiteralArgs(arrayNode.getElementType());
|
||||
InsnArg arrArg = arrayNode.getArg(0);
|
||||
ArgType arrayType = arrArg.getType();
|
||||
ArgType elemType;
|
||||
if (arrayType.isTypeKnown() && arrayType.isArray()) {
|
||||
elemType = arrayType.getArrayElement();
|
||||
} else {
|
||||
ArgType elementType = arrayNode.getElementType(); // unknown type
|
||||
elemType = elementType.selectFirst();
|
||||
}
|
||||
List<LiteralArg> args = arrayNode.getLiteralArgs(elemType);
|
||||
int len = args.size();
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (i != 0) {
|
||||
@@ -599,12 +624,12 @@ public class InsnGen {
|
||||
code.add('}');
|
||||
}
|
||||
|
||||
private void makeConstructor(ConstructorInsn insn, CodeWriter code)
|
||||
throws CodegenException {
|
||||
ClassNode cls = mth.dex().resolveClass(insn.getClassType());
|
||||
private void makeConstructor(ConstructorInsn insn, CodeWriter code) throws CodegenException {
|
||||
ClassNode cls = mth.root().resolveClass(insn.getClassType());
|
||||
if (cls != null && cls.isAnonymous() && !fallback) {
|
||||
cls.ensureProcessed();
|
||||
inlineAnonymousConstructor(code, cls, insn);
|
||||
mth.getParentClass().addInlinedClass(cls);
|
||||
return;
|
||||
}
|
||||
if (insn.isSelf()) {
|
||||
@@ -617,26 +642,24 @@ public class InsnGen {
|
||||
} else {
|
||||
code.add("new ");
|
||||
useClass(code, insn.getClassType());
|
||||
ArgType argType = insn.getResult().getSVar().getCodeVar().getType();
|
||||
boolean genericCls = cls == null || !cls.getGenerics().isEmpty();
|
||||
if (argType != null
|
||||
&& argType.getGenericTypes() != null
|
||||
&& genericCls) {
|
||||
GenericInfoAttr genericInfoAttr = insn.get(AType.GENERIC_INFO);
|
||||
if (genericInfoAttr != null) {
|
||||
code.add('<');
|
||||
if (insn.contains(AFlag.EXPLICIT_GENERICS)) {
|
||||
if (genericInfoAttr.isExplicit()) {
|
||||
boolean first = true;
|
||||
for (ArgType type : argType.getGenericTypes()) {
|
||||
for (ArgType type : genericInfoAttr.getGenericTypes()) {
|
||||
if (!first) {
|
||||
code.add(',');
|
||||
} else {
|
||||
first = false;
|
||||
}
|
||||
mgen.getClassGen().useType(code, type);
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
code.add('>');
|
||||
}
|
||||
}
|
||||
MethodNode callMth = mth.dex().resolveMethod(insn.getCallMth());
|
||||
MethodNode callMth = mth.root().resolveMethod(insn.getCallMth());
|
||||
generateMethodArguments(code, insn, 0, callMth);
|
||||
}
|
||||
|
||||
@@ -670,20 +693,15 @@ public class InsnGen {
|
||||
} else {
|
||||
useClass(code, parent);
|
||||
}
|
||||
MethodNode callMth = mth.dex().resolveMethod(insn.getCallMth());
|
||||
MethodNode callMth = mth.root().resolveMethod(insn.getCallMth());
|
||||
generateMethodArguments(code, insn, 0, callMth);
|
||||
code.add(' ');
|
||||
new ClassGen(cls, mgen.getClassGen().getParentGen()).addClassBody(code);
|
||||
new ClassGen(cls, mgen.getClassGen().getParentGen()).addClassBody(code, true);
|
||||
}
|
||||
|
||||
private void makeInvoke(InvokeNode insn, CodeWriter code) throws CodegenException {
|
||||
MethodInfo callMth = insn.getCallMth();
|
||||
|
||||
// inline method
|
||||
MethodNode callMthNode = mth.root().deepResolveMethod(callMth);
|
||||
if (callMthNode != null && inlineMethod(callMthNode, insn, code)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int k = 0;
|
||||
InvokeType type = insn.getInvokeType();
|
||||
@@ -758,30 +776,31 @@ public class InsnGen {
|
||||
return useCls.getParentClass().getClassInfo();
|
||||
}
|
||||
|
||||
void generateMethodArguments(CodeWriter code, InsnNode insn, int startArgNum,
|
||||
@Nullable MethodNode callMth) throws CodegenException {
|
||||
void generateMethodArguments(CodeWriter code, BaseInvokeNode insn, int startArgNum,
|
||||
@Nullable MethodNode mthNode) throws CodegenException {
|
||||
int k = startArgNum;
|
||||
if (callMth != null && callMth.contains(AFlag.SKIP_FIRST_ARG)) {
|
||||
if (mthNode != null && mthNode.contains(AFlag.SKIP_FIRST_ARG)) {
|
||||
k++;
|
||||
}
|
||||
int argsCount = insn.getArgsCount();
|
||||
code.add('(');
|
||||
boolean firstArg = true;
|
||||
if (k < argsCount) {
|
||||
boolean overloaded = callMth != null && callMth.isArgsOverload();
|
||||
for (int i = k; i < argsCount; i++) {
|
||||
InsnArg arg = insn.getArg(i);
|
||||
if (arg.contains(AFlag.SKIP_ARG)) {
|
||||
continue;
|
||||
}
|
||||
if (SkipMethodArgsAttr.isSkip(callMth, i - startArgNum)) {
|
||||
int argOrigPos = i - startArgNum;
|
||||
if (SkipMethodArgsAttr.isSkip(mthNode, argOrigPos)) {
|
||||
continue;
|
||||
}
|
||||
if (!firstArg) {
|
||||
code.add(", ");
|
||||
} else {
|
||||
firstArg = false;
|
||||
}
|
||||
boolean cast = addArgCast(code, insn, callMth, arg, i - startArgNum, overloaded);
|
||||
if (!cast && i == argsCount - 1 && processVarArg(code, callMth, arg)) {
|
||||
if (i == argsCount - 1 && processVarArg(code, insn, arg)) {
|
||||
continue;
|
||||
}
|
||||
addArg(code, arg, false);
|
||||
@@ -791,151 +810,31 @@ public class InsnGen {
|
||||
code.add(')');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add additional cast for method argument.
|
||||
*/
|
||||
private boolean addArgCast(CodeWriter code, InsnNode insn, @Nullable MethodNode callMth,
|
||||
InsnArg arg, int origPos, boolean overloaded) {
|
||||
ArgType castType = null;
|
||||
if (callMth != null) {
|
||||
List<ArgType> argTypes = callMth.getArgTypes();
|
||||
ArgType origType = argTypes.get(origPos);
|
||||
if (origType.isGenericType() && !callMth.getParentClass().equals(mth.getParentClass())) {
|
||||
// cancel cast
|
||||
return false;
|
||||
}
|
||||
if (insn instanceof CallMthInterface && origType.containsGenericType()) {
|
||||
ArgType clsType;
|
||||
CallMthInterface mthCall = (CallMthInterface) insn;
|
||||
RegisterArg instanceArg = mthCall.getInstanceArg();
|
||||
if (instanceArg != null) {
|
||||
clsType = instanceArg.getType();
|
||||
} else {
|
||||
clsType = mthCall.getCallMth().getDeclClass().getType();
|
||||
}
|
||||
ArgType replacedType = TypeUtils.replaceClassGenerics(root, clsType, origType);
|
||||
if (replacedType != null) {
|
||||
castType = replacedType;
|
||||
}
|
||||
if (castType == null) {
|
||||
ArgType invReplType = TypeUtils.replaceMethodGenerics(root, insn, origType);
|
||||
if (invReplType != null) {
|
||||
castType = invReplType;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (castType == null) {
|
||||
castType = origType;
|
||||
}
|
||||
} else {
|
||||
castType = arg.getType();
|
||||
}
|
||||
// TODO: check castType for left type variables
|
||||
|
||||
if (isCastNeeded(arg, castType, overloaded)) {
|
||||
code.add('(');
|
||||
useType(code, castType);
|
||||
code.add(") ");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isCastNeeded(InsnArg arg, ArgType origType, boolean overloaded) {
|
||||
ArgType argType = arg.getType();
|
||||
if (arg.isLiteral() && ((LiteralArg) arg).getLiteral() == 0
|
||||
&& (argType.isObject() || argType.isArray())) {
|
||||
return true;
|
||||
}
|
||||
if (argType.equals(origType)) {
|
||||
return false;
|
||||
}
|
||||
return overloaded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand varArgs from filled array.
|
||||
*/
|
||||
private boolean processVarArg(CodeWriter code, MethodNode callMth, InsnArg lastArg) throws CodegenException {
|
||||
if (callMth == null || !callMth.getAccessFlags().isVarArgs()) {
|
||||
private boolean processVarArg(CodeWriter code, BaseInvokeNode invokeInsn, InsnArg lastArg) throws CodegenException {
|
||||
if (!invokeInsn.contains(AFlag.VARARG_CALL)) {
|
||||
return false;
|
||||
}
|
||||
if (!lastArg.getType().isArray() || !lastArg.isInsnWrap()) {
|
||||
return false;
|
||||
}
|
||||
InsnNode insn = ((InsnWrapArg) lastArg).getWrapInsn();
|
||||
if (insn.getType() == InsnType.FILLED_NEW_ARRAY) {
|
||||
int count = insn.getArgsCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
InsnArg elemArg = insn.getArg(i);
|
||||
addArg(code, elemArg, false);
|
||||
if (i < count - 1) {
|
||||
code.add(", ");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean inlineMethod(MethodNode callMthNode, InvokeNode insn, CodeWriter code) throws CodegenException {
|
||||
MethodInlineAttr mia = callMthNode.get(AType.METHOD_INLINE);
|
||||
if (mia == null) {
|
||||
if (insn.getType() != InsnType.FILLED_NEW_ARRAY) {
|
||||
return false;
|
||||
}
|
||||
InsnNode inl = mia.getInsn();
|
||||
if (Consts.DEBUG) {
|
||||
code.add("/* inline method: ").add(callMthNode.toString()).add("*/").startLine();
|
||||
}
|
||||
if (forceAssign(inl, insn, callMthNode)) {
|
||||
ArgType varType = callMthNode.getReturnType();
|
||||
useType(code, varType);
|
||||
code.add(' ');
|
||||
code.add(mgen.getNameGen().assignNamedArg(new NamedArg("unused", varType)));
|
||||
code.add(" = ");
|
||||
}
|
||||
if (callMthNode.getMethodInfo().getArgumentsTypes().isEmpty()) {
|
||||
makeInsn(inl, code, Flags.BODY_ONLY);
|
||||
} else {
|
||||
// remap args
|
||||
InsnArg[] regs = new InsnArg[callMthNode.getRegsCount()];
|
||||
int[] regNums = mia.getArgsRegNums();
|
||||
for (int i = 0; i < regNums.length; i++) {
|
||||
InsnArg arg = insn.getArg(i);
|
||||
regs[regNums[i]] = arg;
|
||||
int count = insn.getArgsCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
InsnArg elemArg = insn.getArg(i);
|
||||
addArg(code, elemArg, false);
|
||||
if (i < count - 1) {
|
||||
code.add(", ");
|
||||
}
|
||||
// replace args
|
||||
InsnNode inlCopy = inl.copy();
|
||||
List<RegisterArg> inlArgs = new ArrayList<>();
|
||||
inlCopy.getRegisterArgs(inlArgs);
|
||||
for (RegisterArg r : inlArgs) {
|
||||
int regNum = r.getRegNum();
|
||||
if (regNum >= regs.length) {
|
||||
LOG.warn("Unknown register number {} in method call: {} from {}", r, callMthNode, mth);
|
||||
} else {
|
||||
InsnArg repl = regs[regNum];
|
||||
if (repl == null) {
|
||||
LOG.warn("Not passed register {} in method call: {} from {}", r, callMthNode, mth);
|
||||
} else {
|
||||
inlCopy.replaceArg(r, repl);
|
||||
}
|
||||
}
|
||||
}
|
||||
makeInsn(inlCopy, code, Flags.BODY_ONLY);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean forceAssign(InsnNode inlineInsn, InvokeNode parentInsn, MethodNode callMthNode) {
|
||||
if (parentInsn.getResult() != null) {
|
||||
return false;
|
||||
}
|
||||
if (parentInsn.contains(AFlag.WRAPPED)) {
|
||||
return false;
|
||||
}
|
||||
return !callMthNode.getReturnType().equals(ArgType.VOID);
|
||||
}
|
||||
|
||||
private void makeTernary(TernaryInsn insn, CodeWriter code, Set<Flags> state) throws CodegenException {
|
||||
boolean wrap = state.contains(Flags.BODY_ONLY);
|
||||
if (wrap) {
|
||||
@@ -944,7 +843,7 @@ public class InsnGen {
|
||||
InsnArg first = insn.getArg(0);
|
||||
InsnArg second = insn.getArg(1);
|
||||
ConditionGen condGen = new ConditionGen(this);
|
||||
if (first.equals(LiteralArg.TRUE) && second.equals(LiteralArg.FALSE)) {
|
||||
if (first.isTrue() && second.isFalse()) {
|
||||
condGen.add(code, insn.getCondition());
|
||||
} else {
|
||||
condGen.wrap(code, insn.getCondition());
|
||||
|
||||
@@ -7,25 +7,29 @@ import java.util.List;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.android.dx.rop.code.AccessFlags;
|
||||
|
||||
import jadx.api.plugins.input.data.AccessFlags;
|
||||
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.Jadx;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.annotations.MethodParameters;
|
||||
import jadx.core.dex.attributes.nodes.JumpInfo;
|
||||
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.instructions.ConstStringNode;
|
||||
import jadx.core.dex.instructions.IfNode;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.CodeVar;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
import jadx.core.dex.nodes.IMethodDetails;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.trycatch.CatchAttr;
|
||||
import jadx.core.dex.visitors.DepthTraversal;
|
||||
import jadx.core.dex.visitors.FallbackModeVisitor;
|
||||
import jadx.core.dex.visitors.IDexTreeVisitor;
|
||||
import jadx.core.utils.CodeGenUtils;
|
||||
import jadx.core.utils.InsnUtils;
|
||||
import jadx.core.utils.Utils;
|
||||
@@ -33,6 +37,10 @@ import jadx.core.utils.exceptions.CodegenException;
|
||||
import jadx.core.utils.exceptions.DecodeException;
|
||||
import jadx.core.utils.exceptions.JadxOverflowException;
|
||||
|
||||
import static jadx.core.codegen.MethodGen.FallbackOption.BLOCK_DUMP;
|
||||
import static jadx.core.codegen.MethodGen.FallbackOption.COMMENTED_DUMP;
|
||||
import static jadx.core.codegen.MethodGen.FallbackOption.FALLBACK_MODE;
|
||||
|
||||
public class MethodGen {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MethodGen.class);
|
||||
|
||||
@@ -72,18 +80,22 @@ public class MethodGen {
|
||||
code.attachDefinition(mth);
|
||||
return false;
|
||||
}
|
||||
if (Consts.DEBUG_USAGE) {
|
||||
ClassGen.addMthUsageInfo(code, mth);
|
||||
}
|
||||
addOverrideAnnotation(code, mth);
|
||||
annotationGen.addForMethod(code, mth);
|
||||
|
||||
AccessInfo clsAccFlags = mth.getParentClass().getAccessFlags();
|
||||
AccessInfo ai = mth.getAccessFlags();
|
||||
// don't add 'abstract' and 'public' to methods in interface
|
||||
if (clsAccFlags.isInterface()) {
|
||||
ai = ai.remove(AccessFlags.ACC_ABSTRACT);
|
||||
ai = ai.remove(AccessFlags.ACC_PUBLIC);
|
||||
ai = ai.remove(AccessFlags.ABSTRACT);
|
||||
ai = ai.remove(AccessFlags.PUBLIC);
|
||||
}
|
||||
// don't add 'public' for annotations
|
||||
if (clsAccFlags.isAnnotation()) {
|
||||
ai = ai.remove(AccessFlags.ACC_PUBLIC);
|
||||
ai = ai.remove(AccessFlags.PUBLIC);
|
||||
}
|
||||
|
||||
if (mth.getMethodInfo().hasAlias() && !ai.isConstructor()) {
|
||||
@@ -98,8 +110,12 @@ public class MethodGen {
|
||||
if (Consts.DEBUG) {
|
||||
code.add(mth.isVirtual() ? "/* virtual */ " : "/* direct */ ");
|
||||
}
|
||||
if (clsAccFlags.isInterface() && !mth.isNoCode()) {
|
||||
// add 'default' for method with code in interface
|
||||
code.add("default ");
|
||||
}
|
||||
|
||||
if (classGen.addGenericMap(code, mth.getGenerics(), false)) {
|
||||
if (classGen.addGenericTypeParameters(code, mth.getTypeParameters(), false)) {
|
||||
code.add(' ');
|
||||
}
|
||||
if (ai.isConstructor()) {
|
||||
@@ -133,15 +149,32 @@ public class MethodGen {
|
||||
|
||||
// add default value if in annotation class
|
||||
if (mth.getParentClass().getAccessFlags().isAnnotation()) {
|
||||
Object def = annotationGen.getAnnotationDefaultValue(mth.getName());
|
||||
EncodedValue def = annotationGen.getAnnotationDefaultValue(mth.getName());
|
||||
if (def != null) {
|
||||
code.add(" default ");
|
||||
annotationGen.encodeValue(code, def);
|
||||
annotationGen.encodeValue(mth.root(), code, def);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void addOverrideAnnotation(CodeWriter code, MethodNode mth) {
|
||||
MethodOverrideAttr overrideAttr = mth.get(AType.METHOD_OVERRIDE);
|
||||
if (overrideAttr == null) {
|
||||
return;
|
||||
}
|
||||
code.startLine("@Override");
|
||||
code.add(" // ");
|
||||
Iterator<IMethodDetails> it = overrideAttr.getOverrideList().iterator();
|
||||
while (it.hasNext()) {
|
||||
IMethodDetails methodDetails = it.next();
|
||||
code.add(methodDetails.getMethodInfo().getDeclClass().getAliasFullName());
|
||||
if (it.hasNext()) {
|
||||
code.add(", ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addMethodArguments(CodeWriter code, List<RegisterArg> args) {
|
||||
MethodParameters paramsAnnotation = mth.get(AType.ANNOTATION_MTH_PARAMETERS);
|
||||
int i = 0;
|
||||
@@ -152,7 +185,7 @@ public class MethodGen {
|
||||
CodeVar var;
|
||||
if (ssaVar == null) {
|
||||
// null for abstract or interface methods
|
||||
var = CodeVar.fromMthArg(mthArg);
|
||||
var = CodeVar.fromMthArg(mthArg, classGen.isFallbackMode());
|
||||
} else {
|
||||
var = ssaVar.getCodeVar();
|
||||
}
|
||||
@@ -197,7 +230,7 @@ public class MethodGen {
|
||||
|
||||
public void addInstructions(CodeWriter code) throws CodegenException {
|
||||
if (mth.root().getArgs().isFallbackMode()) {
|
||||
addFallbackMethodCode(code);
|
||||
addFallbackMethodCode(code, FALLBACK_MODE);
|
||||
} else if (classGen.isFallbackMode()) {
|
||||
dumpInstructions(code);
|
||||
} else {
|
||||
@@ -225,7 +258,7 @@ public class MethodGen {
|
||||
|
||||
public void dumpInstructions(CodeWriter code) {
|
||||
code.startLine("/*");
|
||||
addFallbackMethodCode(code);
|
||||
addFallbackMethodCode(code, COMMENTED_DUMP);
|
||||
code.startLine("*/");
|
||||
|
||||
code.startLine("throw new UnsupportedOperationException(\"Method not decompiled: ")
|
||||
@@ -239,33 +272,44 @@ public class MethodGen {
|
||||
.add("\");");
|
||||
}
|
||||
|
||||
public void addFallbackMethodCode(CodeWriter code) {
|
||||
if (mth.getInstructions() == null) {
|
||||
// load original instructions
|
||||
try {
|
||||
mth.unload();
|
||||
mth.load();
|
||||
DepthTraversal.visit(new FallbackModeVisitor(), mth);
|
||||
} catch (DecodeException e) {
|
||||
LOG.error("Error reload instructions in fallback mode:", e);
|
||||
code.startLine("// Can't load method instructions: " + e.getMessage());
|
||||
return;
|
||||
public void addFallbackMethodCode(CodeWriter code, FallbackOption fallbackOption) {
|
||||
// load original instructions
|
||||
try {
|
||||
mth.unload();
|
||||
mth.load();
|
||||
for (IDexTreeVisitor visitor : Jadx.getFallbackPassesList()) {
|
||||
DepthTraversal.visit(visitor, mth);
|
||||
}
|
||||
} catch (DecodeException e) {
|
||||
LOG.error("Error reload instructions in fallback mode:", e);
|
||||
code.startLine("// Can't load method instructions: " + e.getMessage());
|
||||
return;
|
||||
}
|
||||
InsnNode[] insnArr = mth.getInstructions();
|
||||
if (insnArr == null) {
|
||||
code.startLine("// Can't load method instructions.");
|
||||
return;
|
||||
}
|
||||
if (insnArr.length > 100) {
|
||||
code.startLine("// Method dump skipped, instructions count: " + insnArr.length);
|
||||
return;
|
||||
}
|
||||
code.incIndent();
|
||||
if (mth.getThisArg() != null) {
|
||||
code.startLine(nameGen.useArg(mth.getThisArg())).add(" = this;");
|
||||
}
|
||||
addFallbackInsns(code, mth, insnArr, true);
|
||||
addFallbackInsns(code, mth, insnArr, fallbackOption);
|
||||
code.decIndent();
|
||||
}
|
||||
|
||||
public static void addFallbackInsns(CodeWriter code, MethodNode mth, InsnNode[] insnArr, boolean addLabels) {
|
||||
public enum FallbackOption {
|
||||
FALLBACK_MODE,
|
||||
BLOCK_DUMP,
|
||||
COMMENTED_DUMP
|
||||
}
|
||||
|
||||
public static void addFallbackInsns(CodeWriter code, MethodNode mth, InsnNode[] insnArr, FallbackOption option) {
|
||||
int startIndent = code.getIndent();
|
||||
InsnGen insnGen = new InsnGen(getFallbackMethodGen(mth), true);
|
||||
boolean attachInsns = mth.root().getArgs().isJsonOutput();
|
||||
InsnNode prevInsn = null;
|
||||
@@ -273,7 +317,7 @@ public class MethodGen {
|
||||
if (insn == null) {
|
||||
continue;
|
||||
}
|
||||
if (addLabels && needLabel(insn, prevInsn)) {
|
||||
if (option != BLOCK_DUMP && needLabel(insn, prevInsn)) {
|
||||
code.decIndent();
|
||||
code.startLine(getLabelName(insn.getOffset()) + ':');
|
||||
code.incIndent();
|
||||
@@ -282,7 +326,14 @@ public class MethodGen {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
code.startLine();
|
||||
boolean escapeComment = isCommentEscapeNeeded(insn, option);
|
||||
if (escapeComment) {
|
||||
code.decIndent();
|
||||
code.startLine("*/");
|
||||
code.startLine("// ");
|
||||
} else {
|
||||
code.startLine();
|
||||
}
|
||||
if (attachInsns) {
|
||||
code.attachLineAnnotation(insn);
|
||||
}
|
||||
@@ -294,18 +345,34 @@ public class MethodGen {
|
||||
}
|
||||
}
|
||||
insnGen.makeInsn(insn, code, InsnGen.Flags.INLINE);
|
||||
if (escapeComment) {
|
||||
code.startLine("/*");
|
||||
code.incIndent();
|
||||
}
|
||||
|
||||
CatchAttr catchAttr = insn.get(AType.CATCH_BLOCK);
|
||||
if (catchAttr != null) {
|
||||
code.add(" // " + catchAttr);
|
||||
}
|
||||
} catch (CodegenException e) {
|
||||
} catch (Exception e) {
|
||||
LOG.debug("Error generate fallback instruction: ", e.getCause());
|
||||
code.setIndent(startIndent);
|
||||
code.startLine("// error: " + insn);
|
||||
}
|
||||
prevInsn = insn;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isCommentEscapeNeeded(InsnNode insn, FallbackOption option) {
|
||||
if (option == COMMENTED_DUMP) {
|
||||
if (insn.getType() == InsnType.CONST_STR) {
|
||||
String str = ((ConstStringNode) insn).getString();
|
||||
return str.contains("*/");
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean needLabel(InsnNode insn, InsnNode prevInsn) {
|
||||
if (insn.contains(AType.EXC_HANDLER)) {
|
||||
return true;
|
||||
|
||||
@@ -72,11 +72,13 @@ public class NameGen {
|
||||
}
|
||||
|
||||
public String assignArg(CodeVar var) {
|
||||
String name = makeArgName(var);
|
||||
if (fallback) {
|
||||
return name;
|
||||
return getFallbackName(var);
|
||||
}
|
||||
name = getUniqueVarName(name);
|
||||
if (var.isThis()) {
|
||||
return RegisterArg.THIS_ARG_NAME;
|
||||
}
|
||||
String name = getUniqueVarName(makeArgName(var));
|
||||
var.setName(name);
|
||||
return name;
|
||||
}
|
||||
@@ -118,24 +120,17 @@ public class NameGen {
|
||||
}
|
||||
|
||||
private String makeArgName(CodeVar var) {
|
||||
if (fallback) {
|
||||
return getFallbackName(var);
|
||||
}
|
||||
if (var.isThis()) {
|
||||
return RegisterArg.THIS_ARG_NAME;
|
||||
}
|
||||
String name = var.getName();
|
||||
String varName = name != null ? name : guessName(var);
|
||||
if (NameMapper.isReserved(varName)) {
|
||||
varName = varName + 'R';
|
||||
if (name == null) {
|
||||
name = guessName(var);
|
||||
}
|
||||
if (!NameMapper.isValidAndPrintable(varName)) {
|
||||
varName = getFallbackName(var);
|
||||
if (!NameMapper.isValidAndPrintable(name)) {
|
||||
name = getFallbackName(var);
|
||||
}
|
||||
if (Consts.DEBUG) {
|
||||
varName += '_' + getFallbackName(var);
|
||||
name += '_' + getFallbackName(var);
|
||||
}
|
||||
return varName;
|
||||
return name;
|
||||
}
|
||||
|
||||
private String getFallbackName(CodeVar var) {
|
||||
|
||||
@@ -10,11 +10,13 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.FieldInitAttr;
|
||||
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
|
||||
import jadx.core.dex.attributes.nodes.ForceReturnAttr;
|
||||
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.instructions.SwitchNode;
|
||||
import jadx.core.dex.instructions.SwitchInsn;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.CodeVar;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.NamedArg;
|
||||
@@ -25,9 +27,9 @@ import jadx.core.dex.nodes.IBlock;
|
||||
import jadx.core.dex.nodes.IContainer;
|
||||
import jadx.core.dex.nodes.IRegion;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.parser.FieldInitAttr;
|
||||
import jadx.core.dex.regions.Region;
|
||||
import jadx.core.dex.regions.SwitchRegion;
|
||||
import jadx.core.dex.regions.SwitchRegion.CaseInfo;
|
||||
import jadx.core.dex.regions.SynchronizedRegion;
|
||||
import jadx.core.dex.regions.TryCatchRegion;
|
||||
import jadx.core.dex.regions.conditions.IfCondition;
|
||||
@@ -38,7 +40,6 @@ import jadx.core.dex.regions.loops.LoopRegion;
|
||||
import jadx.core.dex.regions.loops.LoopType;
|
||||
import jadx.core.dex.trycatch.ExceptionHandler;
|
||||
import jadx.core.utils.BlockUtils;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.utils.RegionUtils;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
@@ -181,18 +182,6 @@ public class RegionGen extends InsnGen {
|
||||
}
|
||||
|
||||
private CodeWriter makeLoop(LoopRegion region, CodeWriter code) throws CodegenException {
|
||||
BlockNode header = region.getHeader();
|
||||
if (header != null) {
|
||||
List<InsnNode> headerInsns = header.getInstructions();
|
||||
if (headerInsns.size() > 1) {
|
||||
ErrorsCounter.methodWarn(mth, "Found not inlined instructions from loop header");
|
||||
int last = headerInsns.size() - 1;
|
||||
for (int i = 0; i < last; i++) {
|
||||
InsnNode insn = headerInsns.get(i);
|
||||
makeInsn(insn, code);
|
||||
}
|
||||
}
|
||||
}
|
||||
LoopLabelAttr labelAttr = region.getInfo().getStart().get(AType.LOOP_LABEL);
|
||||
if (labelAttr != null) {
|
||||
code.startLine(mgen.getNameGen().getLoopLabel(labelAttr)).add(':');
|
||||
@@ -262,7 +251,7 @@ public class RegionGen extends InsnGen {
|
||||
}
|
||||
|
||||
private CodeWriter makeSwitch(SwitchRegion sw, CodeWriter code) throws CodegenException {
|
||||
SwitchNode insn = (SwitchNode) BlockUtils.getLastInsn(sw.getHeader());
|
||||
SwitchInsn insn = (SwitchInsn) BlockUtils.getLastInsn(sw.getHeader());
|
||||
Objects.requireNonNull(insn, "Switch insn not found in header");
|
||||
InsnArg arg = insn.getArg(0);
|
||||
code.startLine("switch (");
|
||||
@@ -270,42 +259,48 @@ public class RegionGen extends InsnGen {
|
||||
code.add(") {");
|
||||
code.incIndent();
|
||||
|
||||
int size = sw.getKeys().size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
List<Object> keys = sw.getKeys().get(i);
|
||||
IContainer c = sw.getCases().get(i);
|
||||
for (CaseInfo caseInfo : sw.getCases()) {
|
||||
List<Object> keys = caseInfo.getKeys();
|
||||
IContainer c = caseInfo.getContainer();
|
||||
for (Object k : keys) {
|
||||
code.startLine("case ");
|
||||
if (k instanceof FieldNode) {
|
||||
FieldNode fn = (FieldNode) k;
|
||||
if (fn.getParentClass().isEnum()) {
|
||||
code.add(fn.getAlias());
|
||||
} else {
|
||||
staticField(code, fn.getFieldInfo());
|
||||
// print original value, sometimes replace with incorrect field
|
||||
FieldInitAttr valueAttr = fn.get(AType.FIELD_INIT);
|
||||
if (valueAttr != null && valueAttr.getValue() != null) {
|
||||
code.add(" /*").add(valueAttr.getValue().toString()).add("*/");
|
||||
}
|
||||
}
|
||||
} else if (k instanceof Integer) {
|
||||
code.add(TypeGen.literalToString((Integer) k, arg.getType(), mth, fallback));
|
||||
if (k == SwitchRegion.DEFAULT_CASE_KEY) {
|
||||
code.startLine("default:");
|
||||
} else {
|
||||
throw new JadxRuntimeException("Unexpected key in switch: " + (k != null ? k.getClass() : null));
|
||||
code.startLine("case ");
|
||||
addCaseKey(code, arg, k);
|
||||
code.add(':');
|
||||
}
|
||||
code.add(':');
|
||||
}
|
||||
makeRegionIndent(code, c);
|
||||
}
|
||||
if (sw.getDefaultCase() != null) {
|
||||
code.startLine("default:");
|
||||
makeRegionIndent(code, sw.getDefaultCase());
|
||||
}
|
||||
code.decIndent();
|
||||
code.startLine('}');
|
||||
return code;
|
||||
}
|
||||
|
||||
private void addCaseKey(CodeWriter code, InsnArg arg, Object k) {
|
||||
if (k instanceof FieldNode) {
|
||||
FieldNode fn = (FieldNode) k;
|
||||
if (fn.getParentClass().isEnum()) {
|
||||
code.add(fn.getAlias());
|
||||
} else {
|
||||
staticField(code, fn.getFieldInfo());
|
||||
// print original value, sometimes replaced with incorrect field
|
||||
FieldInitAttr valueAttr = fn.get(AType.FIELD_INIT);
|
||||
if (valueAttr != null && valueAttr.getValueType() == FieldInitAttr.InitType.CONST) {
|
||||
Object value = valueAttr.getEncodedValue();
|
||||
if (value != null) {
|
||||
code.add(" /*").add(value.toString()).add("*/");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (k instanceof Integer) {
|
||||
code.add(TypeGen.literalToString((Integer) k, arg.getType(), mth, fallback));
|
||||
} else {
|
||||
throw new JadxRuntimeException("Unexpected key in switch: " + (k != null ? k.getClass() : null));
|
||||
}
|
||||
}
|
||||
|
||||
private void makeTryCatch(TryCatchRegion region, CodeWriter code) throws CodegenException {
|
||||
code.startLine("try {");
|
||||
makeRegionIndent(code, region.getTryRegion());
|
||||
@@ -340,7 +335,7 @@ public class RegionGen extends InsnGen {
|
||||
}
|
||||
code.startLine("} catch (");
|
||||
if (handler.isCatchAll()) {
|
||||
code.add("Throwable");
|
||||
useClass(code, ArgType.THROWABLE);
|
||||
} else {
|
||||
Iterator<ClassInfo> it = handler.getCatchTypes().iterator();
|
||||
if (it.hasNext()) {
|
||||
@@ -353,11 +348,15 @@ public class RegionGen extends InsnGen {
|
||||
}
|
||||
code.add(' ');
|
||||
InsnArg arg = handler.getArg();
|
||||
if (arg instanceof RegisterArg) {
|
||||
if (arg == null) {
|
||||
code.add("unknown"); // throwing exception is too late at this point
|
||||
} else if (arg instanceof RegisterArg) {
|
||||
RegisterArg reg = (RegisterArg) arg;
|
||||
code.add(mgen.getNameGen().assignArg(reg.getSVar().getCodeVar()));
|
||||
} else if (arg instanceof NamedArg) {
|
||||
code.add(mgen.getNameGen().assignNamedArg((NamedArg) arg));
|
||||
} else {
|
||||
throw new JadxRuntimeException("Unexpected arg type in catch block: " + arg + ", class: " + arg.getClass().getSimpleName());
|
||||
}
|
||||
code.add(") {");
|
||||
makeRegionIndent(code, region);
|
||||
|
||||
@@ -3,7 +3,6 @@ package jadx.core.codegen;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.deobf.NameMapper;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.LiteralArg;
|
||||
@@ -71,11 +70,7 @@ public class TypeGen {
|
||||
case BOOLEAN:
|
||||
return lit == 0 ? "false" : "true";
|
||||
case CHAR:
|
||||
char ch = (char) lit;
|
||||
if (!NameMapper.isPrintableChar(ch)) {
|
||||
return Integer.toString(ch);
|
||||
}
|
||||
return stringUtils.unescapeChar(ch);
|
||||
return stringUtils.unescapeChar((char) lit, cast);
|
||||
case BYTE:
|
||||
return formatByte(lit, cast);
|
||||
case SHORT:
|
||||
|
||||
@@ -13,6 +13,7 @@ import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import jadx.api.CodePosition;
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.core.codegen.ClassGen;
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
@@ -67,7 +68,7 @@ public class JsonCodeGen {
|
||||
|
||||
JsonClass jsonCls = new JsonClass();
|
||||
jsonCls.setPkg(classInfo.getAliasPkg());
|
||||
jsonCls.setDex(cls.dex().getDexFile().getName());
|
||||
jsonCls.setDex(cls.getInputFileName());
|
||||
jsonCls.setName(classInfo.getFullName());
|
||||
if (classInfo.hasAlias()) {
|
||||
jsonCls.setAlias(classInfo.getAliasFullName());
|
||||
@@ -85,7 +86,7 @@ public class JsonCodeGen {
|
||||
CodeGenUtils.addComments(cw, cls);
|
||||
classGen.insertDecompilationProblems(cw, cls);
|
||||
classGen.addClassDeclaration(cw);
|
||||
jsonCls.setDeclaration(cw.finish().toString());
|
||||
jsonCls.setDeclaration(cw.getCodeStr());
|
||||
|
||||
addFields(cls, jsonCls, classGen);
|
||||
addMethods(cls, jsonCls, classGen);
|
||||
@@ -128,7 +129,7 @@ public class JsonCodeGen {
|
||||
|
||||
CodeWriter cw = new CodeWriter();
|
||||
classGen.addField(cw, field);
|
||||
jsonField.setDeclaration(cw.finish().toString());
|
||||
jsonField.setDeclaration(cw.getCodeStr());
|
||||
jsonField.setAccessFlags(field.getAccessFlags().rawValue());
|
||||
|
||||
jsonCls.getFields().add(jsonField);
|
||||
@@ -153,7 +154,7 @@ public class JsonCodeGen {
|
||||
MethodGen mthGen = new MethodGen(classGen, mth);
|
||||
CodeWriter cw = new CodeWriter();
|
||||
mthGen.addDefinition(cw);
|
||||
jsonMth.setDeclaration(cw.finish().toString());
|
||||
jsonMth.setDeclaration(cw.getCodeStr());
|
||||
jsonMth.setAccessFlags(mth.getAccessFlags().rawValue());
|
||||
jsonMth.setLines(fillMthCode(mth, mthGen));
|
||||
jsonMth.setOffset("0x" + Long.toHexString(mth.getMethodCodeOffset()));
|
||||
@@ -166,14 +167,14 @@ public class JsonCodeGen {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
CodeWriter code = new CodeWriter();
|
||||
CodeWriter cw = new CodeWriter();
|
||||
try {
|
||||
mthGen.addInstructions(code);
|
||||
mthGen.addInstructions(cw);
|
||||
} catch (Exception e) {
|
||||
throw new JadxRuntimeException("Method generation error", e);
|
||||
}
|
||||
code.finish();
|
||||
String codeStr = code.toString();
|
||||
ICodeInfo code = cw.finish();
|
||||
String codeStr = code.getCodeStr();
|
||||
if (codeStr.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -26,9 +25,10 @@ import jadx.core.dex.info.FieldInfo;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.kotlin.KotlinMetadataUtils;
|
||||
|
||||
public class Deobfuscator {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Deobfuscator.class);
|
||||
@@ -39,8 +39,7 @@ public class Deobfuscator {
|
||||
public static final String INNER_CLASS_SEPARATOR = "$";
|
||||
|
||||
private final JadxArgs args;
|
||||
@NotNull
|
||||
private final List<DexNode> dexNodes;
|
||||
private final RootNode root;
|
||||
private final DeobfPresets deobfPresets;
|
||||
|
||||
private final Map<ClassInfo, DeobfClsInfo> clsMap = new LinkedHashMap<>();
|
||||
@@ -57,19 +56,21 @@ public class Deobfuscator {
|
||||
private final int maxLength;
|
||||
private final int minLength;
|
||||
private final boolean useSourceNameAsAlias;
|
||||
private final boolean parseKotlinMetadata;
|
||||
|
||||
private int pkgIndex = 0;
|
||||
private int clsIndex = 0;
|
||||
private int fldIndex = 0;
|
||||
private int mthIndex = 0;
|
||||
|
||||
public Deobfuscator(JadxArgs args, @NotNull List<DexNode> dexNodes, Path deobfMapFile) {
|
||||
public Deobfuscator(JadxArgs args, RootNode root, Path deobfMapFile) {
|
||||
this.args = args;
|
||||
this.dexNodes = dexNodes;
|
||||
this.root = root;
|
||||
|
||||
this.minLength = args.getDeobfuscationMinLength();
|
||||
this.maxLength = args.getDeobfuscationMaxLength();
|
||||
this.useSourceNameAsAlias = args.isUseSourceNameAsClassAlias();
|
||||
this.parseKotlinMetadata = args.isParseKotlinMetadata();
|
||||
|
||||
this.deobfPresets = new DeobfPresets(this, deobfMapFile);
|
||||
}
|
||||
@@ -104,15 +105,11 @@ public class Deobfuscator {
|
||||
}
|
||||
|
||||
private void preProcess() {
|
||||
for (DexNode dexNode : dexNodes) {
|
||||
for (ClassNode cls : dexNode.getClasses()) {
|
||||
Collections.addAll(reservedClsNames, cls.getPackage().split("\\."));
|
||||
}
|
||||
for (ClassNode cls : root.getClasses()) {
|
||||
Collections.addAll(reservedClsNames, cls.getPackage().split("\\."));
|
||||
}
|
||||
for (DexNode dexNode : dexNodes) {
|
||||
for (ClassNode cls : dexNode.getClasses()) {
|
||||
preProcessClass(cls);
|
||||
}
|
||||
for (ClassNode cls : root.getClasses()) {
|
||||
preProcessClass(cls);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,10 +118,8 @@ public class Deobfuscator {
|
||||
if (DEBUG) {
|
||||
dumpAlias();
|
||||
}
|
||||
for (DexNode dexNode : dexNodes) {
|
||||
for (ClassNode cls : dexNode.getClasses()) {
|
||||
processClass(cls);
|
||||
}
|
||||
for (ClassNode cls : root.getClasses()) {
|
||||
processClass(cls);
|
||||
}
|
||||
postProcess();
|
||||
}
|
||||
@@ -207,14 +202,14 @@ public class Deobfuscator {
|
||||
if (added) {
|
||||
ArgType superClass = cls.getSuperClass();
|
||||
if (superClass != null) {
|
||||
ClassNode superNode = cls.dex().resolveClass(superClass);
|
||||
ClassNode superNode = cls.root().resolveClass(superClass);
|
||||
if (superNode != null) {
|
||||
collectClassHierarchy(superNode, collected);
|
||||
}
|
||||
}
|
||||
|
||||
for (ArgType argType : cls.getInterfaces()) {
|
||||
ClassNode interfaceNode = cls.dex().resolveClass(argType);
|
||||
ClassNode interfaceNode = cls.root().resolveClass(argType);
|
||||
if (interfaceNode != null) {
|
||||
collectClassHierarchy(interfaceNode, collected);
|
||||
}
|
||||
@@ -354,9 +349,8 @@ public class Deobfuscator {
|
||||
} else {
|
||||
if (!clsMap.containsKey(classInfo)) {
|
||||
String clsShortName = classInfo.getShortName();
|
||||
if (shouldRename(clsShortName) || reservedClsNames.contains(clsShortName)) {
|
||||
makeClsAlias(cls);
|
||||
}
|
||||
boolean badName = shouldRename(clsShortName) || reservedClsNames.contains(clsShortName);
|
||||
makeClsAlias(cls, badName);
|
||||
}
|
||||
}
|
||||
for (ClassNode innerCls : cls.getInnerClasses()) {
|
||||
@@ -369,12 +363,12 @@ public class Deobfuscator {
|
||||
if (deobfClsInfo != null) {
|
||||
return deobfClsInfo.getAlias();
|
||||
}
|
||||
return makeClsAlias(cls);
|
||||
return makeClsAlias(cls, true);
|
||||
}
|
||||
|
||||
public String getPkgAlias(ClassNode cls) {
|
||||
ClassInfo classInfo = cls.getClassInfo();
|
||||
PackageNode pkg = null;
|
||||
PackageNode pkg;
|
||||
DeobfClsInfo deobfClsInfo = clsMap.get(classInfo);
|
||||
if (deobfClsInfo != null) {
|
||||
pkg = deobfClsInfo.getPkg();
|
||||
@@ -390,23 +384,94 @@ public class Deobfuscator {
|
||||
}
|
||||
}
|
||||
|
||||
private String makeClsAlias(ClassNode cls) {
|
||||
ClassInfo classInfo = cls.getClassInfo();
|
||||
private String makeClsAlias(ClassNode cls, boolean badName) {
|
||||
String alias = null;
|
||||
|
||||
if (this.useSourceNameAsAlias) {
|
||||
String pkgName = null;
|
||||
if (this.parseKotlinMetadata) {
|
||||
ClassInfo kotlinCls = KotlinMetadataUtils.getClassName(cls);
|
||||
if (kotlinCls != null) {
|
||||
alias = prepareNameFull(kotlinCls.getShortName(), "C");
|
||||
pkgName = kotlinCls.getPackage();
|
||||
}
|
||||
}
|
||||
if (alias == null && this.useSourceNameAsAlias) {
|
||||
alias = getAliasFromSourceFile(cls);
|
||||
}
|
||||
|
||||
ClassInfo classInfo = cls.getClassInfo();
|
||||
if (alias == null) {
|
||||
String clsName = classInfo.getShortName();
|
||||
alias = String.format("C%04d%s", clsIndex++, prepareNamePart(clsName));
|
||||
if (badName) {
|
||||
String clsName = classInfo.getShortName();
|
||||
String prefix = makeClsPrefix(cls);
|
||||
alias = String.format("%sC%04d%s", prefix, clsIndex++, prepareNamePart(clsName));
|
||||
} else {
|
||||
// rename not needed
|
||||
return classInfo.getShortName();
|
||||
}
|
||||
}
|
||||
PackageNode pkg = getPackageNode(classInfo.getPackage(), true);
|
||||
if (pkgName == null) {
|
||||
pkgName = classInfo.getPackage();
|
||||
}
|
||||
PackageNode pkg = getPackageNode(pkgName, true);
|
||||
clsMap.put(classInfo, new DeobfClsInfo(this, cls, pkg, alias));
|
||||
return alias;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a prefix for a class name that bases on certain class properties, certain
|
||||
* extended superclasses or implemented interfaces.
|
||||
*/
|
||||
private String makeClsPrefix(ClassNode cls) {
|
||||
if (cls.isEnum()) {
|
||||
return "Enum";
|
||||
}
|
||||
String result = "";
|
||||
if (cls.getAccessFlags().isAbstract()) {
|
||||
result += "Abstract";
|
||||
}
|
||||
|
||||
// Process current class and all super classes
|
||||
ClassNode currentCls = cls;
|
||||
outerLoop: while (currentCls != null) {
|
||||
if (currentCls.getSuperClass() != null) {
|
||||
String superClsName = currentCls.getSuperClass().getObject();
|
||||
if (superClsName.startsWith("android.app.")) {
|
||||
// e.g. Activity or Fragment
|
||||
result += superClsName.substring(12);
|
||||
break;
|
||||
} else if (superClsName.startsWith("android.os.")) {
|
||||
// e.g. AsyncTask
|
||||
result += superClsName.substring(11);
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (ArgType intf : cls.getInterfaces()) {
|
||||
String intfClsName = intf.getObject();
|
||||
if (intfClsName.equals("java.lang.Runnable")) {
|
||||
result += "Runnable";
|
||||
break outerLoop;
|
||||
} else if (intfClsName.startsWith("java.util.concurrent.")) {
|
||||
// e.g. Callable
|
||||
result += intfClsName.substring(21);
|
||||
break outerLoop;
|
||||
} else if (intfClsName.startsWith("android.view.")) {
|
||||
// e.g. View.OnClickListener
|
||||
result += intfClsName.substring(13);
|
||||
break outerLoop;
|
||||
} else if (intfClsName.startsWith("android.content.")) {
|
||||
// e.g. DialogInterface.OnClickListener
|
||||
result += intfClsName.substring(16);
|
||||
break outerLoop;
|
||||
}
|
||||
}
|
||||
if (currentCls.getSuperClass() == null) {
|
||||
break;
|
||||
}
|
||||
currentCls = cls.root().resolveClass(currentCls.getSuperClass());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String getAliasFromSourceFile(ClassNode cls) {
|
||||
SourceFileAttr sourceFileAttr = cls.get(AType.SOURCE_FILE);
|
||||
@@ -430,7 +495,7 @@ public class Deobfuscator {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
ClassNode otherCls = cls.root().searchClassByName(cls.getPackage() + '.' + name);
|
||||
ClassNode otherCls = cls.root().resolveClass(cls.getPackage() + '.' + name);
|
||||
if (otherCls != null) {
|
||||
return null;
|
||||
}
|
||||
@@ -528,6 +593,24 @@ public class Deobfuscator {
|
||||
return NameMapper.removeInvalidCharsMiddle(name);
|
||||
}
|
||||
|
||||
private String prepareNameFull(String name, String prefix) {
|
||||
if (name.length() > maxLength) {
|
||||
return makeHashName(name, prefix);
|
||||
}
|
||||
String result = NameMapper.removeInvalidChars(name, prefix);
|
||||
if (result.isEmpty()) {
|
||||
return makeHashName(name, prefix);
|
||||
}
|
||||
if (NameMapper.isReserved(result)) {
|
||||
return prefix + result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static String makeHashName(String name, String invalidPrefix) {
|
||||
return invalidPrefix + 'x' + Integer.toHexString(name.hashCode());
|
||||
}
|
||||
|
||||
private void dumpClassAlias(ClassNode cls) {
|
||||
PackageNode pkg = getPackageNode(cls.getPackage(), false);
|
||||
|
||||
@@ -541,10 +624,8 @@ public class Deobfuscator {
|
||||
}
|
||||
|
||||
private void dumpAlias() {
|
||||
for (DexNode dexNode : dexNodes) {
|
||||
for (ClassNode cls : dexNode.getClasses()) {
|
||||
dumpClassAlias(cls);
|
||||
}
|
||||
for (ClassNode cls : root.getClasses()) {
|
||||
dumpClassAlias(cls);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import jadx.core.dex.info.MethodInfo;
|
||||
|
||||
class OverridedMethodsNode {
|
||||
|
||||
private Set<MethodInfo> methods;
|
||||
private final Set<MethodInfo> methods;
|
||||
|
||||
public OverridedMethodsNode(Set<MethodInfo> methodsSet) {
|
||||
methods = methodsSet;
|
||||
|
||||
@@ -31,14 +31,17 @@ public class PackageNode {
|
||||
public String getFullName() {
|
||||
if (cachedPackageFullName == null) {
|
||||
Deque<PackageNode> pp = getParentPackages();
|
||||
|
||||
StringBuilder result = new StringBuilder();
|
||||
result.append(pp.pop().getName());
|
||||
while (!pp.isEmpty()) {
|
||||
result.append(SEPARATOR_CHAR);
|
||||
if (pp.isEmpty()) {
|
||||
cachedPackageFullName = "";
|
||||
} else {
|
||||
StringBuilder result = new StringBuilder();
|
||||
result.append(pp.pop().getName());
|
||||
while (!pp.isEmpty()) {
|
||||
result.append(SEPARATOR_CHAR);
|
||||
result.append(pp.pop().getName());
|
||||
}
|
||||
cachedPackageFullName = result.toString();
|
||||
}
|
||||
cachedPackageFullName = result.toString();
|
||||
}
|
||||
return cachedPackageFullName;
|
||||
}
|
||||
|
||||
@@ -15,13 +15,13 @@ public enum AFlag {
|
||||
|
||||
DONT_WRAP,
|
||||
DONT_INLINE,
|
||||
DONT_INLINE_CONST,
|
||||
DONT_GENERATE, // process as usual, but don't output to generated code
|
||||
COMMENT_OUT, // process as usual, but comment insn in generated code
|
||||
REMOVE, // can be completely removed
|
||||
|
||||
HIDDEN, // instruction used inside other instruction but not listed in args
|
||||
|
||||
RESTART_CODEGEN,
|
||||
DONT_RENAME, // do not rename during deobfuscation
|
||||
ADDED_TO_REGION,
|
||||
|
||||
@@ -33,6 +33,7 @@ public enum AFlag {
|
||||
ANONYMOUS_CLASS,
|
||||
|
||||
THIS,
|
||||
SUPER,
|
||||
|
||||
/**
|
||||
* RegisterArg attribute for method arguments
|
||||
@@ -59,13 +60,23 @@ public enum AFlag {
|
||||
|
||||
FALL_THROUGH,
|
||||
|
||||
EXPLICIT_GENERICS,
|
||||
VARARG_CALL,
|
||||
|
||||
/**
|
||||
* Use constants with explicit type: cast '(byte) 1' or type letter '7L'
|
||||
*/
|
||||
EXPLICIT_PRIMITIVE_TYPE,
|
||||
EXPLICIT_CAST,
|
||||
SOFT_CAST, // synthetic cast to help type inference (allow unchecked casts for generics)
|
||||
|
||||
INCONSISTENT_CODE, // warning about incorrect decompilation
|
||||
|
||||
REQUEST_IF_REGION_OPTIMIZE, // run if region visitor again
|
||||
|
||||
// Class processing flags
|
||||
RESTART_CODEGEN, // codegen must be executed again
|
||||
RELOAD_AT_CODEGEN_STAGE, // class can't be analyzed at 'process' stage => unload before 'codegen' stage
|
||||
CLASS_DEEP_RELOAD, // perform deep class unload (reload) before process
|
||||
|
||||
DONT_UNLOAD_CLASS, // don't unload class after code generation (only for tests and debug!)
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import jadx.core.dex.attributes.nodes.EnumClassAttr;
|
||||
import jadx.core.dex.attributes.nodes.EnumMapAttr;
|
||||
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
|
||||
import jadx.core.dex.attributes.nodes.ForceReturnAttr;
|
||||
import jadx.core.dex.attributes.nodes.GenericInfoAttr;
|
||||
import jadx.core.dex.attributes.nodes.IgnoreEdgeAttr;
|
||||
import jadx.core.dex.attributes.nodes.JadxError;
|
||||
import jadx.core.dex.attributes.nodes.JumpInfo;
|
||||
@@ -19,12 +20,14 @@ import jadx.core.dex.attributes.nodes.LocalVarsDebugInfoAttr;
|
||||
import jadx.core.dex.attributes.nodes.LoopInfo;
|
||||
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
||||
import jadx.core.dex.attributes.nodes.MethodInlineAttr;
|
||||
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
|
||||
import jadx.core.dex.attributes.nodes.MethodTypeVarsAttr;
|
||||
import jadx.core.dex.attributes.nodes.PhiListAttr;
|
||||
import jadx.core.dex.attributes.nodes.RegDebugInfoAttr;
|
||||
import jadx.core.dex.attributes.nodes.RenameReasonAttr;
|
||||
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
|
||||
import jadx.core.dex.attributes.nodes.SourceFileAttr;
|
||||
import jadx.core.dex.nodes.parser.FieldInitAttr;
|
||||
import jadx.core.dex.nodes.IMethodDetails;
|
||||
import jadx.core.dex.trycatch.CatchAttr;
|
||||
import jadx.core.dex.trycatch.ExcHandlerAttr;
|
||||
import jadx.core.dex.trycatch.SplitterBlockAttr;
|
||||
@@ -35,6 +38,7 @@ import jadx.core.dex.trycatch.SplitterBlockAttr;
|
||||
*
|
||||
* @param <T> attribute class implementation
|
||||
*/
|
||||
@SuppressWarnings("InstantiationOfUtilityClass")
|
||||
public class AType<T extends IAttribute> {
|
||||
|
||||
// class, method, field
|
||||
@@ -60,6 +64,8 @@ public class AType<T extends IAttribute> {
|
||||
public static final AType<MethodInlineAttr> METHOD_INLINE = new AType<>();
|
||||
public static final AType<MethodParameters> ANNOTATION_MTH_PARAMETERS = new AType<>();
|
||||
public static final AType<SkipMethodArgsAttr> SKIP_MTH_ARGS = new AType<>();
|
||||
public static final AType<MethodOverrideAttr> METHOD_OVERRIDE = new AType<>();
|
||||
public static final AType<MethodTypeVarsAttr> METHOD_TYPE_VARS = new AType<>();
|
||||
|
||||
// region
|
||||
public static final AType<DeclareVariablesAttr> DECLARE_VARIABLES = new AType<>();
|
||||
@@ -79,6 +85,8 @@ public class AType<T extends IAttribute> {
|
||||
// instruction
|
||||
public static final AType<LoopLabelAttr> LOOP_LABEL = new AType<>();
|
||||
public static final AType<AttrList<JumpInfo>> JUMP = new AType<>();
|
||||
public static final AType<IMethodDetails> METHOD_DETAILS = new AType<>();
|
||||
public static final AType<GenericInfoAttr> GENERIC_INFO = new AType<>();
|
||||
|
||||
// register
|
||||
public static final AType<RegDebugInfoAttr> REG_DEBUG_INFO = new AType<>();
|
||||
|
||||
@@ -3,6 +3,7 @@ package jadx.core.dex.attributes;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
public class AttrList<T> implements IAttribute {
|
||||
@@ -25,6 +26,6 @@ public class AttrList<T> implements IAttribute {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Utils.listToString(list, "\n");
|
||||
return Utils.listToString(list, CodeWriter.NL);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package jadx.core.dex.attributes;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.dex.attributes.annotations.Annotation;
|
||||
import jadx.api.plugins.input.data.annotations.IAnnotation;
|
||||
|
||||
public abstract class AttrNode implements IAttributeNode {
|
||||
|
||||
@@ -64,7 +64,7 @@ public abstract class AttrNode implements IAttributeNode {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Annotation getAnnotation(String cls) {
|
||||
public IAnnotation getAnnotation(String cls) {
|
||||
return storage.getAnnotation(cls);
|
||||
}
|
||||
|
||||
@@ -118,6 +118,7 @@ public abstract class AttrNode implements IAttributeNode {
|
||||
return storage.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAttrStorageEmpty() {
|
||||
return storage.isEmpty();
|
||||
}
|
||||
|
||||
@@ -8,9 +8,10 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import jadx.core.dex.attributes.annotations.Annotation;
|
||||
import jadx.api.plugins.input.data.annotations.IAnnotation;
|
||||
import jadx.core.dex.attributes.annotations.AnnotationsList;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
/**
|
||||
* Storage for different attribute types:
|
||||
@@ -19,6 +20,13 @@ import jadx.core.utils.Utils;
|
||||
*/
|
||||
public class AttributeStorage {
|
||||
|
||||
static {
|
||||
int flagsCount = AFlag.values().length;
|
||||
if (flagsCount >= 64) {
|
||||
throw new JadxRuntimeException("Try to reduce flags count to 64 for use one long in EnumSet, now " + flagsCount);
|
||||
}
|
||||
}
|
||||
|
||||
private final Set<AFlag> flags;
|
||||
private Map<AType<?>, IAttribute> attributes;
|
||||
|
||||
@@ -62,7 +70,7 @@ public class AttributeStorage {
|
||||
return (T) attributes.get(type);
|
||||
}
|
||||
|
||||
public Annotation getAnnotation(String cls) {
|
||||
public IAnnotation getAnnotation(String cls) {
|
||||
AnnotationsList aList = get(AType.ANNOTATION_LIST);
|
||||
return aList == null ? null : aList.get(cls);
|
||||
}
|
||||
@@ -127,7 +135,7 @@ public class AttributeStorage {
|
||||
list.add(a.toString());
|
||||
}
|
||||
for (IAttribute a : attributes.values()) {
|
||||
list.add(a.toString());
|
||||
list.add(a.toAttrString());
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package jadx.core.dex.attributes;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.dex.attributes.annotations.Annotation;
|
||||
import jadx.api.plugins.input.data.annotations.IAnnotation;
|
||||
|
||||
public final class EmptyAttrStorage extends AttributeStorage {
|
||||
|
||||
@@ -23,7 +23,7 @@ public final class EmptyAttrStorage extends AttributeStorage {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Annotation getAnnotation(String cls) {
|
||||
public IAnnotation getAnnotation(String cls) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
+6
-8
@@ -1,18 +1,16 @@
|
||||
package jadx.core.dex.nodes.parser;
|
||||
package jadx.core.dex.attributes;
|
||||
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.IAttribute;
|
||||
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
|
||||
public class FieldInitAttr implements IAttribute {
|
||||
|
||||
public static final FieldInitAttr NULL_VALUE = constValue(null);
|
||||
public static final FieldInitAttr NULL_VALUE = constValue(EncodedValue.NULL);
|
||||
|
||||
public enum InitType {
|
||||
CONST,
|
||||
INSN
|
||||
|
||||
}
|
||||
|
||||
private final Object value;
|
||||
@@ -25,7 +23,7 @@ public class FieldInitAttr implements IAttribute {
|
||||
this.insnMth = insnMth;
|
||||
}
|
||||
|
||||
public static FieldInitAttr constValue(Object value) {
|
||||
public static FieldInitAttr constValue(EncodedValue value) {
|
||||
return new FieldInitAttr(InitType.CONST, value, null);
|
||||
}
|
||||
|
||||
@@ -33,8 +31,8 @@ public class FieldInitAttr implements IAttribute {
|
||||
return new FieldInitAttr(InitType.INSN, insn, mth);
|
||||
}
|
||||
|
||||
public Object getValue() {
|
||||
return value;
|
||||
public EncodedValue getEncodedValue() {
|
||||
return (EncodedValue) value;
|
||||
}
|
||||
|
||||
public InsnNode getInsn() {
|
||||
@@ -2,4 +2,8 @@ package jadx.core.dex.attributes;
|
||||
|
||||
public interface IAttribute {
|
||||
AType<? extends IAttribute> getType();
|
||||
|
||||
default String toAttrString() {
|
||||
return this.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package jadx.core.dex.attributes;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.dex.attributes.annotations.Annotation;
|
||||
import jadx.api.plugins.input.data.annotations.IAnnotation;
|
||||
|
||||
public interface IAttributeNode {
|
||||
|
||||
@@ -20,7 +20,7 @@ public interface IAttributeNode {
|
||||
|
||||
<T extends IAttribute> T get(AType<T> type);
|
||||
|
||||
Annotation getAnnotation(String cls);
|
||||
IAnnotation getAnnotation(String cls);
|
||||
|
||||
<T> List<T> getAll(AType<AttrList<T>> type);
|
||||
|
||||
@@ -35,4 +35,6 @@ public interface IAttributeNode {
|
||||
List<String> getAttributesStringsList();
|
||||
|
||||
String getAttributesString();
|
||||
|
||||
boolean isAttrStorageEmpty();
|
||||
}
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
package jadx.core.dex.attributes.annotations;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
|
||||
public class Annotation {
|
||||
|
||||
public enum Visibility {
|
||||
BUILD, RUNTIME, SYSTEM
|
||||
}
|
||||
|
||||
private final Visibility visibility;
|
||||
private final ArgType atype;
|
||||
private final Map<String, Object> values;
|
||||
|
||||
public Annotation(Visibility visibility, ArgType type, Map<String, Object> values) {
|
||||
this.visibility = visibility;
|
||||
this.atype = type;
|
||||
this.values = values;
|
||||
}
|
||||
|
||||
public Visibility getVisibility() {
|
||||
return visibility;
|
||||
}
|
||||
|
||||
public ArgType getType() {
|
||||
return atype;
|
||||
}
|
||||
|
||||
public String getAnnotationClass() {
|
||||
return atype.getObject();
|
||||
}
|
||||
|
||||
public Map<String, Object> getValues() {
|
||||
return values;
|
||||
}
|
||||
|
||||
public Object getDefaultValue() {
|
||||
return values.get("value");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Annotation[" + visibility + ", " + atype + ", " + values + ']';
|
||||
}
|
||||
}
|
||||
@@ -1,33 +1,50 @@
|
||||
package jadx.core.dex.attributes.annotations;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.plugins.input.data.annotations.IAnnotation;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.IAttribute;
|
||||
import jadx.core.dex.nodes.ICodeNode;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
public class AnnotationsList implements IAttribute {
|
||||
|
||||
public static final AnnotationsList EMPTY = new AnnotationsList(Collections.emptyList());
|
||||
|
||||
private final Map<String, Annotation> map;
|
||||
|
||||
public AnnotationsList(List<Annotation> anList) {
|
||||
map = new HashMap<>(anList.size());
|
||||
for (Annotation a : anList) {
|
||||
map.put(a.getAnnotationClass(), a);
|
||||
public static void attach(ICodeNode node, List<IAnnotation> annotationList) {
|
||||
AnnotationsList attrList = pack(annotationList);
|
||||
if (attrList != null) {
|
||||
node.addAttr(attrList);
|
||||
}
|
||||
}
|
||||
|
||||
public Annotation get(String className) {
|
||||
@Nullable
|
||||
public static AnnotationsList pack(List<IAnnotation> annotationList) {
|
||||
if (annotationList.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
Map<String, IAnnotation> annMap = new HashMap<>(annotationList.size());
|
||||
for (IAnnotation ann : annotationList) {
|
||||
annMap.put(ann.getAnnotationClass(), ann);
|
||||
}
|
||||
return new AnnotationsList(annMap);
|
||||
}
|
||||
|
||||
private final Map<String, IAnnotation> map;
|
||||
|
||||
public AnnotationsList(Map<String, IAnnotation> map) {
|
||||
this.map = map;
|
||||
}
|
||||
|
||||
public IAnnotation get(String className) {
|
||||
return map.get(className);
|
||||
}
|
||||
|
||||
public Collection<Annotation> getAll() {
|
||||
public Collection<IAnnotation> getAll() {
|
||||
return map.values();
|
||||
}
|
||||
|
||||
|
||||
@@ -3,16 +3,29 @@ package jadx.core.dex.attributes.annotations;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.api.plugins.input.data.annotations.IAnnotation;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.IAttribute;
|
||||
import jadx.core.dex.nodes.ICodeNode;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
public class MethodParameters implements IAttribute {
|
||||
|
||||
public static void attach(ICodeNode node, List<List<IAnnotation>> annotationRefList) {
|
||||
if (annotationRefList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<AnnotationsList> list = new ArrayList<>(annotationRefList.size());
|
||||
for (List<IAnnotation> annList : annotationRefList) {
|
||||
list.add(AnnotationsList.pack(annList));
|
||||
}
|
||||
node.addAttr(new MethodParameters(list));
|
||||
}
|
||||
|
||||
private final List<AnnotationsList> paramList;
|
||||
|
||||
public MethodParameters(int paramCount) {
|
||||
paramList = new ArrayList<>(paramCount);
|
||||
public MethodParameters(List<AnnotationsList> paramsList) {
|
||||
this.paramList = paramsList;
|
||||
}
|
||||
|
||||
public List<AnnotationsList> getParamList() {
|
||||
|
||||
@@ -1,30 +1,27 @@
|
||||
package jadx.core.dex.attributes.nodes;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.IAttribute;
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
import jadx.core.dex.instructions.mods.ConstructorInsn;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
|
||||
public class EnumClassAttr implements IAttribute {
|
||||
|
||||
public static class EnumField {
|
||||
private final FieldInfo field;
|
||||
private final FieldNode field;
|
||||
private final ConstructorInsn constrInsn;
|
||||
private final int startArg;
|
||||
private ClassNode cls;
|
||||
|
||||
public EnumField(FieldInfo field, ConstructorInsn co, int startArg) {
|
||||
public EnumField(FieldNode field, ConstructorInsn co) {
|
||||
this.field = field;
|
||||
this.constrInsn = co;
|
||||
this.startArg = startArg;
|
||||
}
|
||||
|
||||
public FieldInfo getField() {
|
||||
public FieldNode getField() {
|
||||
return field;
|
||||
}
|
||||
|
||||
@@ -32,10 +29,6 @@ public class EnumClassAttr implements IAttribute {
|
||||
return constrInsn;
|
||||
}
|
||||
|
||||
public int getStartArg() {
|
||||
return startArg;
|
||||
}
|
||||
|
||||
public ClassNode getCls() {
|
||||
return cls;
|
||||
}
|
||||
@@ -53,8 +46,8 @@ public class EnumClassAttr implements IAttribute {
|
||||
private final List<EnumField> fields;
|
||||
private MethodNode staticMethod;
|
||||
|
||||
public EnumClassAttr(int fieldsCount) {
|
||||
this.fields = new ArrayList<>(fieldsCount);
|
||||
public EnumClassAttr(List<EnumField> fields) {
|
||||
this.fields = fields;
|
||||
}
|
||||
|
||||
public List<EnumField> getFields() {
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package jadx.core.dex.attributes.nodes;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.IAttribute;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
|
||||
public class GenericInfoAttr implements IAttribute {
|
||||
private final List<ArgType> genericTypes;
|
||||
private boolean explicit;
|
||||
|
||||
public GenericInfoAttr(List<ArgType> genericTypes) {
|
||||
this.genericTypes = genericTypes;
|
||||
}
|
||||
|
||||
public List<ArgType> getGenericTypes() {
|
||||
return genericTypes;
|
||||
}
|
||||
|
||||
public boolean isExplicit() {
|
||||
return explicit;
|
||||
}
|
||||
|
||||
public void setExplicit(boolean explicit) {
|
||||
this.explicit = explicit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AType<GenericInfoAttr> getType() {
|
||||
return AType.GENERIC_INFO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "GenericInfoAttr{" + genericTypes + ", explicit=" + explicit + '}';
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import java.util.Objects;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
public class JadxError implements Comparable<JadxError> {
|
||||
@@ -58,7 +59,7 @@ public class JadxError implements Comparable<JadxError> {
|
||||
str.append(cause.getClass());
|
||||
str.append(':');
|
||||
str.append(cause.getMessage());
|
||||
str.append('\n');
|
||||
str.append(CodeWriter.NL);
|
||||
str.append(Utils.getStackTrace(cause));
|
||||
}
|
||||
return str.toString();
|
||||
|
||||
@@ -2,19 +2,21 @@ package jadx.core.dex.attributes.nodes;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import jadx.api.plugins.input.data.ILocalVar;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.IAttribute;
|
||||
import jadx.core.dex.visitors.debuginfo.LocalVar;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
public class LocalVarsDebugInfoAttr implements IAttribute {
|
||||
private final List<LocalVar> localVars;
|
||||
import static jadx.core.codegen.CodeWriter.NL;
|
||||
|
||||
public LocalVarsDebugInfoAttr(List<LocalVar> localVars) {
|
||||
public class LocalVarsDebugInfoAttr implements IAttribute {
|
||||
private final List<ILocalVar> localVars;
|
||||
|
||||
public LocalVarsDebugInfoAttr(List<ILocalVar> localVars) {
|
||||
this.localVars = localVars;
|
||||
}
|
||||
|
||||
public List<LocalVar> getLocalVars() {
|
||||
public List<ILocalVar> getLocalVars() {
|
||||
return localVars;
|
||||
}
|
||||
|
||||
@@ -25,6 +27,6 @@ public class LocalVarsDebugInfoAttr implements IAttribute {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Debug Info:\n " + Utils.listToString(localVars, "\n ");
|
||||
return "Debug Info:" + NL + " " + Utils.listToString(localVars, NL + " ");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +72,10 @@ public class LoopInfo {
|
||||
return edges;
|
||||
}
|
||||
|
||||
public BlockNode getPreHeader() {
|
||||
return BlockUtils.selectOther(end, start.getPredecessors());
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package jadx.core.dex.attributes.nodes;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.IAttribute;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
@@ -10,7 +13,10 @@ import jadx.core.dex.nodes.MethodNode;
|
||||
|
||||
public class MethodInlineAttr implements IAttribute {
|
||||
|
||||
public static void markForInline(MethodNode mth, InsnNode replaceInsn) {
|
||||
private static final MethodInlineAttr INLINE_NOT_NEEDED = new MethodInlineAttr(null, null);
|
||||
|
||||
public static MethodInlineAttr markForInline(MethodNode mth, InsnNode replaceInsn) {
|
||||
Objects.requireNonNull(replaceInsn);
|
||||
List<RegisterArg> allArgRegs = mth.getAllArgRegs();
|
||||
int argsCount = allArgRegs.size();
|
||||
int[] regNums = new int[argsCount];
|
||||
@@ -18,7 +24,19 @@ public class MethodInlineAttr implements IAttribute {
|
||||
RegisterArg reg = allArgRegs.get(i);
|
||||
regNums[i] = reg.getRegNum();
|
||||
}
|
||||
mth.addAttr(new MethodInlineAttr(replaceInsn, regNums));
|
||||
MethodInlineAttr mia = new MethodInlineAttr(replaceInsn, regNums);
|
||||
mth.addAttr(mia);
|
||||
if (Consts.DEBUG) {
|
||||
mth.addAttr(AType.COMMENTS, "Removed for inline");
|
||||
} else {
|
||||
mth.add(AFlag.DONT_GENERATE);
|
||||
}
|
||||
return mia;
|
||||
}
|
||||
|
||||
public static MethodInlineAttr inlineNotNeeded(MethodNode mth) {
|
||||
mth.addAttr(INLINE_NOT_NEEDED);
|
||||
return INLINE_NOT_NEEDED;
|
||||
}
|
||||
|
||||
private final InsnNode insn;
|
||||
@@ -33,6 +51,10 @@ public class MethodInlineAttr implements IAttribute {
|
||||
this.argsRegNums = argsRegNums;
|
||||
}
|
||||
|
||||
public boolean notNeeded() {
|
||||
return insn == null;
|
||||
}
|
||||
|
||||
public InsnNode getInsn() {
|
||||
return insn;
|
||||
}
|
||||
@@ -48,6 +70,9 @@ public class MethodInlineAttr implements IAttribute {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (notNeeded()) {
|
||||
return "INLINE_NOT_NEEDED";
|
||||
}
|
||||
return "INLINE: " + insn;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package jadx.core.dex.attributes.nodes;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.IAttribute;
|
||||
import jadx.core.dex.nodes.IMethodDetails;
|
||||
|
||||
public class MethodOverrideAttr implements IAttribute {
|
||||
|
||||
private final List<IMethodDetails> overrideList;
|
||||
|
||||
public MethodOverrideAttr(List<IMethodDetails> overrideList) {
|
||||
this.overrideList = overrideList;
|
||||
}
|
||||
|
||||
public List<IMethodDetails> getOverrideList() {
|
||||
return overrideList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AType<MethodOverrideAttr> getType() {
|
||||
return AType.METHOD_OVERRIDE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "METHOD_OVERRIDE: " + overrideList;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package jadx.core.dex.attributes.nodes;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.IAttribute;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
|
||||
import static jadx.core.utils.Utils.isEmpty;
|
||||
|
||||
/**
|
||||
* Set of known type variables at current method
|
||||
*/
|
||||
public class MethodTypeVarsAttr implements IAttribute {
|
||||
private static final MethodTypeVarsAttr EMPTY = new MethodTypeVarsAttr(Collections.emptySet());
|
||||
|
||||
public static MethodTypeVarsAttr build(Set<ArgType> typeVars) {
|
||||
if (isEmpty(typeVars)) {
|
||||
return EMPTY;
|
||||
}
|
||||
return new MethodTypeVarsAttr(typeVars);
|
||||
}
|
||||
|
||||
private final Set<ArgType> typeVars;
|
||||
|
||||
private MethodTypeVarsAttr(Set<ArgType> typeVars) {
|
||||
this.typeVars = typeVars;
|
||||
}
|
||||
|
||||
public Set<ArgType> getTypeVars() {
|
||||
return typeVars;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AType<MethodTypeVarsAttr> getType() {
|
||||
return AType.METHOD_TYPE_VARS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (this == EMPTY) {
|
||||
return "TYPE_VARS: EMPTY";
|
||||
}
|
||||
return "TYPE_VARS: " + typeVars;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package jadx.core.dex.attributes.nodes;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.nodes.ICodeNode;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
|
||||
public abstract class NotificationAttrNode extends LineAttrNode implements ICodeNode {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(NotificationAttrNode.class);
|
||||
|
||||
public void addError(String errStr, Throwable e) {
|
||||
ErrorsCounter.error(this, errStr, e);
|
||||
}
|
||||
|
||||
public void addWarn(String warnStr) {
|
||||
ErrorsCounter.warning(this, warnStr);
|
||||
}
|
||||
|
||||
public void addWarnComment(String warn) {
|
||||
addWarnComment(warn, null);
|
||||
}
|
||||
|
||||
public void addWarnComment(String warn, @Nullable Throwable exc) {
|
||||
String commentStr = "JADX WARN: " + warn;
|
||||
addAttr(AType.COMMENTS, commentStr);
|
||||
if (exc != null) {
|
||||
LOG.warn("{} in {}", warn, this, exc);
|
||||
} else {
|
||||
LOG.warn("{} in {}", warn, this);
|
||||
}
|
||||
}
|
||||
|
||||
public void addComment(String commentStr) {
|
||||
addAttr(AType.COMMENTS, commentStr);
|
||||
LOG.info("{} in {}", commentStr, this);
|
||||
}
|
||||
|
||||
public void addDebugComment(String commentStr) {
|
||||
addAttr(AType.COMMENTS, "JADX DEBUG: " + commentStr);
|
||||
LOG.debug("{} in {}", commentStr, this);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package jadx.core.dex.attributes.nodes;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.IAttribute;
|
||||
import jadx.core.dex.instructions.PhiInsn;
|
||||
@@ -28,7 +29,7 @@ public class PhiListAttr implements IAttribute {
|
||||
sb.append('r').append(phiInsn.getResult().getRegNum()).append(' ');
|
||||
}
|
||||
for (PhiInsn phiInsn : list) {
|
||||
sb.append("\n ").append(phiInsn).append(' ').append(phiInsn.getAttributesString());
|
||||
sb.append(CodeWriter.NL).append(" ").append(phiInsn).append(' ').append(phiInsn.getAttributesString());
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@@ -5,17 +5,12 @@ import java.util.Objects;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.IAttribute;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.visitors.debuginfo.LocalVar;
|
||||
|
||||
public class RegDebugInfoAttr implements IAttribute {
|
||||
|
||||
private final ArgType type;
|
||||
private final String name;
|
||||
|
||||
public RegDebugInfoAttr(LocalVar var) {
|
||||
this(var.getType(), var.getName());
|
||||
}
|
||||
|
||||
public RegDebugInfoAttr(ArgType type, String name) {
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
|
||||
@@ -44,7 +44,7 @@ public class SkipMethodArgsAttr implements IAttribute {
|
||||
private final BitSet skipArgs;
|
||||
|
||||
private SkipMethodArgsAttr(MethodNode mth) {
|
||||
this.skipArgs = new BitSet(mth.getArgRegs().size());
|
||||
this.skipArgs = new BitSet(mth.getMethodInfo().getArgsCount());
|
||||
}
|
||||
|
||||
public void skip(int argNum) {
|
||||
@@ -55,6 +55,10 @@ public class SkipMethodArgsAttr implements IAttribute {
|
||||
return skipArgs.get(argNum);
|
||||
}
|
||||
|
||||
public int getSkipCount() {
|
||||
return skipArgs.cardinality();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AType<SkipMethodArgsAttr> getType() {
|
||||
return AType.SKIP_MTH_ARGS;
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
package jadx.core.dex.info;
|
||||
|
||||
import com.android.dx.rop.code.AccessFlags;
|
||||
|
||||
import jadx.api.plugins.input.data.AccessFlags;
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public class AccessInfo {
|
||||
|
||||
public static final int VISIBILITY_FLAGS = AccessFlags.ACC_PUBLIC | AccessFlags.ACC_PROTECTED | AccessFlags.ACC_PRIVATE;
|
||||
public static final int VISIBILITY_FLAGS = AccessFlags.PUBLIC | AccessFlags.PROTECTED | AccessFlags.PRIVATE;
|
||||
private final int accFlags;
|
||||
|
||||
public enum AFType {
|
||||
@@ -53,15 +52,15 @@ public class AccessInfo {
|
||||
}
|
||||
|
||||
public boolean isPublic() {
|
||||
return (accFlags & AccessFlags.ACC_PUBLIC) != 0;
|
||||
return (accFlags & AccessFlags.PUBLIC) != 0;
|
||||
}
|
||||
|
||||
public boolean isProtected() {
|
||||
return (accFlags & AccessFlags.ACC_PROTECTED) != 0;
|
||||
return (accFlags & AccessFlags.PROTECTED) != 0;
|
||||
}
|
||||
|
||||
public boolean isPrivate() {
|
||||
return (accFlags & AccessFlags.ACC_PRIVATE) != 0;
|
||||
return (accFlags & AccessFlags.PRIVATE) != 0;
|
||||
}
|
||||
|
||||
public boolean isPackagePrivate() {
|
||||
@@ -69,59 +68,59 @@ public class AccessInfo {
|
||||
}
|
||||
|
||||
public boolean isAbstract() {
|
||||
return (accFlags & AccessFlags.ACC_ABSTRACT) != 0;
|
||||
return (accFlags & AccessFlags.ABSTRACT) != 0;
|
||||
}
|
||||
|
||||
public boolean isInterface() {
|
||||
return (accFlags & AccessFlags.ACC_INTERFACE) != 0;
|
||||
return (accFlags & AccessFlags.INTERFACE) != 0;
|
||||
}
|
||||
|
||||
public boolean isAnnotation() {
|
||||
return (accFlags & AccessFlags.ACC_ANNOTATION) != 0;
|
||||
return (accFlags & AccessFlags.ANNOTATION) != 0;
|
||||
}
|
||||
|
||||
public boolean isNative() {
|
||||
return (accFlags & AccessFlags.ACC_NATIVE) != 0;
|
||||
return (accFlags & AccessFlags.NATIVE) != 0;
|
||||
}
|
||||
|
||||
public boolean isStatic() {
|
||||
return (accFlags & AccessFlags.ACC_STATIC) != 0;
|
||||
return (accFlags & AccessFlags.STATIC) != 0;
|
||||
}
|
||||
|
||||
public boolean isFinal() {
|
||||
return (accFlags & AccessFlags.ACC_FINAL) != 0;
|
||||
return (accFlags & AccessFlags.FINAL) != 0;
|
||||
}
|
||||
|
||||
public boolean isConstructor() {
|
||||
return (accFlags & AccessFlags.ACC_CONSTRUCTOR) != 0;
|
||||
return (accFlags & AccessFlags.CONSTRUCTOR) != 0;
|
||||
}
|
||||
|
||||
public boolean isEnum() {
|
||||
return (accFlags & AccessFlags.ACC_ENUM) != 0;
|
||||
return (accFlags & AccessFlags.ENUM) != 0;
|
||||
}
|
||||
|
||||
public boolean isSynthetic() {
|
||||
return (accFlags & AccessFlags.ACC_SYNTHETIC) != 0;
|
||||
return (accFlags & AccessFlags.SYNTHETIC) != 0;
|
||||
}
|
||||
|
||||
public boolean isBridge() {
|
||||
return (accFlags & AccessFlags.ACC_BRIDGE) != 0;
|
||||
return (accFlags & AccessFlags.BRIDGE) != 0;
|
||||
}
|
||||
|
||||
public boolean isVarArgs() {
|
||||
return (accFlags & AccessFlags.ACC_VARARGS) != 0;
|
||||
return (accFlags & AccessFlags.VARARGS) != 0;
|
||||
}
|
||||
|
||||
public boolean isSynchronized() {
|
||||
return (accFlags & (AccessFlags.ACC_SYNCHRONIZED | AccessFlags.ACC_DECLARED_SYNCHRONIZED)) != 0;
|
||||
return (accFlags & (AccessFlags.SYNCHRONIZED | AccessFlags.DECLARED_SYNCHRONIZED)) != 0;
|
||||
}
|
||||
|
||||
public boolean isTransient() {
|
||||
return (accFlags & AccessFlags.ACC_TRANSIENT) != 0;
|
||||
return (accFlags & AccessFlags.TRANSIENT) != 0;
|
||||
}
|
||||
|
||||
public boolean isVolatile() {
|
||||
return (accFlags & AccessFlags.ACC_VOLATILE) != 0;
|
||||
return (accFlags & AccessFlags.VOLATILE) != 0;
|
||||
}
|
||||
|
||||
public AFType getType() {
|
||||
@@ -174,14 +173,14 @@ public class AccessInfo {
|
||||
break;
|
||||
|
||||
case CLASS:
|
||||
if ((accFlags & AccessFlags.ACC_STRICT) != 0) {
|
||||
if ((accFlags & AccessFlags.STRICT) != 0) {
|
||||
code.append("strict ");
|
||||
}
|
||||
if (Consts.DEBUG) {
|
||||
if ((accFlags & AccessFlags.ACC_SUPER) != 0) {
|
||||
if ((accFlags & AccessFlags.SUPER) != 0) {
|
||||
code.append("/* super */ ");
|
||||
}
|
||||
if ((accFlags & AccessFlags.ACC_ENUM) != 0) {
|
||||
if ((accFlags & AccessFlags.ENUM) != 0) {
|
||||
code.append("/* enum */ ");
|
||||
}
|
||||
}
|
||||
@@ -209,25 +208,12 @@ public class AccessInfo {
|
||||
throw new JadxRuntimeException("Unknown visibility flags: " + getVisibility());
|
||||
}
|
||||
|
||||
public String rawString() {
|
||||
switch (type) {
|
||||
case CLASS:
|
||||
return AccessFlags.classString(accFlags);
|
||||
case FIELD:
|
||||
return AccessFlags.fieldString(accFlags);
|
||||
case METHOD:
|
||||
return AccessFlags.methodString(accFlags);
|
||||
default:
|
||||
return "?";
|
||||
}
|
||||
}
|
||||
|
||||
public int rawValue() {
|
||||
return accFlags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "AccessInfo: " + type + " 0x" + Integer.toHexString(accFlags) + " (" + rawString() + ')';
|
||||
return "AccessInfo: " + type + " 0x" + Integer.toHexString(accFlags) + " (" + makeString() + ')';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
@@ -37,13 +36,6 @@ public final class ClassInfo implements Comparable<ClassInfo> {
|
||||
return root.getInfoStorage().putCls(newClsInfo);
|
||||
}
|
||||
|
||||
public static ClassInfo fromDex(DexNode dex, int clsIndex) {
|
||||
if (clsIndex == DexNode.NO_INDEX) {
|
||||
return null;
|
||||
}
|
||||
return fromType(dex.root(), dex.getType(clsIndex));
|
||||
}
|
||||
|
||||
public static ClassInfo fromName(RootNode root, String clsName) {
|
||||
return fromType(root, ArgType.object(clsName));
|
||||
}
|
||||
@@ -70,6 +62,8 @@ public final class ClassInfo implements Comparable<ClassInfo> {
|
||||
ClassAliasInfo newAlias = new ClassAliasInfo(getAliasPkg(), aliasName);
|
||||
fillAliasFullName(newAlias);
|
||||
this.alias = newAlias;
|
||||
} else {
|
||||
this.alias = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,26 +2,28 @@ package jadx.core.dex.info;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.FieldInitAttr;
|
||||
import jadx.core.dex.instructions.args.LiteralArg;
|
||||
import jadx.core.dex.instructions.args.PrimitiveType;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.parser.FieldInitAttr;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
|
||||
public class ConstStorage {
|
||||
|
||||
private static final class ValueStorage {
|
||||
private final Map<Object, FieldNode> values = new HashMap<>();
|
||||
private final Map<Object, FieldNode> values = new ConcurrentHashMap<>();
|
||||
private final Set<Object> duplicates = new HashSet<>();
|
||||
|
||||
public Map<Object, FieldNode> getValues() {
|
||||
@@ -36,22 +38,33 @@ public class ConstStorage {
|
||||
* @return true if this value is duplicated
|
||||
*/
|
||||
public boolean put(Object value, FieldNode fld) {
|
||||
if (duplicates.contains(value)) {
|
||||
values.remove(value);
|
||||
return true;
|
||||
}
|
||||
FieldNode prev = values.put(value, fld);
|
||||
if (prev != null) {
|
||||
values.remove(value);
|
||||
duplicates.add(value);
|
||||
return true;
|
||||
}
|
||||
if (duplicates.contains(value)) {
|
||||
values.remove(value);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean contains(Object value) {
|
||||
return duplicates.contains(value) || values.containsKey(value);
|
||||
}
|
||||
|
||||
void removeForCls(ClassNode cls) {
|
||||
Iterator<Entry<Object, FieldNode>> it = values.entrySet().iterator();
|
||||
while (it.hasNext()) {
|
||||
Entry<Object, FieldNode> entry = it.next();
|
||||
FieldNode field = entry.getValue();
|
||||
if (field.getParentClass().equals(cls)) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final boolean replaceEnabled;
|
||||
@@ -73,15 +86,20 @@ public class ConstStorage {
|
||||
if (accFlags.isStatic() && accFlags.isFinal()) {
|
||||
FieldInitAttr fv = f.get(AType.FIELD_INIT);
|
||||
if (fv != null
|
||||
&& fv.getValue() != null
|
||||
&& fv.getValueType() == FieldInitAttr.InitType.CONST
|
||||
&& fv != FieldInitAttr.NULL_VALUE) {
|
||||
addConstField(cls, f, fv.getValue(), accFlags.isPublic());
|
||||
&& fv != FieldInitAttr.NULL_VALUE
|
||||
&& fv.getEncodedValue() != null) {
|
||||
addConstField(cls, f, fv.getEncodedValue().getValue(), accFlags.isPublic());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void removeForClass(ClassNode cls) {
|
||||
classes.remove(cls);
|
||||
globalValues.removeForCls(cls);
|
||||
}
|
||||
|
||||
private void addConstField(ClassNode cls, FieldNode fld, Object value, boolean isPublic) {
|
||||
if (isPublic) {
|
||||
globalValues.put(value, fld);
|
||||
@@ -91,12 +109,7 @@ public class ConstStorage {
|
||||
}
|
||||
|
||||
private ValueStorage getClsValues(ClassNode cls) {
|
||||
ValueStorage classValues = classes.get(cls);
|
||||
if (classValues == null) {
|
||||
classValues = new ValueStorage();
|
||||
classes.put(cls, classValues);
|
||||
}
|
||||
return classValues;
|
||||
return classes.computeIfAbsent(cls, c -> new ValueStorage());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -104,9 +117,9 @@ public class ConstStorage {
|
||||
if (!replaceEnabled) {
|
||||
return null;
|
||||
}
|
||||
DexNode dex = cls.dex();
|
||||
RootNode root = cls.root();
|
||||
if (value instanceof Integer) {
|
||||
FieldNode rField = getResourceField((Integer) value, dex);
|
||||
FieldNode rField = getResourceField((Integer) value, root);
|
||||
if (rField != null) {
|
||||
return rField;
|
||||
}
|
||||
@@ -131,7 +144,7 @@ public class ConstStorage {
|
||||
if (parentClass == null) {
|
||||
break;
|
||||
}
|
||||
current = dex.resolveClass(parentClass);
|
||||
current = root.resolveClass(parentClass);
|
||||
}
|
||||
if (searchGlobal) {
|
||||
return globalValues.get(value);
|
||||
@@ -140,12 +153,12 @@ public class ConstStorage {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private FieldNode getResourceField(Integer value, DexNode dex) {
|
||||
private FieldNode getResourceField(Integer value, RootNode root) {
|
||||
String str = resourcesNames.get(value);
|
||||
if (str == null) {
|
||||
return null;
|
||||
}
|
||||
ClassNode appResClass = dex.root().getAppResClass();
|
||||
ClassNode appResClass = root.getAppResClass();
|
||||
if (appResClass == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -160,7 +173,7 @@ public class ConstStorage {
|
||||
return innerClass.searchFieldByName(fieldName);
|
||||
}
|
||||
}
|
||||
ErrorsCounter.classWarn(appResClass, "Not found resource field with id: " + value + ", name: " + str.replace('/', '.'));
|
||||
appResClass.addWarn("Not found resource field with id: " + value + ", name: " + str.replace('/', '.'));
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,11 +2,10 @@ package jadx.core.dex.info;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import com.android.dex.FieldId;
|
||||
|
||||
import jadx.api.plugins.input.data.IFieldData;
|
||||
import jadx.core.codegen.TypeGen;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
|
||||
public final class FieldInfo {
|
||||
|
||||
@@ -22,17 +21,15 @@ public final class FieldInfo {
|
||||
this.alias = name;
|
||||
}
|
||||
|
||||
public static FieldInfo from(DexNode dex, ClassInfo declClass, String name, ArgType type) {
|
||||
public static FieldInfo from(RootNode root, ClassInfo declClass, String name, ArgType type) {
|
||||
FieldInfo field = new FieldInfo(declClass, name, type);
|
||||
return dex.root().getInfoStorage().getField(field);
|
||||
return root.getInfoStorage().getField(field);
|
||||
}
|
||||
|
||||
public static FieldInfo fromDex(DexNode dex, int index) {
|
||||
FieldId field = dex.getFieldId(index);
|
||||
return from(dex,
|
||||
ClassInfo.fromDex(dex, field.getDeclaringClassIndex()),
|
||||
dex.getString(field.getNameIndex()),
|
||||
dex.getType(field.getTypeIndex()));
|
||||
public static FieldInfo fromData(RootNode root, IFieldData fieldData) {
|
||||
ClassInfo declClass = ClassInfo.fromName(root, fieldData.getParentClassType());
|
||||
FieldInfo field = new FieldInfo(declClass, fieldData.getName(), ArgType.parse(fieldData.getType()));
|
||||
return root.getInfoStorage().getField(field);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
|
||||
@@ -4,13 +4,15 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
|
||||
public class InfoStorage {
|
||||
|
||||
private final Map<ArgType, ClassInfo> classes = new HashMap<>();
|
||||
private final Map<Integer, MethodInfo> methods = new HashMap<>();
|
||||
private final Map<FieldInfo, FieldInfo> fields = new HashMap<>();
|
||||
// use only one MethodInfo instance
|
||||
private final Map<MethodInfo, MethodInfo> uniqueMethods = new HashMap<>();
|
||||
// can contain same method with different ids (from different dex files)
|
||||
private final Map<Integer, MethodInfo> methods = new HashMap<>();
|
||||
|
||||
public ClassInfo getCls(ArgType type) {
|
||||
return classes.get(type);
|
||||
@@ -23,18 +25,26 @@ public class InfoStorage {
|
||||
}
|
||||
}
|
||||
|
||||
private int generateMethodLookupId(DexNode dex, int mthId) {
|
||||
return dex.getDexId() << 16 | mthId;
|
||||
}
|
||||
|
||||
public MethodInfo getMethod(DexNode dex, int mtdId) {
|
||||
return methods.get(generateMethodLookupId(dex, mtdId));
|
||||
}
|
||||
|
||||
public MethodInfo putMethod(DexNode dex, int mthId, MethodInfo mth) {
|
||||
public MethodInfo getByUniqId(int id) {
|
||||
synchronized (methods) {
|
||||
MethodInfo prev = methods.put(generateMethodLookupId(dex, mthId), mth);
|
||||
return prev == null ? mth : prev;
|
||||
return methods.get(id);
|
||||
}
|
||||
}
|
||||
|
||||
public void putByUniqId(int id, MethodInfo mth) {
|
||||
synchronized (methods) {
|
||||
methods.put(id, mth);
|
||||
}
|
||||
}
|
||||
|
||||
public MethodInfo putMethod(MethodInfo newMth) {
|
||||
synchronized (uniqueMethods) {
|
||||
MethodInfo prev = uniqueMethods.get(newMth);
|
||||
if (prev != null) {
|
||||
return prev;
|
||||
}
|
||||
uniqueMethods.put(newMth, newMth);
|
||||
return newMth;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,59 +1,57 @@
|
||||
package jadx.core.dex.info;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import com.android.dex.MethodId;
|
||||
import com.android.dex.ProtoId;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.plugins.input.data.IMethodRef;
|
||||
import jadx.core.codegen.TypeGen;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
public final class MethodInfo {
|
||||
public final class MethodInfo implements Comparable<MethodInfo> {
|
||||
|
||||
private final String name;
|
||||
private final ArgType retType;
|
||||
private final List<ArgType> args;
|
||||
private final List<ArgType> argTypes;
|
||||
private final ClassInfo declClass;
|
||||
private final String shortId;
|
||||
private String alias;
|
||||
private boolean aliasFromPreset;
|
||||
|
||||
private MethodInfo(DexNode dex, int mthIndex) {
|
||||
MethodId mthId = dex.getMethodId(mthIndex);
|
||||
name = dex.getString(mthId.getNameIndex());
|
||||
alias = name;
|
||||
aliasFromPreset = false;
|
||||
declClass = ClassInfo.fromDex(dex, mthId.getDeclaringClassIndex());
|
||||
|
||||
ProtoId proto = dex.getProtoId(mthId.getProtoIndex());
|
||||
retType = dex.getType(proto.getReturnTypeIndex());
|
||||
args = dex.readParamList(proto.getParametersOffset());
|
||||
shortId = makeSignature(true);
|
||||
}
|
||||
|
||||
private MethodInfo(ClassInfo declClass, String name, List<ArgType> args, ArgType retType) {
|
||||
this.name = name;
|
||||
this.alias = name;
|
||||
this.aliasFromPreset = false;
|
||||
this.declClass = declClass;
|
||||
this.args = args;
|
||||
this.argTypes = args;
|
||||
this.retType = retType;
|
||||
this.shortId = makeSignature(true);
|
||||
this.shortId = makeShortId(name, argTypes, retType);
|
||||
}
|
||||
|
||||
public static MethodInfo externalMth(ClassInfo declClass, String name, List<ArgType> args, ArgType retType) {
|
||||
return new MethodInfo(declClass, name, args, retType);
|
||||
}
|
||||
|
||||
public static MethodInfo fromDex(DexNode dex, int mthIndex) {
|
||||
MethodInfo mth = dex.root().getInfoStorage().getMethod(dex, mthIndex);
|
||||
if (mth != null) {
|
||||
return mth;
|
||||
public static MethodInfo fromRef(RootNode root, IMethodRef methodRef) {
|
||||
InfoStorage infoStorage = root.getInfoStorage();
|
||||
int uniqId = methodRef.getUniqId();
|
||||
MethodInfo prevMth = infoStorage.getByUniqId(uniqId);
|
||||
if (prevMth != null) {
|
||||
return prevMth;
|
||||
}
|
||||
mth = new MethodInfo(dex, mthIndex);
|
||||
return dex.root().getInfoStorage().putMethod(dex, mthIndex, mth);
|
||||
methodRef.load();
|
||||
ArgType parentClsType = ArgType.parse(methodRef.getParentClassType());
|
||||
ClassInfo parentClass = ClassInfo.fromType(root, parentClsType);
|
||||
ArgType returnType = ArgType.parse(methodRef.getReturnType());
|
||||
List<ArgType> args = Utils.collectionMap(methodRef.getArgTypes(), ArgType::parse);
|
||||
MethodInfo newMth = new MethodInfo(parentClass, methodRef.getName(), args, returnType);
|
||||
MethodInfo uniqMth = infoStorage.putMethod(newMth);
|
||||
infoStorage.putByUniqId(uniqId, uniqMth);
|
||||
return uniqMth;
|
||||
}
|
||||
|
||||
public static MethodInfo fromDetails(RootNode root, ClassInfo declClass, String name, List<ArgType> args, ArgType retType) {
|
||||
MethodInfo newMth = new MethodInfo(declClass, name, args, retType);
|
||||
return root.getInfoStorage().putMethod(newMth);
|
||||
}
|
||||
|
||||
public String makeSignature(boolean includeRetType) {
|
||||
@@ -61,17 +59,29 @@ public final class MethodInfo {
|
||||
}
|
||||
|
||||
public String makeSignature(boolean useAlias, boolean includeRetType) {
|
||||
StringBuilder signature = new StringBuilder();
|
||||
signature.append(useAlias ? alias : name);
|
||||
signature.append('(');
|
||||
for (ArgType arg : args) {
|
||||
signature.append(TypeGen.signature(arg));
|
||||
return makeShortId(useAlias ? alias : name,
|
||||
argTypes,
|
||||
includeRetType ? retType : null);
|
||||
}
|
||||
|
||||
public static String makeShortId(String name, List<ArgType> argTypes, @Nullable ArgType retType) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(name);
|
||||
sb.append('(');
|
||||
for (ArgType arg : argTypes) {
|
||||
sb.append(TypeGen.signature(arg));
|
||||
}
|
||||
signature.append(')');
|
||||
if (includeRetType) {
|
||||
signature.append(TypeGen.signature(retType));
|
||||
sb.append(')');
|
||||
if (retType != null) {
|
||||
sb.append(TypeGen.signature(retType));
|
||||
}
|
||||
return signature.toString();
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public boolean isOverloadedBy(MethodInfo otherMthInfo) {
|
||||
return argTypes.size() == otherMthInfo.argTypes.size()
|
||||
&& name.equals(otherMthInfo.name)
|
||||
&& !Objects.equals(this.shortId, otherMthInfo.shortId);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
@@ -106,11 +116,11 @@ public final class MethodInfo {
|
||||
}
|
||||
|
||||
public List<ArgType> getArgumentsTypes() {
|
||||
return args;
|
||||
return argTypes;
|
||||
}
|
||||
|
||||
public int getArgsCount() {
|
||||
return args.size();
|
||||
return argTypes.size();
|
||||
}
|
||||
|
||||
public boolean isConstructor() {
|
||||
@@ -143,10 +153,7 @@ public final class MethodInfo {
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = declClass.hashCode();
|
||||
result = 31 * result + retType.hashCode();
|
||||
result = 31 * result + shortId.hashCode();
|
||||
return result;
|
||||
return shortId.hashCode() + 31 * declClass.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -159,13 +166,21 @@ public final class MethodInfo {
|
||||
}
|
||||
MethodInfo other = (MethodInfo) obj;
|
||||
return shortId.equals(other.shortId)
|
||||
&& retType.equals(other.retType)
|
||||
&& declClass.equals(other.declClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(MethodInfo other) {
|
||||
int clsCmp = declClass.compareTo(other.declClass);
|
||||
if (clsCmp != 0) {
|
||||
return clsCmp;
|
||||
}
|
||||
return shortId.compareTo(other.shortId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return declClass.getFullName() + '.' + name
|
||||
+ '(' + Utils.listToString(args) + "):" + retType;
|
||||
+ '(' + Utils.listToString(argTypes) + "):" + retType;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
package jadx.core.dex.instructions;
|
||||
|
||||
import com.android.dx.io.instructions.DecodedInstruction;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.plugins.input.insns.InsnData;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
@@ -9,41 +10,54 @@ import jadx.core.dex.instructions.args.LiteralArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.utils.InsnUtils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public class ArithNode extends InsnNode {
|
||||
|
||||
private final ArithOp op;
|
||||
|
||||
public ArithNode(DecodedInstruction insn, ArithOp op, ArgType type, boolean literal) {
|
||||
super(InsnType.ARITH, 2);
|
||||
this.op = op;
|
||||
setResult(InsnArg.reg(insn, 0, type));
|
||||
|
||||
int rc = insn.getRegisterCount();
|
||||
if (literal) {
|
||||
if (rc == 1) {
|
||||
// self
|
||||
addReg(insn, 0, type);
|
||||
addLit(insn, type);
|
||||
} else if (rc == 2) {
|
||||
// normal
|
||||
addReg(insn, 1, type);
|
||||
addLit(insn, type);
|
||||
}
|
||||
} else {
|
||||
if (rc == 2) {
|
||||
// self
|
||||
addReg(insn, 0, type);
|
||||
addReg(insn, 1, type);
|
||||
} else if (rc == 3) {
|
||||
// normal
|
||||
addReg(insn, 1, type);
|
||||
addReg(insn, 2, type);
|
||||
}
|
||||
public static ArithNode build(InsnData insn, ArithOp op, ArgType type) {
|
||||
RegisterArg resArg = InsnArg.reg(insn, 0, fixResultType(op, type));
|
||||
ArgType argType = fixArgType(op, type);
|
||||
switch (insn.getRegsCount()) {
|
||||
case 2:
|
||||
return new ArithNode(op, resArg, InsnArg.reg(insn, 0, argType), InsnArg.reg(insn, 1, argType));
|
||||
case 3:
|
||||
return new ArithNode(op, resArg, InsnArg.reg(insn, 1, argType), InsnArg.reg(insn, 2, argType));
|
||||
default:
|
||||
throw new JadxRuntimeException("Unexpected registers count in " + insn);
|
||||
}
|
||||
}
|
||||
|
||||
public ArithNode(ArithOp op, RegisterArg res, InsnArg a, InsnArg b) {
|
||||
public static ArithNode buildLit(InsnData insn, ArithOp op, ArgType type) {
|
||||
RegisterArg resArg = InsnArg.reg(insn, 0, fixResultType(op, type));
|
||||
ArgType argType = fixArgType(op, type);
|
||||
LiteralArg litArg = InsnArg.lit(insn, argType);
|
||||
switch (insn.getRegsCount()) {
|
||||
case 1:
|
||||
return new ArithNode(op, resArg, InsnArg.reg(insn, 0, argType), litArg);
|
||||
case 2:
|
||||
return new ArithNode(op, resArg, InsnArg.reg(insn, 1, argType), litArg);
|
||||
default:
|
||||
throw new JadxRuntimeException("Unexpected registers count in " + insn);
|
||||
}
|
||||
}
|
||||
|
||||
private static ArgType fixResultType(ArithOp op, ArgType type) {
|
||||
if (type == ArgType.INT && op.isBitOp()) {
|
||||
return ArgType.INT_BOOLEAN;
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
private static ArgType fixArgType(ArithOp op, ArgType type) {
|
||||
if (type == ArgType.INT && op.isBitOp()) {
|
||||
return ArgType.NARROW_NUMBERS_NO_FLOAT;
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
private final ArithOp op;
|
||||
|
||||
public ArithNode(ArithOp op, @Nullable RegisterArg res, InsnArg a, InsnArg b) {
|
||||
super(InsnType.ARITH, 2);
|
||||
this.op = op;
|
||||
setResult(res);
|
||||
@@ -97,10 +111,7 @@ public class ArithNode extends InsnNode {
|
||||
|
||||
@Override
|
||||
public InsnNode copy() {
|
||||
ArithNode copy = new ArithNode(op,
|
||||
getResult().duplicate(),
|
||||
getArg(0).duplicate(),
|
||||
getArg(1).duplicate());
|
||||
ArithNode copy = new ArithNode(op, null, getArg(0).duplicate(), getArg(1).duplicate());
|
||||
return copyCommonParams(copy);
|
||||
}
|
||||
|
||||
|
||||
@@ -24,4 +24,15 @@ public enum ArithOp {
|
||||
public String getSymbol() {
|
||||
return this.symbol;
|
||||
}
|
||||
|
||||
public boolean isBitOp() {
|
||||
switch (this) {
|
||||
case AND:
|
||||
case OR:
|
||||
case XOR:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package jadx.core.dex.instructions;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
|
||||
public abstract class BaseInvokeNode extends InsnNode {
|
||||
public BaseInvokeNode(InsnType type, int argsCount) {
|
||||
super(type, argsCount);
|
||||
}
|
||||
|
||||
public abstract MethodInfo getCallMth();
|
||||
|
||||
@Nullable
|
||||
public abstract InsnArg getInstanceArg();
|
||||
|
||||
public abstract boolean isStaticCall();
|
||||
|
||||
/**
|
||||
* Return offset to match method args from {@link #getCallMth()}
|
||||
*/
|
||||
public abstract int getFirstArgOffset();
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package jadx.core.dex.instructions;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
|
||||
public interface CallMthInterface {
|
||||
|
||||
MethodInfo getCallMth();
|
||||
|
||||
@Nullable
|
||||
RegisterArg getInstanceArg();
|
||||
|
||||
/**
|
||||
* Return offset to match method args from {@link #getCallMth()}
|
||||
*/
|
||||
int getFirstArgOffset();
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user