Compare commits
137 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fdf170529f | |||
| 50283ab543 | |||
| 3fa3e5acec | |||
| 4230cd5b5a | |||
| 1ad6527de5 | |||
| 0421ad80c1 | |||
| 35e0201f06 | |||
| 118eea5e77 | |||
| 7f317be325 | |||
| e1aa9f6de4 | |||
| 058a5e3bb2 | |||
| 92b49ec2b5 | |||
| 583a04b092 | |||
| 444a04e2f7 | |||
| 157e702ffd | |||
| 77892f41ec | |||
| 6ba0e1dbf6 | |||
| 950fbbaa83 | |||
| 912c431511 | |||
| 5d6b82724a | |||
| 78c976ad4f | |||
| fbdfd135da | |||
| dd51783d9e | |||
| 158fc2fca3 | |||
| 24284a6f3a | |||
| 85c2c63aa3 | |||
| f354f7de63 | |||
| 540c0a8100 | |||
| 4d00fede56 | |||
| b1bc5c08ff | |||
| 305d4f4fe5 | |||
| 2d149e9a5d | |||
| 87b9ff3c35 | |||
| 1c36b3c74c | |||
| 068e4b8e3d | |||
| df38a6424f | |||
| 5d186e56a5 | |||
| 0fafcfa006 | |||
| e3fdbafd86 | |||
| 07c2b14479 | |||
| cdc844aaf3 | |||
| e1b7d361b9 | |||
| 12ef29bebc | |||
| 22ed241d50 | |||
| 28e5a3c5be | |||
| bb4d88cc68 | |||
| 4aaea2b93f | |||
| bc8d7c4fc3 | |||
| 5ea6c46778 | |||
| b28f8ba85b | |||
| 4db50fb749 | |||
| 1dd0c90a04 | |||
| 2bace2bde2 | |||
| 1a9cb832ab | |||
| 6844a46c93 | |||
| e9e45707da | |||
| b9d02ff4c4 | |||
| 29b64300bc | |||
| 777355e86e | |||
| 620a177ce8 | |||
| 683c2dfbeb | |||
| 266cbcc6f4 | |||
| 8a45602ae6 | |||
| 711419a797 | |||
| 603f3057eb | |||
| fa6fc1f871 | |||
| 49fa320989 | |||
| 2f301bf150 | |||
| b4892ce17f | |||
| 151c171616 | |||
| 79477a2de3 | |||
| 78aadda931 | |||
| b50706505f | |||
| 9114821fb1 | |||
| 1195582da8 | |||
| 258987b0ff | |||
| a6a734c70d | |||
| d6c23a2a9b | |||
| db028904d7 | |||
| 63a571306c | |||
| bc4db61e25 | |||
| c7e6e28830 | |||
| 1d7b6fdb2c | |||
| ce5d8eeff8 | |||
| 894e0e6132 | |||
| 127f0ecf3f | |||
| cf7767e702 | |||
| e0aedc7949 | |||
| bad78de74c | |||
| 12df8a169f | |||
| 15c9d33339 | |||
| 7e0fafbaf1 | |||
| 57b9c1dd7a | |||
| 8ba0c17259 | |||
| cd32151083 | |||
| 75b52d672e | |||
| 11d04508f7 | |||
| e641b773b5 | |||
| 6e5899c654 | |||
| c66ffaa7f9 | |||
| 5193c6a5d8 | |||
| e7212af547 | |||
| 3ca1357af4 | |||
| 90e95213e4 | |||
| ae2d4da585 | |||
| 691d5cd1e6 | |||
| 58a46c6417 | |||
| d3f6160e62 | |||
| 03e4afb12f | |||
| 6802f6028e | |||
| 5ca61cfe18 | |||
| 32d55b48f2 | |||
| ab4b6f9e54 | |||
| 9100ad1220 | |||
| 8b4f8fb572 | |||
| 87e0e5bf16 | |||
| e4c2d6cf6e | |||
| fb0bdb5112 | |||
| f4b3645435 | |||
| c27f2badf7 | |||
| 1a877d6535 | |||
| 5ada9331b6 | |||
| a0f4ccb7a4 | |||
| 5b5524a7dd | |||
| 3cc464c9c9 | |||
| 51555667cf | |||
| e01ea7010f | |||
| 77732c83c9 | |||
| a67fc83949 | |||
| 3d920725aa | |||
| 2f2fbea558 | |||
| e7a86a2960 | |||
| b282d97ffe | |||
| e4ca52a95f | |||
| d972d9ec74 | |||
| 0721a6b050 | |||
| 762ee6550e |
@@ -57,7 +57,9 @@ jobs:
|
|||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up JDK
|
- name: Set up JDK
|
||||||
uses: oracle-actions/setup-java@v1 # set latest java version by default
|
uses: oracle-actions/setup-java@v1
|
||||||
|
with:
|
||||||
|
release: 17
|
||||||
|
|
||||||
- name: Print Java version
|
- name: Print Java version
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v2
|
uses: github/codeql-action/init@v2
|
||||||
|
|||||||
@@ -6,5 +6,5 @@ jobs:
|
|||||||
name: "Validation"
|
name: "Validation"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: gradle/wrapper-validation-action@v1
|
- uses: gradle/wrapper-validation-action@v1
|
||||||
|
|||||||
@@ -102,6 +102,7 @@ options:
|
|||||||
--add-debug-lines - add comments with debug line numbers if available
|
--add-debug-lines - add comments with debug line numbers if available
|
||||||
--no-inline-anonymous - disable anonymous classes inline
|
--no-inline-anonymous - disable anonymous classes inline
|
||||||
--no-inline-methods - disable methods inline
|
--no-inline-methods - disable methods inline
|
||||||
|
--no-finally - don't extract finally block
|
||||||
--no-replace-consts - don't replace constant value with matching constant field
|
--no-replace-consts - don't replace constant value with matching constant field
|
||||||
--escape-unicode - escape non latin characters in strings (with \u)
|
--escape-unicode - escape non latin characters in strings (with \u)
|
||||||
--respect-bytecode-access-modifiers - don't change original access modifiers
|
--respect-bytecode-access-modifiers - don't change original access modifiers
|
||||||
@@ -116,6 +117,10 @@ options:
|
|||||||
'ignore' - don't read and don't save
|
'ignore' - don't read and don't save
|
||||||
--deobf-use-sourcename - use source file name as class name alias
|
--deobf-use-sourcename - use source file name as class name alias
|
||||||
--deobf-parse-kotlin-metadata - parse kotlin metadata to class and package names
|
--deobf-parse-kotlin-metadata - parse kotlin metadata to class and package names
|
||||||
|
--deobf-res-name-source - better name source for resources:
|
||||||
|
'auto' - automatically select best name (default)
|
||||||
|
'resources' - use resources names
|
||||||
|
'code' - use R class fields names
|
||||||
--use-kotlin-methods-for-var-names - use kotlin intrinsic methods to rename variables, values: disable, apply, apply-and-hide, default: apply
|
--use-kotlin-methods-for-var-names - use kotlin intrinsic methods to rename variables, values: disable, apply, apply-and-hide, default: apply
|
||||||
--rename-flags - fix options (comma-separated list of):
|
--rename-flags - fix options (comma-separated list of):
|
||||||
'case' - fix case sensitivity issues (according to --fs-case-sensitive option),
|
'case' - fix case sensitivity issues (according to --fs-case-sensitive option),
|
||||||
|
|||||||
+10
-10
@@ -1,6 +1,6 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'com.github.ben-manes.versions' version '0.42.0'
|
id 'com.github.ben-manes.versions' version '0.45.0'
|
||||||
id 'com.diffplug.spotless' version '6.6.1'
|
id 'com.diffplug.spotless' version '6.13.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev"
|
ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev"
|
||||||
@@ -26,18 +26,18 @@ allprojects {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'org.slf4j:slf4j-api:1.7.36'
|
implementation 'org.slf4j:slf4j-api:2.0.6'
|
||||||
compileOnly 'org.jetbrains:annotations:23.0.0'
|
compileOnly 'org.jetbrains:annotations:24.0.0'
|
||||||
|
|
||||||
testImplementation 'ch.qos.logback:logback-classic:1.2.11'
|
testImplementation 'ch.qos.logback:logback-classic:1.3.5'
|
||||||
testImplementation 'org.hamcrest:hamcrest-library:2.2'
|
testImplementation 'org.hamcrest:hamcrest-library:2.2'
|
||||||
testImplementation 'org.mockito:mockito-core:4.6.0'
|
testImplementation 'org.mockito:mockito-core:4.10.0'
|
||||||
testImplementation 'org.assertj:assertj-core:3.22.0'
|
testImplementation 'org.assertj:assertj-core:3.24.2'
|
||||||
|
|
||||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
|
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.2'
|
||||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2'
|
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.2'
|
||||||
|
|
||||||
testCompileOnly 'org.jetbrains:annotations:23.0.0'
|
testCompileOnly 'org.jetbrains:annotations:24.0.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
test {
|
test {
|
||||||
|
|||||||
Vendored
BIN
Binary file not shown.
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionSha256Sum=29e49b10984e585d8118b7d0bc452f944e386458df27371b49b4ac1dec4b7fda
|
distributionSha256Sum=7ba68c54029790ab444b39d7e293d3236b2632631fb5f2e012bb28b4ff669e4b
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
@@ -205,6 +205,12 @@ set -- \
|
|||||||
org.gradle.wrapper.GradleWrapperMain \
|
org.gradle.wrapper.GradleWrapperMain \
|
||||||
"$@"
|
"$@"
|
||||||
|
|
||||||
|
# Stop when "xargs" is not available.
|
||||||
|
if ! command -v xargs >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
echo "xargs is not available"
|
||||||
|
fi
|
||||||
|
|
||||||
# Use "xargs" to parse quoted args.
|
# Use "xargs" to parse quoted args.
|
||||||
#
|
#
|
||||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||||
|
|||||||
Vendored
+8
-6
@@ -14,7 +14,7 @@
|
|||||||
@rem limitations under the License.
|
@rem limitations under the License.
|
||||||
@rem
|
@rem
|
||||||
|
|
||||||
@if "%DEBUG%" == "" @echo off
|
@if "%DEBUG%"=="" @echo off
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
@rem
|
@rem
|
||||||
@rem Gradle startup script for Windows
|
@rem Gradle startup script for Windows
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
if "%OS%"=="Windows_NT" setlocal
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
set DIRNAME=%~dp0
|
set DIRNAME=%~dp0
|
||||||
if "%DIRNAME%" == "" set DIRNAME=.
|
if "%DIRNAME%"=="" set DIRNAME=.
|
||||||
set APP_BASE_NAME=%~n0
|
set APP_BASE_NAME=%~n0
|
||||||
set APP_HOME=%DIRNAME%
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
|||||||
|
|
||||||
set JAVA_EXE=java.exe
|
set JAVA_EXE=java.exe
|
||||||
%JAVA_EXE% -version >NUL 2>&1
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
if "%ERRORLEVEL%" == "0" goto execute
|
if %ERRORLEVEL% equ 0 goto execute
|
||||||
|
|
||||||
echo.
|
echo.
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
@@ -75,13 +75,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
|||||||
|
|
||||||
:end
|
:end
|
||||||
@rem End local scope for the variables with windows NT shell
|
@rem End local scope for the variables with windows NT shell
|
||||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||||
|
|
||||||
:fail
|
:fail
|
||||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
rem the _cmd.exe /c_ return code!
|
rem the _cmd.exe /c_ return code!
|
||||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
set EXIT_CODE=%ERRORLEVEL%
|
||||||
exit /b 1
|
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||||
|
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||||
|
exit /b %EXIT_CODE%
|
||||||
|
|
||||||
:mainEnd
|
:mainEnd
|
||||||
if "%OS%"=="Windows_NT" endlocal
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ dependencies {
|
|||||||
runtimeOnly(project(':jadx-plugins:jadx-smali-input'))
|
runtimeOnly(project(':jadx-plugins:jadx-smali-input'))
|
||||||
|
|
||||||
implementation 'com.beust:jcommander:1.82'
|
implementation 'com.beust:jcommander:1.82'
|
||||||
implementation 'ch.qos.logback:logback-classic:1.2.11'
|
implementation 'ch.qos.logback:logback-classic:1.3.5'
|
||||||
}
|
}
|
||||||
|
|
||||||
application {
|
application {
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import java.util.List;
|
|||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Supplier;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
@@ -21,6 +23,7 @@ import jadx.api.JadxArgs.RenameEnum;
|
|||||||
import jadx.api.JadxArgs.UseKotlinMethodsForVarNames;
|
import jadx.api.JadxArgs.UseKotlinMethodsForVarNames;
|
||||||
import jadx.api.JadxDecompiler;
|
import jadx.api.JadxDecompiler;
|
||||||
import jadx.api.args.DeobfuscationMapFileMode;
|
import jadx.api.args.DeobfuscationMapFileMode;
|
||||||
|
import jadx.api.args.ResourceNameSource;
|
||||||
import jadx.core.utils.exceptions.JadxException;
|
import jadx.core.utils.exceptions.JadxException;
|
||||||
import jadx.core.utils.files.FileUtils;
|
import jadx.core.utils.files.FileUtils;
|
||||||
|
|
||||||
@@ -88,6 +91,12 @@ public class JadxCLIArgs {
|
|||||||
@Parameter(names = { "--no-inline-methods" }, description = "disable methods inline")
|
@Parameter(names = { "--no-inline-methods" }, description = "disable methods inline")
|
||||||
protected boolean inlineMethods = true;
|
protected boolean inlineMethods = true;
|
||||||
|
|
||||||
|
@Parameter(names = { "--no-inline-kotlin-lambda" }, description = "disable inline for Kotlin lambdas")
|
||||||
|
protected boolean allowInlineKotlinLambda = true;
|
||||||
|
|
||||||
|
@Parameter(names = "--no-finally", description = "don't extract finally block")
|
||||||
|
protected boolean extractFinally = true;
|
||||||
|
|
||||||
@Parameter(names = "--no-replace-consts", description = "don't replace constant value with matching constant field")
|
@Parameter(names = "--no-replace-consts", description = "don't replace constant value with matching constant field")
|
||||||
protected boolean replaceConsts = true;
|
protected boolean replaceConsts = true;
|
||||||
|
|
||||||
@@ -129,6 +138,16 @@ public class JadxCLIArgs {
|
|||||||
@Parameter(names = { "--deobf-parse-kotlin-metadata" }, description = "parse kotlin metadata to class and package names")
|
@Parameter(names = { "--deobf-parse-kotlin-metadata" }, description = "parse kotlin metadata to class and package names")
|
||||||
protected boolean deobfuscationParseKotlinMetadata = false;
|
protected boolean deobfuscationParseKotlinMetadata = false;
|
||||||
|
|
||||||
|
@Parameter(
|
||||||
|
names = { "--deobf-res-name-source" },
|
||||||
|
description = "better name source for resources:"
|
||||||
|
+ "\n 'auto' - automatically select best name (default)"
|
||||||
|
+ "\n 'resources' - use resources names"
|
||||||
|
+ "\n 'code' - use R class fields names",
|
||||||
|
converter = ResourceNameSourceConverter.class
|
||||||
|
)
|
||||||
|
protected ResourceNameSource resourceNameSource = ResourceNameSource.AUTO;
|
||||||
|
|
||||||
@Parameter(
|
@Parameter(
|
||||||
names = { "--use-kotlin-methods-for-var-names" },
|
names = { "--use-kotlin-methods-for-var-names" },
|
||||||
description = "use kotlin intrinsic methods to rename variables, values: disable, apply, apply-and-hide",
|
description = "use kotlin intrinsic methods to rename variables, values: disable, apply, apply-and-hide",
|
||||||
@@ -173,7 +192,7 @@ public class JadxCLIArgs {
|
|||||||
@Parameter(
|
@Parameter(
|
||||||
names = { "--log-level" },
|
names = { "--log-level" },
|
||||||
description = "set log level, values: quiet, progress, error, warn, info, debug",
|
description = "set log level, values: quiet, progress, error, warn, info, debug",
|
||||||
converter = LogHelper.LogLevelConverter.class
|
converter = LogLevelConverter.class
|
||||||
)
|
)
|
||||||
protected LogHelper.LogLevelEnum logLevel = LogHelper.LogLevelEnum.PROGRESS;
|
protected LogHelper.LogLevelEnum logLevel = LogHelper.LogLevelEnum.PROGRESS;
|
||||||
|
|
||||||
@@ -262,6 +281,7 @@ public class JadxCLIArgs {
|
|||||||
args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias);
|
args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias);
|
||||||
args.setParseKotlinMetadata(deobfuscationParseKotlinMetadata);
|
args.setParseKotlinMetadata(deobfuscationParseKotlinMetadata);
|
||||||
args.setUseKotlinMethodsForVarNames(useKotlinMethodsForVarNames);
|
args.setUseKotlinMethodsForVarNames(useKotlinMethodsForVarNames);
|
||||||
|
args.setResourceNameSource(resourceNameSource);
|
||||||
args.setEscapeUnicode(escapeUnicode);
|
args.setEscapeUnicode(escapeUnicode);
|
||||||
args.setRespectBytecodeAccModifiers(respectBytecodeAccessModifiers);
|
args.setRespectBytecodeAccModifiers(respectBytecodeAccessModifiers);
|
||||||
args.setExportAsGradleProject(exportAsGradleProject);
|
args.setExportAsGradleProject(exportAsGradleProject);
|
||||||
@@ -270,6 +290,8 @@ public class JadxCLIArgs {
|
|||||||
args.setInsertDebugLines(addDebugLines);
|
args.setInsertDebugLines(addDebugLines);
|
||||||
args.setInlineAnonymousClasses(inlineAnonymousClasses);
|
args.setInlineAnonymousClasses(inlineAnonymousClasses);
|
||||||
args.setInlineMethods(inlineMethods);
|
args.setInlineMethods(inlineMethods);
|
||||||
|
args.setAllowInlineKotlinLambda(allowInlineKotlinLambda);
|
||||||
|
args.setExtractFinally(extractFinally);
|
||||||
args.setRenameFlags(renameFlags);
|
args.setRenameFlags(renameFlags);
|
||||||
args.setFsCaseSensitive(fsCaseSensitive);
|
args.setFsCaseSensitive(fsCaseSensitive);
|
||||||
args.setCommentsLevel(commentsLevel);
|
args.setCommentsLevel(commentsLevel);
|
||||||
@@ -350,6 +372,14 @@ public class JadxCLIArgs {
|
|||||||
return inlineMethods;
|
return inlineMethods;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isAllowInlineKotlinLambda() {
|
||||||
|
return allowInlineKotlinLambda;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isExtractFinally() {
|
||||||
|
return extractFinally;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isDeobfuscationOn() {
|
public boolean isDeobfuscationOn() {
|
||||||
return deobfuscationOn;
|
return deobfuscationOn;
|
||||||
}
|
}
|
||||||
@@ -378,6 +408,10 @@ public class JadxCLIArgs {
|
|||||||
return deobfuscationParseKotlinMetadata;
|
return deobfuscationParseKotlinMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ResourceNameSource getResourceNameSource() {
|
||||||
|
return resourceNameSource;
|
||||||
|
}
|
||||||
|
|
||||||
public UseKotlinMethodsForVarNames getUseKotlinMethodsForVarNames() {
|
public UseKotlinMethodsForVarNames getUseKotlinMethodsForVarNames() {
|
||||||
return useKotlinMethodsForVarNames;
|
return useKotlinMethodsForVarNames;
|
||||||
}
|
}
|
||||||
@@ -463,54 +497,58 @@ public class JadxCLIArgs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class CommentsLevelConverter implements IStringConverter<CommentsLevel> {
|
public static class CommentsLevelConverter extends BaseEnumConverter<CommentsLevel> {
|
||||||
@Override
|
public CommentsLevelConverter() {
|
||||||
public CommentsLevel convert(String value) {
|
super(CommentsLevel::valueOf, CommentsLevel::values);
|
||||||
try {
|
|
||||||
return CommentsLevel.valueOf(value.toUpperCase());
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
'\'' + value + "' is unknown comments level, possible values are: "
|
|
||||||
+ JadxCLIArgs.enumValuesString(CommentsLevel.values()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class UseKotlinMethodsForVarNamesConverter implements IStringConverter<UseKotlinMethodsForVarNames> {
|
public static class UseKotlinMethodsForVarNamesConverter extends BaseEnumConverter<UseKotlinMethodsForVarNames> {
|
||||||
@Override
|
public UseKotlinMethodsForVarNamesConverter() {
|
||||||
public UseKotlinMethodsForVarNames convert(String value) {
|
super(UseKotlinMethodsForVarNames::valueOf, UseKotlinMethodsForVarNames::values);
|
||||||
try {
|
|
||||||
return UseKotlinMethodsForVarNames.valueOf(value.replace('-', '_').toUpperCase());
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
'\'' + value + "' is unknown, possible values are: "
|
|
||||||
+ JadxCLIArgs.enumValuesString(CommentsLevel.values()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class DeobfuscationMapFileModeConverter implements IStringConverter<DeobfuscationMapFileMode> {
|
public static class DeobfuscationMapFileModeConverter extends BaseEnumConverter<DeobfuscationMapFileMode> {
|
||||||
@Override
|
public DeobfuscationMapFileModeConverter() {
|
||||||
public DeobfuscationMapFileMode convert(String value) {
|
super(DeobfuscationMapFileMode::valueOf, DeobfuscationMapFileMode::values);
|
||||||
try {
|
|
||||||
return DeobfuscationMapFileMode.valueOf(value.toUpperCase());
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
'\'' + value + "' is unknown, possible values are: "
|
|
||||||
+ JadxCLIArgs.enumValuesString(DeobfuscationMapFileMode.values()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class DecompilationModeConverter implements IStringConverter<DecompilationMode> {
|
public static class ResourceNameSourceConverter extends BaseEnumConverter<ResourceNameSource> {
|
||||||
|
public ResourceNameSourceConverter() {
|
||||||
|
super(ResourceNameSource::valueOf, ResourceNameSource::values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class DecompilationModeConverter extends BaseEnumConverter<DecompilationMode> {
|
||||||
|
public DecompilationModeConverter() {
|
||||||
|
super(DecompilationMode::valueOf, DecompilationMode::values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class LogLevelConverter extends BaseEnumConverter<LogHelper.LogLevelEnum> {
|
||||||
|
public LogLevelConverter() {
|
||||||
|
super(LogHelper.LogLevelEnum::valueOf, LogHelper.LogLevelEnum::values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract static class BaseEnumConverter<E extends Enum<E>> implements IStringConverter<E> {
|
||||||
|
private final Function<String, E> parse;
|
||||||
|
private final Supplier<E[]> values;
|
||||||
|
|
||||||
|
public BaseEnumConverter(Function<String, E> parse, Supplier<E[]> values) {
|
||||||
|
this.parse = parse;
|
||||||
|
this.values = values;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DecompilationMode convert(String value) {
|
public E convert(String value) {
|
||||||
try {
|
try {
|
||||||
return DecompilationMode.valueOf(value.toUpperCase());
|
return parse.apply(stringAsEnumName(value));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
'\'' + value + "' is unknown, possible values are: "
|
'\'' + value + "' is unknown, possible values are: " + enumValuesString(values.get()));
|
||||||
+ JadxCLIArgs.enumValuesString(DecompilationMode.values()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -520,4 +558,9 @@ public class JadxCLIArgs {
|
|||||||
.map(v -> v.name().replace('_', '-').toLowerCase(Locale.ROOT))
|
.map(v -> v.name().replace('_', '-').toLowerCase(Locale.ROOT))
|
||||||
.collect(Collectors.joining(", "));
|
.collect(Collectors.joining(", "));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String stringAsEnumName(String value) {
|
||||||
|
// inverse of enumValuesString conversion
|
||||||
|
return value.replace('-', '_').toUpperCase(Locale.ROOT);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import com.beust.jcommander.IStringConverter;
|
|
||||||
|
|
||||||
import ch.qos.logback.classic.Level;
|
import ch.qos.logback.classic.Level;
|
||||||
import ch.qos.logback.classic.Logger;
|
import ch.qos.logback.classic.Logger;
|
||||||
|
|
||||||
@@ -119,18 +117,4 @@ public class LogHelper {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class LogLevelConverter implements IStringConverter<LogLevelEnum> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public LogLevelEnum convert(String value) {
|
|
||||||
try {
|
|
||||||
return LogLevelEnum.valueOf(value.toUpperCase());
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
'\'' + value + "' is unknown log level, possible values are "
|
|
||||||
+ JadxCLIArgs.enumValuesString(LogLevelEnum.values()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,12 +5,11 @@ plugins {
|
|||||||
dependencies {
|
dependencies {
|
||||||
api(project(':jadx-plugins:jadx-plugins-api'))
|
api(project(':jadx-plugins:jadx-plugins-api'))
|
||||||
|
|
||||||
implementation 'com.google.code.gson:gson:2.9.0'
|
implementation 'com.google.code.gson:gson:2.10.1'
|
||||||
implementation 'com.android.tools.build:aapt2-proto:4.2.1-7147631'
|
|
||||||
constraints {
|
// TODO: move resources decoding to separate plugin module
|
||||||
// Force protobuf version to prevent Java-7 issue
|
implementation 'com.android.tools.build:aapt2-proto:7.3.1-8691043'
|
||||||
implementation 'com.google.protobuf:protobuf-java:3.11.4'
|
implementation 'com.google.protobuf:protobuf-java:3.21.12' // forcing latest version
|
||||||
}
|
|
||||||
|
|
||||||
testImplementation 'org.apache.commons:commons-lang3:3.12.0'
|
testImplementation 'org.apache.commons:commons-lang3:3.12.0'
|
||||||
|
|
||||||
@@ -20,8 +19,8 @@ dependencies {
|
|||||||
testRuntimeOnly(project(':jadx-plugins:jadx-java-input'))
|
testRuntimeOnly(project(':jadx-plugins:jadx-java-input'))
|
||||||
testRuntimeOnly(project(':jadx-plugins:jadx-raung-input'))
|
testRuntimeOnly(project(':jadx-plugins:jadx-raung-input'))
|
||||||
|
|
||||||
testImplementation 'org.eclipse.jdt:ecj:3.29.0'
|
testImplementation 'org.eclipse.jdt:ecj:3.32.0'
|
||||||
testImplementation 'tools.profiler:async-profiler:1.8.3'
|
testImplementation 'tools.profiler:async-profiler:2.9'
|
||||||
}
|
}
|
||||||
|
|
||||||
test {
|
test {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import jadx.api.args.DeobfuscationMapFileMode;
|
import jadx.api.args.DeobfuscationMapFileMode;
|
||||||
|
import jadx.api.args.ResourceNameSource;
|
||||||
import jadx.api.data.ICodeData;
|
import jadx.api.data.ICodeData;
|
||||||
import jadx.api.impl.AnnotatedCodeWriter;
|
import jadx.api.impl.AnnotatedCodeWriter;
|
||||||
import jadx.api.impl.InMemoryCodeCache;
|
import jadx.api.impl.InMemoryCodeCache;
|
||||||
@@ -52,6 +53,7 @@ public class JadxArgs {
|
|||||||
private boolean extractFinally = true;
|
private boolean extractFinally = true;
|
||||||
private boolean inlineAnonymousClasses = true;
|
private boolean inlineAnonymousClasses = true;
|
||||||
private boolean inlineMethods = true;
|
private boolean inlineMethods = true;
|
||||||
|
private boolean allowInlineKotlinLambda = true;
|
||||||
|
|
||||||
private boolean skipResources = false;
|
private boolean skipResources = false;
|
||||||
private boolean skipSources = false;
|
private boolean skipSources = false;
|
||||||
@@ -72,6 +74,7 @@ public class JadxArgs {
|
|||||||
private File deobfuscationMapFile = null;
|
private File deobfuscationMapFile = null;
|
||||||
|
|
||||||
private DeobfuscationMapFileMode deobfuscationMapFileMode = DeobfuscationMapFileMode.READ;
|
private DeobfuscationMapFileMode deobfuscationMapFileMode = DeobfuscationMapFileMode.READ;
|
||||||
|
private ResourceNameSource resourceNameSource = ResourceNameSource.AUTO;
|
||||||
|
|
||||||
private int deobfuscationMinLength = 0;
|
private int deobfuscationMinLength = 0;
|
||||||
private int deobfuscationMaxLength = Integer.MAX_VALUE;
|
private int deobfuscationMaxLength = Integer.MAX_VALUE;
|
||||||
@@ -261,6 +264,14 @@ public class JadxArgs {
|
|||||||
this.inlineMethods = inlineMethods;
|
this.inlineMethods = inlineMethods;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isAllowInlineKotlinLambda() {
|
||||||
|
return allowInlineKotlinLambda;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAllowInlineKotlinLambda(boolean allowInlineKotlinLambda) {
|
||||||
|
this.allowInlineKotlinLambda = allowInlineKotlinLambda;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isExtractFinally() {
|
public boolean isExtractFinally() {
|
||||||
return extractFinally;
|
return extractFinally;
|
||||||
}
|
}
|
||||||
@@ -369,6 +380,14 @@ public class JadxArgs {
|
|||||||
this.deobfuscationMapFile = deobfuscationMapFile;
|
this.deobfuscationMapFile = deobfuscationMapFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ResourceNameSource getResourceNameSource() {
|
||||||
|
return resourceNameSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setResourceNameSource(ResourceNameSource resourceNameSource) {
|
||||||
|
this.resourceNameSource = resourceNameSource;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isEscapeUnicode() {
|
public boolean isEscapeUnicode() {
|
||||||
return escapeUnicode;
|
return escapeUnicode;
|
||||||
}
|
}
|
||||||
@@ -540,6 +559,7 @@ public class JadxArgs {
|
|||||||
String argStr = "args:" + decompilationMode + useImports + showInconsistentCode
|
String argStr = "args:" + decompilationMode + useImports + showInconsistentCode
|
||||||
+ inlineAnonymousClasses + inlineMethods
|
+ inlineAnonymousClasses + inlineMethods
|
||||||
+ deobfuscationOn + deobfuscationMinLength + deobfuscationMaxLength
|
+ deobfuscationOn + deobfuscationMinLength + deobfuscationMaxLength
|
||||||
|
+ resourceNameSource
|
||||||
+ parseKotlinMetadata + useKotlinMethodsForVarNames
|
+ parseKotlinMetadata + useKotlinMethodsForVarNames
|
||||||
+ insertDebugLines + extractFinally
|
+ insertDebugLines + extractFinally
|
||||||
+ debugInfo + useSourceNameAsClassAlias + escapeUnicode + replaceConsts
|
+ debugInfo + useSourceNameAsClassAlias + escapeUnicode + replaceConsts
|
||||||
@@ -564,6 +584,7 @@ public class JadxArgs {
|
|||||||
+ ", deobfuscationOn=" + deobfuscationOn
|
+ ", deobfuscationOn=" + deobfuscationOn
|
||||||
+ ", deobfuscationMapFile=" + deobfuscationMapFile
|
+ ", deobfuscationMapFile=" + deobfuscationMapFile
|
||||||
+ ", deobfuscationMapFileMode=" + deobfuscationMapFileMode
|
+ ", deobfuscationMapFileMode=" + deobfuscationMapFileMode
|
||||||
|
+ ", resourceNameSource=" + resourceNameSource
|
||||||
+ ", useSourceNameAsClassAlias=" + useSourceNameAsClassAlias
|
+ ", useSourceNameAsClassAlias=" + useSourceNameAsClassAlias
|
||||||
+ ", parseKotlinMetadata=" + parseKotlinMetadata
|
+ ", parseKotlinMetadata=" + parseKotlinMetadata
|
||||||
+ ", useKotlinMethodsForVarNames=" + useKotlinMethodsForVarNames
|
+ ", useKotlinMethodsForVarNames=" + useKotlinMethodsForVarNames
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.ThreadPoolExecutor;
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
@@ -37,8 +36,6 @@ import jadx.api.plugins.input.data.ILoadResult;
|
|||||||
import jadx.api.plugins.options.JadxPluginOptions;
|
import jadx.api.plugins.options.JadxPluginOptions;
|
||||||
import jadx.core.Jadx;
|
import jadx.core.Jadx;
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
import jadx.core.dex.attributes.AType;
|
|
||||||
import jadx.core.dex.attributes.nodes.InlinedAttr;
|
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
import jadx.core.dex.nodes.FieldNode;
|
import jadx.core.dex.nodes.FieldNode;
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
@@ -95,10 +92,6 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
private BinaryXMLParser binaryXmlParser;
|
private BinaryXMLParser binaryXmlParser;
|
||||||
private ProtoXMLParser protoXmlParser;
|
private ProtoXMLParser protoXmlParser;
|
||||||
|
|
||||||
private final Map<ClassNode, JavaClass> classesMap = new ConcurrentHashMap<>();
|
|
||||||
private final Map<MethodNode, JavaMethod> methodsMap = new ConcurrentHashMap<>();
|
|
||||||
private final Map<FieldNode, JavaField> fieldsMap = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
private final IDecompileScheduler decompileScheduler = new DecompilerScheduler();
|
private final IDecompileScheduler decompileScheduler = new DecompilerScheduler();
|
||||||
|
|
||||||
private final List<ILoadResult> customLoads = new ArrayList<>();
|
private final List<ILoadResult> customLoads = new ArrayList<>();
|
||||||
@@ -157,10 +150,6 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
resources = null;
|
resources = null;
|
||||||
binaryXmlParser = null;
|
binaryXmlParser = null;
|
||||||
protoXmlParser = null;
|
protoXmlParser = null;
|
||||||
|
|
||||||
classesMap.clear();
|
|
||||||
methodsMap.clear();
|
|
||||||
fieldsMap.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -320,9 +309,21 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
if (args.isSkipFilesSave()) {
|
if (args.isSkipFilesSave()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// process AndroidManifest.xml first to load complete resource ids table
|
||||||
|
for (ResourceFile resourceFile : getResources()) {
|
||||||
|
if (resourceFile.getType() == ResourceType.MANIFEST) {
|
||||||
|
new ResourcesSaver(outDir, resourceFile).run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Set<String> inputFileNames = args.getInputFiles().stream().map(File::getAbsolutePath).collect(Collectors.toSet());
|
Set<String> inputFileNames = args.getInputFiles().stream().map(File::getAbsolutePath).collect(Collectors.toSet());
|
||||||
for (ResourceFile resourceFile : getResources()) {
|
for (ResourceFile resourceFile : getResources()) {
|
||||||
if (resourceFile.getType() != ResourceType.ARSC
|
ResourceType resType = resourceFile.getType();
|
||||||
|
if (resType == ResourceType.MANIFEST) {
|
||||||
|
// already processed
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (resType != ResourceType.ARSC
|
||||||
&& inputFileNames.contains(resourceFile.getOriginalName())) {
|
&& inputFileNames.contains(resourceFile.getOriginalName())) {
|
||||||
// ignore resource made from input file
|
// ignore resource made from input file
|
||||||
continue;
|
continue;
|
||||||
@@ -358,8 +359,9 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
tasks.add(() -> {
|
tasks.add(() -> {
|
||||||
for (JavaClass cls : decompileBatch) {
|
for (JavaClass cls : decompileBatch) {
|
||||||
try {
|
try {
|
||||||
ICodeInfo code = cls.getCodeInfo();
|
ClassNode clsNode = cls.getClassNode();
|
||||||
SaveCode.save(outDir, cls.getClassNode(), code);
|
ICodeInfo code = clsNode.getCode();
|
||||||
|
SaveCode.save(outDir, clsNode, code);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error("Error saving class: {}", cls, e);
|
LOG.error("Error saving class: {}", cls, e);
|
||||||
}
|
}
|
||||||
@@ -392,7 +394,7 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
return Utils.collectionMap(root.getClasses(), this::convertClassNode);
|
return Utils.collectionMap(root.getClasses(), this::convertClassNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<ResourceFile> getResources() {
|
public synchronized List<ResourceFile> getResources() {
|
||||||
if (resources == null) {
|
if (resources == null) {
|
||||||
if (root == null) {
|
if (root == null) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
@@ -472,47 +474,36 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
* Get JavaClass by ClassNode without loading and decompilation
|
* Get JavaClass by ClassNode without loading and decompilation
|
||||||
*/
|
*/
|
||||||
@ApiStatus.Internal
|
@ApiStatus.Internal
|
||||||
JavaClass convertClassNode(ClassNode cls) {
|
synchronized JavaClass convertClassNode(ClassNode cls) {
|
||||||
return classesMap.compute(cls, (node, prevJavaCls) -> {
|
JavaClass javaClass = cls.getJavaNode();
|
||||||
if (prevJavaCls != null && prevJavaCls.getClassNode() == cls) {
|
if (javaClass == null) {
|
||||||
// keep previous variable
|
javaClass = cls.isInner()
|
||||||
return prevJavaCls;
|
? new JavaClass(cls, convertClassNode(cls.getParentClass()))
|
||||||
}
|
: new JavaClass(cls, this);
|
||||||
if (cls.isInner()) {
|
cls.setJavaNode(javaClass);
|
||||||
return new JavaClass(cls, convertClassNode(cls.getParentClass()));
|
}
|
||||||
}
|
return javaClass;
|
||||||
return new JavaClass(cls, this);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiStatus.Internal
|
@ApiStatus.Internal
|
||||||
JavaField convertFieldNode(FieldNode field) {
|
synchronized JavaField convertFieldNode(FieldNode fld) {
|
||||||
return fieldsMap.computeIfAbsent(field, fldNode -> {
|
JavaField javaField = fld.getJavaNode();
|
||||||
JavaClass parentCls = convertClassNode(fldNode.getParentClass());
|
if (javaField == null) {
|
||||||
return new JavaField(parentCls, fldNode);
|
JavaClass parentCls = convertClassNode(fld.getParentClass());
|
||||||
});
|
javaField = new JavaField(parentCls, fld);
|
||||||
|
fld.setJavaNode(javaField);
|
||||||
|
}
|
||||||
|
return javaField;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiStatus.Internal
|
@ApiStatus.Internal
|
||||||
JavaMethod convertMethodNode(MethodNode method) {
|
synchronized JavaMethod convertMethodNode(MethodNode mth) {
|
||||||
return methodsMap.computeIfAbsent(method, mthNode -> {
|
JavaMethod javaMethod = mth.getJavaNode();
|
||||||
ClassNode codeCls = getCodeParentClass(mthNode.getParentClass());
|
if (javaMethod == null) {
|
||||||
return new JavaMethod(convertClassNode(codeCls), mthNode);
|
javaMethod = new JavaMethod(convertClassNode(mth.getParentClass()), mth);
|
||||||
});
|
mth.setJavaNode(javaMethod);
|
||||||
}
|
|
||||||
|
|
||||||
private static ClassNode getCodeParentClass(ClassNode cls) {
|
|
||||||
ClassNode codeCls;
|
|
||||||
InlinedAttr inlinedAttr = cls.get(AType.INLINED);
|
|
||||||
if (inlinedAttr != null) {
|
|
||||||
codeCls = inlinedAttr.getInlineCls().getTopParentClass();
|
|
||||||
} else {
|
|
||||||
codeCls = cls.getTopParentClass();
|
|
||||||
}
|
}
|
||||||
if (codeCls == cls) {
|
return javaMethod;
|
||||||
return codeCls;
|
|
||||||
}
|
|
||||||
return getCodeParentClass(codeCls);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -589,14 +580,9 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private JavaVariable resolveVarNode(VarNode varNode) {
|
private JavaVariable resolveVarNode(VarNode varNode) {
|
||||||
MethodNode mthNode = varNode.getMth();
|
JavaMethod javaNode = convertMethodNode(varNode.getMth());
|
||||||
JavaMethod mth = convertMethodNode(mthNode);
|
return new JavaVariable(javaNode, varNode);
|
||||||
if (mth == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return new JavaVariable(mth, varNode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import jadx.api.metadata.ICodeNodeRef;
|
|||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
import jadx.core.dex.attributes.AType;
|
import jadx.core.dex.attributes.AType;
|
||||||
import jadx.core.dex.attributes.nodes.AnonymousClassAttr;
|
import jadx.core.dex.attributes.nodes.AnonymousClassAttr;
|
||||||
|
import jadx.core.dex.attributes.nodes.InlinedAttr;
|
||||||
import jadx.core.dex.info.AccessInfo;
|
import jadx.core.dex.info.AccessInfo;
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
import jadx.core.dex.nodes.FieldNode;
|
import jadx.core.dex.nodes.FieldNode;
|
||||||
@@ -57,7 +58,10 @@ public final class JavaClass implements JavaNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public @NotNull ICodeInfo getCodeInfo() {
|
public @NotNull ICodeInfo getCodeInfo() {
|
||||||
load();
|
ICodeInfo code = load();
|
||||||
|
if (code != null) {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
return cls.decompile();
|
return cls.decompile();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,19 +110,24 @@ public final class JavaClass implements JavaNode {
|
|||||||
/**
|
/**
|
||||||
* Decompile class and loads internal lists of fields, methods, etc.
|
* Decompile class and loads internal lists of fields, methods, etc.
|
||||||
* Do nothing if already loaded.
|
* Do nothing if already loaded.
|
||||||
|
*
|
||||||
|
* @return code info if decompilation was executed, null otherwise
|
||||||
*/
|
*/
|
||||||
@Nullable
|
private synchronized @Nullable ICodeInfo load() {
|
||||||
private synchronized void load() {
|
|
||||||
if (listsLoaded) {
|
if (listsLoaded) {
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
listsLoaded = true;
|
listsLoaded = true;
|
||||||
JadxDecompiler rootDecompiler = getRootDecompiler();
|
|
||||||
ICodeCache codeCache = rootDecompiler.getArgs().getCodeCache();
|
ICodeInfo code;
|
||||||
if (!codeCache.contains(cls.getRawName())) {
|
if (cls.getState().isProcessComplete()) {
|
||||||
cls.decompile();
|
// already decompiled -> class internals loaded
|
||||||
|
code = null;
|
||||||
|
} else {
|
||||||
|
code = cls.decompile();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JadxDecompiler rootDecompiler = getRootDecompiler();
|
||||||
int inClsCount = cls.getInnerClasses().size();
|
int inClsCount = cls.getInnerClasses().size();
|
||||||
if (inClsCount != 0) {
|
if (inClsCount != 0) {
|
||||||
List<JavaClass> list = new ArrayList<>(inClsCount);
|
List<JavaClass> list = new ArrayList<>(inClsCount);
|
||||||
@@ -164,6 +173,7 @@ public final class JavaClass implements JavaNode {
|
|||||||
mths.sort(Comparator.comparing(JavaMethod::getName));
|
mths.sort(Comparator.comparing(JavaMethod::getName));
|
||||||
this.methods = Collections.unmodifiableList(mths);
|
this.methods = Collections.unmodifiableList(mths);
|
||||||
}
|
}
|
||||||
|
return code;
|
||||||
}
|
}
|
||||||
|
|
||||||
JadxDecompiler getRootDecompiler() {
|
JadxDecompiler getRootDecompiler() {
|
||||||
@@ -242,19 +252,37 @@ public final class JavaClass implements JavaNode {
|
|||||||
return parent;
|
return parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public JavaClass getOriginalTopParentClass() {
|
||||||
public JavaClass getTopParentClass() {
|
return parent == null ? this : parent.getOriginalTopParentClass();
|
||||||
if (cls.contains(AType.ANONYMOUS_CLASS)) {
|
|
||||||
// moved to usage class
|
|
||||||
return getParentForAnonymousClass();
|
|
||||||
}
|
|
||||||
return parent == null ? this : parent.getTopParentClass();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private JavaClass getParentForAnonymousClass() {
|
/**
|
||||||
AnonymousClassAttr attr = cls.get(AType.ANONYMOUS_CLASS);
|
* Return top parent class which contains code of this class.
|
||||||
ClassNode topParentClass = attr.getOuterCls().getTopParentClass();
|
* Code parent can be different from original parent after move or inline
|
||||||
return getRootDecompiler().convertClassNode(topParentClass);
|
*
|
||||||
|
* @return this if already a top class
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public JavaClass getTopParentClass() {
|
||||||
|
JavaClass codeParent = getCodeParent();
|
||||||
|
return codeParent == null ? this : codeParent.getTopParentClass();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return parent class which contains code of this class.
|
||||||
|
* Code parent can be different for original parent after move or inline
|
||||||
|
*/
|
||||||
|
public @Nullable JavaClass getCodeParent() {
|
||||||
|
AnonymousClassAttr anonymousClsAttr = cls.get(AType.ANONYMOUS_CLASS);
|
||||||
|
if (anonymousClsAttr != null) {
|
||||||
|
// moved to usage class
|
||||||
|
return getRootDecompiler().convertClassNode(anonymousClsAttr.getOuterCls());
|
||||||
|
}
|
||||||
|
InlinedAttr inlinedAttr = cls.get(AType.INLINED);
|
||||||
|
if (inlinedAttr != null) {
|
||||||
|
return getRootDecompiler().convertClassNode(inlinedAttr.getInlineCls());
|
||||||
|
}
|
||||||
|
return parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AccessInfo getAccessInfo() {
|
public AccessInfo getAccessInfo() {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import java.util.Collections;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.jetbrains.annotations.ApiStatus;
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import jadx.api.metadata.ICodeAnnotation;
|
import jadx.api.metadata.ICodeAnnotation;
|
||||||
import jadx.api.metadata.annotations.VarNode;
|
import jadx.api.metadata.annotations.VarNode;
|
||||||
@@ -32,7 +33,7 @@ public class JavaVariable implements JavaNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public @Nullable String getName() {
|
||||||
return varNode.getName();
|
return varNode.getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package jadx.api.args;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resources original name source (for deobfuscation)
|
||||||
|
*/
|
||||||
|
public enum ResourceNameSource {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Automatically select best name (default)
|
||||||
|
*/
|
||||||
|
AUTO,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Force use resources provided names
|
||||||
|
*/
|
||||||
|
RESOURCES,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Force use resources names from R class
|
||||||
|
*/
|
||||||
|
CODE,
|
||||||
|
}
|
||||||
@@ -9,7 +9,8 @@ public interface ICodeAnnotation {
|
|||||||
VAR,
|
VAR,
|
||||||
VAR_REF,
|
VAR_REF,
|
||||||
DECLARATION,
|
DECLARATION,
|
||||||
OFFSET
|
OFFSET,
|
||||||
|
END // class or method body end
|
||||||
}
|
}
|
||||||
|
|
||||||
AnnType getAnnType();
|
AnnType getAnnType();
|
||||||
|
|||||||
@@ -32,6 +32,22 @@ public class NodeDeclareRef implements ICodeAnnotation {
|
|||||||
return AnnType.DECLARATION;
|
return AnnType.DECLARATION;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(o instanceof NodeDeclareRef)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return node.equals(((NodeDeclareRef) o).node);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return node.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "NodeDeclareRef{" + node + '}';
|
return "NodeDeclareRef{" + node + '}';
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package jadx.api.metadata.annotations;
|
||||||
|
|
||||||
|
import jadx.api.metadata.ICodeAnnotation;
|
||||||
|
|
||||||
|
public class NodeEnd implements ICodeAnnotation {
|
||||||
|
|
||||||
|
public static final NodeEnd VALUE = new NodeEnd();
|
||||||
|
|
||||||
|
private NodeEnd() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AnnType getAnnType() {
|
||||||
|
return AnnType.END;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "END";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,16 +6,14 @@ import java.util.Map;
|
|||||||
import java.util.NavigableMap;
|
import java.util.NavigableMap;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
import java.util.function.BiFunction;
|
import java.util.function.BiFunction;
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import jadx.api.metadata.ICodeAnnotation;
|
import jadx.api.metadata.ICodeAnnotation;
|
||||||
|
import jadx.api.metadata.ICodeAnnotation.AnnType;
|
||||||
import jadx.api.metadata.ICodeMetadata;
|
import jadx.api.metadata.ICodeMetadata;
|
||||||
import jadx.api.metadata.ICodeNodeRef;
|
import jadx.api.metadata.ICodeNodeRef;
|
||||||
import jadx.api.metadata.annotations.NodeDeclareRef;
|
import jadx.api.metadata.annotations.NodeDeclareRef;
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
|
||||||
import jadx.core.utils.Utils;
|
import jadx.core.utils.Utils;
|
||||||
|
|
||||||
public class CodeMetadataStorage implements ICodeMetadata {
|
public class CodeMetadataStorage implements ICodeMetadata {
|
||||||
@@ -55,7 +53,7 @@ public class CodeMetadataStorage implements ICodeMetadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @Nullable ICodeAnnotation searchUp(int position, ICodeAnnotation.AnnType annType) {
|
public @Nullable ICodeAnnotation searchUp(int position, AnnType annType) {
|
||||||
for (ICodeAnnotation v : navMap.tailMap(position, true).values()) {
|
for (ICodeAnnotation v : navMap.tailMap(position, true).values()) {
|
||||||
if (v.getAnnType() == annType) {
|
if (v.getAnnType() == annType) {
|
||||||
return v;
|
return v;
|
||||||
@@ -65,7 +63,7 @@ public class CodeMetadataStorage implements ICodeMetadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @Nullable ICodeAnnotation searchUp(int position, int limitPos, ICodeAnnotation.AnnType annType) {
|
public @Nullable ICodeAnnotation searchUp(int position, int limitPos, AnnType annType) {
|
||||||
for (ICodeAnnotation v : navMap.subMap(position, true, limitPos, true).values()) {
|
for (ICodeAnnotation v : navMap.subMap(position, true, limitPos, true).values()) {
|
||||||
if (v.getAnnType() == annType) {
|
if (v.getAnnType() == annType) {
|
||||||
return v;
|
return v;
|
||||||
@@ -99,28 +97,40 @@ public class CodeMetadataStorage implements ICodeMetadata {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ICodeNodeRef getNodeAt(int position) {
|
public ICodeNodeRef getNodeAt(int position) {
|
||||||
return navMap.tailMap(position, true)
|
int nesting = 0;
|
||||||
.values().stream()
|
for (ICodeAnnotation ann : navMap.tailMap(position, true).values()) {
|
||||||
.flatMap(CodeMetadataStorage::mapEnclosingNode)
|
switch (ann.getAnnType()) {
|
||||||
.findFirst().orElse(null);
|
case END:
|
||||||
|
nesting++;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DECLARATION:
|
||||||
|
ICodeNodeRef node = ((NodeDeclareRef) ann).getNode();
|
||||||
|
AnnType nodeType = node.getAnnType();
|
||||||
|
if (nodeType == AnnType.CLASS || nodeType == AnnType.METHOD) {
|
||||||
|
if (nesting == 0) {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
nesting--;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ICodeNodeRef getNodeBelow(int position) {
|
public ICodeNodeRef getNodeBelow(int position) {
|
||||||
return navMap.headMap(position, true).descendingMap()
|
for (ICodeAnnotation ann : navMap.headMap(position, true).descendingMap().values()) {
|
||||||
.values().stream()
|
if (ann.getAnnType() == AnnType.DECLARATION) {
|
||||||
.flatMap(CodeMetadataStorage::mapEnclosingNode)
|
ICodeNodeRef node = ((NodeDeclareRef) ann).getNode();
|
||||||
.findFirst().orElse(null);
|
AnnType nodeType = node.getAnnType();
|
||||||
}
|
if (nodeType == AnnType.CLASS || nodeType == AnnType.METHOD) {
|
||||||
|
return node;
|
||||||
private static Stream<ICodeNodeRef> mapEnclosingNode(ICodeAnnotation ann) {
|
}
|
||||||
if (ann instanceof NodeDeclareRef) {
|
|
||||||
ICodeNodeRef node = ((NodeDeclareRef) ann).getNode();
|
|
||||||
if (node instanceof ClassNode || node instanceof MethodNode) {
|
|
||||||
return Stream.of(node);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Stream.empty();
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -135,7 +145,7 @@ public class CodeMetadataStorage implements ICodeMetadata {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "CodeMetadata{lines=" + lines
|
return "CodeMetadata{\nlines=" + lines
|
||||||
+ ", annotations=\n" + Utils.listToString(navMap.entrySet(), "\n") + "\n}";
|
+ "\nannotations=\n " + Utils.listToString(navMap.descendingMap().entrySet(), "\n ") + "\n}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -191,7 +191,6 @@ public class Jadx {
|
|||||||
passes.add(new ProcessInstructionsVisitor());
|
passes.add(new ProcessInstructionsVisitor());
|
||||||
|
|
||||||
passes.add(new BlockSplitter());
|
passes.add(new BlockSplitter());
|
||||||
passes.add(new MethodVisitor(mth -> mth.add(AFlag.DISABLE_BLOCKS_LOCK)));
|
|
||||||
if (args.isRawCFGOutput()) {
|
if (args.isRawCFGOutput()) {
|
||||||
passes.add(DotGraphVisitor.dumpRaw());
|
passes.add(DotGraphVisitor.dumpRaw());
|
||||||
}
|
}
|
||||||
@@ -215,9 +214,6 @@ public class Jadx {
|
|||||||
passes.add(new CodeShrinkVisitor());
|
passes.add(new CodeShrinkVisitor());
|
||||||
passes.add(new SimplifyVisitor());
|
passes.add(new SimplifyVisitor());
|
||||||
passes.add(new MethodVisitor(mth -> mth.remove(AFlag.DONT_GENERATE)));
|
passes.add(new MethodVisitor(mth -> mth.remove(AFlag.DONT_GENERATE)));
|
||||||
if (args.isRawCFGOutput()) {
|
|
||||||
passes.add(DotGraphVisitor.dumpRaw());
|
|
||||||
}
|
|
||||||
if (args.isCfgOutput()) {
|
if (args.isCfgOutput()) {
|
||||||
passes.add(DotGraphVisitor.dump());
|
passes.add(DotGraphVisitor.dump());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import jadx.api.CommentsLevel;
|
|||||||
import jadx.api.ICodeInfo;
|
import jadx.api.ICodeInfo;
|
||||||
import jadx.api.ICodeWriter;
|
import jadx.api.ICodeWriter;
|
||||||
import jadx.api.JadxArgs;
|
import jadx.api.JadxArgs;
|
||||||
|
import jadx.api.metadata.annotations.NodeEnd;
|
||||||
import jadx.api.plugins.input.data.AccessFlags;
|
import jadx.api.plugins.input.data.AccessFlags;
|
||||||
import jadx.api.plugins.input.data.annotations.EncodedType;
|
import jadx.api.plugins.input.data.annotations.EncodedType;
|
||||||
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||||
@@ -170,7 +171,7 @@ public class ClassGen {
|
|||||||
ArgType sup = cls.getSuperClass();
|
ArgType sup = cls.getSuperClass();
|
||||||
if (sup != null
|
if (sup != null
|
||||||
&& !sup.equals(ArgType.OBJECT)
|
&& !sup.equals(ArgType.OBJECT)
|
||||||
&& !cls.isEnum()) {
|
&& !cls.contains(AFlag.REMOVE_SUPER_CLASS)) {
|
||||||
clsCode.add("extends ");
|
clsCode.add("extends ");
|
||||||
useClass(clsCode, sup);
|
useClass(clsCode, sup);
|
||||||
clsCode.add(' ');
|
clsCode.add(' ');
|
||||||
@@ -256,6 +257,7 @@ public class ClassGen {
|
|||||||
addInnerClsAndMethods(clsCode);
|
addInnerClsAndMethods(clsCode);
|
||||||
clsCode.decIndent();
|
clsCode.decIndent();
|
||||||
clsCode.startLine('}');
|
clsCode.startLine('}');
|
||||||
|
clsCode.attachAnnotation(NodeEnd.VALUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addInnerClsAndMethods(ICodeWriter clsCode) {
|
private void addInnerClsAndMethods(ICodeWriter clsCode) {
|
||||||
@@ -320,19 +322,25 @@ public class ClassGen {
|
|||||||
if (inlineAttr == null || inlineAttr.notNeeded()) {
|
if (inlineAttr == null || inlineAttr.notNeeded()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (mth.getUseIn().isEmpty()) {
|
try {
|
||||||
mth.add(AFlag.DONT_GENERATE);
|
if (mth.getUseIn().isEmpty()) {
|
||||||
return true;
|
mth.add(AFlag.DONT_GENERATE);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
List<MethodNode> useInCompleted = mth.getUseIn().stream()
|
||||||
|
.filter(m -> m.getTopParentClass().getState().isProcessComplete())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
if (useInCompleted.isEmpty()) {
|
||||||
|
mth.add(AFlag.DONT_GENERATE);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
mth.addDebugComment("Method not inlined, still used in: " + useInCompleted);
|
||||||
|
return false;
|
||||||
|
} catch (Exception e) {
|
||||||
|
// check failed => keep method
|
||||||
|
mth.addWarnComment("Failed to check method usage", e);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
List<MethodNode> useInCompleted = mth.getUseIn().stream()
|
|
||||||
.filter(m -> m.getTopParentClass().getState().isProcessComplete())
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
if (useInCompleted.isEmpty()) {
|
|
||||||
mth.add(AFlag.DONT_GENERATE);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
mth.addDebugComment("Method not inlined, still used in: " + useInCompleted);
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isMethodsPresents() {
|
private boolean isMethodsPresents() {
|
||||||
@@ -369,6 +377,7 @@ public class ClassGen {
|
|||||||
mthGen.addInstructions(code);
|
mthGen.addInstructions(code);
|
||||||
code.decIndent();
|
code.decIndent();
|
||||||
code.startLine('}');
|
code.startLine('}');
|
||||||
|
code.attachAnnotation(NodeEnd.VALUE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package jadx.core.codegen;
|
package jadx.core.codegen;
|
||||||
|
|
||||||
|
import java.util.ArrayDeque;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
|
|
||||||
import jadx.api.ICodeWriter;
|
import jadx.api.ICodeWriter;
|
||||||
@@ -23,7 +23,7 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
|
|||||||
public class ConditionGen extends InsnGen {
|
public class ConditionGen extends InsnGen {
|
||||||
|
|
||||||
private static class CondStack {
|
private static class CondStack {
|
||||||
private final Queue<IfCondition> stack = new LinkedList<>();
|
private final Queue<IfCondition> stack = new ArrayDeque<>();
|
||||||
|
|
||||||
public Queue<IfCondition> getStack() {
|
public Queue<IfCondition> getStack() {
|
||||||
return stack;
|
return stack;
|
||||||
|
|||||||
@@ -14,8 +14,10 @@ import jadx.api.ICodeWriter;
|
|||||||
import jadx.api.metadata.annotations.InsnCodeOffset;
|
import jadx.api.metadata.annotations.InsnCodeOffset;
|
||||||
import jadx.api.metadata.annotations.VarNode;
|
import jadx.api.metadata.annotations.VarNode;
|
||||||
import jadx.api.plugins.input.data.MethodHandleType;
|
import jadx.api.plugins.input.data.MethodHandleType;
|
||||||
|
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
import jadx.core.dex.attributes.AType;
|
import jadx.core.dex.attributes.AType;
|
||||||
|
import jadx.core.dex.attributes.FieldInitInsnAttr;
|
||||||
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
|
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
|
||||||
import jadx.core.dex.attributes.nodes.GenericInfoAttr;
|
import jadx.core.dex.attributes.nodes.GenericInfoAttr;
|
||||||
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
||||||
@@ -36,6 +38,7 @@ import jadx.core.dex.instructions.IfNode;
|
|||||||
import jadx.core.dex.instructions.IndexInsnNode;
|
import jadx.core.dex.instructions.IndexInsnNode;
|
||||||
import jadx.core.dex.instructions.InsnType;
|
import jadx.core.dex.instructions.InsnType;
|
||||||
import jadx.core.dex.instructions.InvokeCustomNode;
|
import jadx.core.dex.instructions.InvokeCustomNode;
|
||||||
|
import jadx.core.dex.instructions.InvokeCustomRawNode;
|
||||||
import jadx.core.dex.instructions.InvokeNode;
|
import jadx.core.dex.instructions.InvokeNode;
|
||||||
import jadx.core.dex.instructions.InvokeType;
|
import jadx.core.dex.instructions.InvokeType;
|
||||||
import jadx.core.dex.instructions.NewArrayNode;
|
import jadx.core.dex.instructions.NewArrayNode;
|
||||||
@@ -208,7 +211,31 @@ public class InsnGen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void staticField(ICodeWriter code, FieldInfo field) throws CodegenException {
|
||||||
|
FieldNode fieldNode = root.resolveField(field);
|
||||||
|
if (fieldNode != null
|
||||||
|
&& fieldNode.contains(AFlag.INLINE_INSTANCE_FIELD)
|
||||||
|
&& fieldNode.getParentClass().contains(AType.ANONYMOUS_CLASS)) {
|
||||||
|
FieldInitInsnAttr initInsnAttr = fieldNode.get(AType.FIELD_INIT_INSN);
|
||||||
|
if (initInsnAttr != null) {
|
||||||
|
InsnNode insn = initInsnAttr.getInsn();
|
||||||
|
if (insn instanceof ConstructorInsn) {
|
||||||
|
fieldNode.add(AFlag.DONT_GENERATE);
|
||||||
|
inlineAnonymousConstructor(code, fieldNode.getParentClass(), (ConstructorInsn) insn);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
makeStaticFieldAccess(code, field, fieldNode, mgen.getClassGen());
|
||||||
|
}
|
||||||
|
|
||||||
public static void makeStaticFieldAccess(ICodeWriter code, FieldInfo field, ClassGen clsGen) {
|
public static void makeStaticFieldAccess(ICodeWriter code, FieldInfo field, ClassGen clsGen) {
|
||||||
|
FieldNode fieldNode = clsGen.getClassNode().root().resolveField(field);
|
||||||
|
makeStaticFieldAccess(code, field, fieldNode, clsGen);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void makeStaticFieldAccess(ICodeWriter code,
|
||||||
|
FieldInfo field, @Nullable FieldNode fieldNode, ClassGen clsGen) {
|
||||||
ClassInfo declClass = field.getDeclClass();
|
ClassInfo declClass = field.getDeclClass();
|
||||||
// TODO
|
// TODO
|
||||||
boolean fieldFromThisClass = clsGen.getClassNode().getClassInfo().equals(declClass);
|
boolean fieldFromThisClass = clsGen.getClassNode().getClassInfo().equals(declClass);
|
||||||
@@ -219,7 +246,6 @@ public class InsnGen {
|
|||||||
}
|
}
|
||||||
code.add('.');
|
code.add('.');
|
||||||
}
|
}
|
||||||
FieldNode fieldNode = clsGen.getClassNode().root().resolveField(field);
|
|
||||||
if (fieldNode != null) {
|
if (fieldNode != null) {
|
||||||
code.attachAnnotation(fieldNode);
|
code.attachAnnotation(fieldNode);
|
||||||
}
|
}
|
||||||
@@ -230,10 +256,6 @@ public class InsnGen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void staticField(ICodeWriter code, FieldInfo field) {
|
|
||||||
makeStaticFieldAccess(code, field, mgen.getClassGen());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void useClass(ICodeWriter code, ArgType type) {
|
public void useClass(ICodeWriter code, ArgType type) {
|
||||||
mgen.getClassGen().useClass(code, type);
|
mgen.getClassGen().useClass(code, type);
|
||||||
}
|
}
|
||||||
@@ -693,9 +715,7 @@ public class InsnGen {
|
|||||||
private void makeConstructor(ConstructorInsn insn, ICodeWriter code) throws CodegenException {
|
private void makeConstructor(ConstructorInsn insn, ICodeWriter code) throws CodegenException {
|
||||||
ClassNode cls = mth.root().resolveClass(insn.getClassType());
|
ClassNode cls = mth.root().resolveClass(insn.getClassType());
|
||||||
if (cls != null && cls.isAnonymous() && !fallback) {
|
if (cls != null && cls.isAnonymous() && !fallback) {
|
||||||
cls.ensureProcessed();
|
|
||||||
inlineAnonymousConstructor(code, cls, insn);
|
inlineAnonymousConstructor(code, cls, insn);
|
||||||
mth.getParentClass().addInlinedClass(cls);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (insn.isSelf()) {
|
if (insn.isSelf()) {
|
||||||
@@ -746,6 +766,7 @@ public class InsnGen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void inlineAnonymousConstructor(ICodeWriter code, ClassNode cls, ConstructorInsn insn) throws CodegenException {
|
private void inlineAnonymousConstructor(ICodeWriter code, ClassNode cls, ConstructorInsn insn) throws CodegenException {
|
||||||
|
cls.ensureProcessed();
|
||||||
if (this.mth.getParentClass() == cls) {
|
if (this.mth.getParentClass() == cls) {
|
||||||
cls.remove(AType.ANONYMOUS_CLASS);
|
cls.remove(AType.ANONYMOUS_CLASS);
|
||||||
cls.remove(AFlag.DONT_GENERATE);
|
cls.remove(AFlag.DONT_GENERATE);
|
||||||
@@ -761,6 +782,7 @@ public class InsnGen {
|
|||||||
ctor.add(AFlag.DONT_GENERATE);
|
ctor.add(AFlag.DONT_GENERATE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
code.attachDefinition(cls);
|
||||||
code.add("new ");
|
code.add("new ");
|
||||||
useClass(code, parent);
|
useClass(code, parent);
|
||||||
MethodNode callMth = mth.root().resolveMethod(insn.getCallMth());
|
MethodNode callMth = mth.root().resolveMethod(insn.getCallMth());
|
||||||
@@ -783,6 +805,8 @@ public class InsnGen {
|
|||||||
ClassGen classGen = new ClassGen(cls, mgen.getClassGen().getParentGen());
|
ClassGen classGen = new ClassGen(cls, mgen.getClassGen().getParentGen());
|
||||||
classGen.setOuterNameGen(mgen.getNameGen());
|
classGen.setOuterNameGen(mgen.getNameGen());
|
||||||
classGen.addClassBody(code, true);
|
classGen.addClassBody(code, true);
|
||||||
|
|
||||||
|
mth.getParentClass().addInlinedClass(cls);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void makeInvoke(InvokeNode insn, ICodeWriter code) throws CodegenException {
|
private void makeInvoke(InvokeNode insn, ICodeWriter code) throws CodegenException {
|
||||||
@@ -794,11 +818,23 @@ public class InsnGen {
|
|||||||
MethodInfo callMth = insn.getCallMth();
|
MethodInfo callMth = insn.getCallMth();
|
||||||
MethodNode callMthNode = mth.root().resolveMethod(callMth);
|
MethodNode callMthNode = mth.root().resolveMethod(callMth);
|
||||||
|
|
||||||
|
if (type == InvokeType.CUSTOM_RAW) {
|
||||||
|
makeInvokeCustomRaw((InvokeCustomRawNode) insn, callMthNode, code);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (insn.isPolymorphicCall()) {
|
||||||
|
// add missing cast
|
||||||
|
code.add('(');
|
||||||
|
useType(code, callMth.getReturnType());
|
||||||
|
code.add(") ");
|
||||||
|
}
|
||||||
|
|
||||||
int k = 0;
|
int k = 0;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case DIRECT:
|
case DIRECT:
|
||||||
case VIRTUAL:
|
case VIRTUAL:
|
||||||
case INTERFACE:
|
case INTERFACE:
|
||||||
|
case POLYMORPHIC:
|
||||||
InsnArg arg = insn.getArg(0);
|
InsnArg arg = insn.getArg(0);
|
||||||
if (needInvokeArg(arg)) {
|
if (needInvokeArg(arg)) {
|
||||||
addArgDot(code, arg);
|
addArgDot(code, arg);
|
||||||
@@ -823,13 +859,44 @@ public class InsnGen {
|
|||||||
}
|
}
|
||||||
if (callMthNode != null) {
|
if (callMthNode != null) {
|
||||||
code.attachAnnotation(callMthNode);
|
code.attachAnnotation(callMthNode);
|
||||||
code.add(callMthNode.getAlias());
|
}
|
||||||
|
if (insn.contains(AFlag.FORCE_RAW_NAME)) {
|
||||||
|
code.add(callMth.getName());
|
||||||
} else {
|
} else {
|
||||||
code.add(callMth.getAlias());
|
if (callMthNode != null) {
|
||||||
|
code.add(callMthNode.getAlias());
|
||||||
|
} else {
|
||||||
|
code.add(callMth.getAlias());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
generateMethodArguments(code, insn, k, callMthNode);
|
generateMethodArguments(code, insn, k, callMthNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void makeInvokeCustomRaw(InvokeCustomRawNode insn,
|
||||||
|
@Nullable MethodNode callMthNode, ICodeWriter code) throws CodegenException {
|
||||||
|
if (isFallback()) {
|
||||||
|
code.add("call_site(");
|
||||||
|
code.incIndent();
|
||||||
|
for (EncodedValue value : insn.getCallSiteValues()) {
|
||||||
|
code.startLine(value.toString());
|
||||||
|
}
|
||||||
|
code.decIndent();
|
||||||
|
code.startLine(").invoke");
|
||||||
|
generateMethodArguments(code, insn, 0, callMthNode);
|
||||||
|
} else {
|
||||||
|
ArgType returnType = insn.getCallMth().getReturnType();
|
||||||
|
if (!returnType.isVoid()) {
|
||||||
|
code.add('(');
|
||||||
|
useType(code, returnType);
|
||||||
|
code.add(") ");
|
||||||
|
}
|
||||||
|
makeInvoke(insn.getResolveInvoke(), code);
|
||||||
|
code.add(".dynamicInvoker().invoke");
|
||||||
|
generateMethodArguments(code, insn, 0, callMthNode);
|
||||||
|
code.add(" /* invoke-custom */");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// FIXME: add 'this' for equals methods in scope
|
// FIXME: add 'this' for equals methods in scope
|
||||||
private boolean needInvokeArg(InsnArg arg) {
|
private boolean needInvokeArg(InsnArg arg) {
|
||||||
if (arg.isAnyThis()) {
|
if (arg.isAnyThis()) {
|
||||||
@@ -947,9 +1014,10 @@ public class InsnGen {
|
|||||||
// force set external arg names into call method args
|
// force set external arg names into call method args
|
||||||
int extArgsCount = customNode.getArgsCount();
|
int extArgsCount = customNode.getArgsCount();
|
||||||
int startArg = customNode.getHandleType() == MethodHandleType.INVOKE_STATIC ? 0 : 1; // skip 'this' arg
|
int startArg = customNode.getHandleType() == MethodHandleType.INVOKE_STATIC ? 0 : 1; // skip 'this' arg
|
||||||
|
int callArg = 0;
|
||||||
for (int i = startArg; i < extArgsCount; i++) {
|
for (int i = startArg; i < extArgsCount; i++) {
|
||||||
RegisterArg extArg = (RegisterArg) customNode.getArg(i);
|
RegisterArg extArg = (RegisterArg) customNode.getArg(i);
|
||||||
RegisterArg callRegArg = callArgs.get(i);
|
RegisterArg callRegArg = callArgs.get(callArg++);
|
||||||
callRegArg.getSVar().setCodeVar(extArg.getSVar().getCodeVar());
|
callRegArg.getSVar().setCodeVar(extArg.getSVar().getCodeVar());
|
||||||
}
|
}
|
||||||
code.add(" -> {");
|
code.add(" -> {");
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import jadx.core.dex.attributes.AType;
|
|||||||
import jadx.core.dex.attributes.nodes.JadxError;
|
import jadx.core.dex.attributes.nodes.JadxError;
|
||||||
import jadx.core.dex.attributes.nodes.JumpInfo;
|
import jadx.core.dex.attributes.nodes.JumpInfo;
|
||||||
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
|
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
|
||||||
|
import jadx.core.dex.attributes.nodes.MethodReplaceAttr;
|
||||||
import jadx.core.dex.info.AccessInfo;
|
import jadx.core.dex.info.AccessInfo;
|
||||||
import jadx.core.dex.instructions.ConstStringNode;
|
import jadx.core.dex.instructions.ConstStringNode;
|
||||||
import jadx.core.dex.instructions.IfNode;
|
import jadx.core.dex.instructions.IfNode;
|
||||||
@@ -144,8 +145,9 @@ public class MethodGen {
|
|||||||
} else {
|
} else {
|
||||||
classGen.useType(code, mth.getReturnType());
|
classGen.useType(code, mth.getReturnType());
|
||||||
code.add(' ');
|
code.add(' ');
|
||||||
code.attachDefinition(mth);
|
MethodNode defMth = getMethodForDefinition();
|
||||||
code.add(mth.getAlias());
|
code.attachDefinition(defMth);
|
||||||
|
code.add(defMth.getAlias());
|
||||||
}
|
}
|
||||||
code.add('(');
|
code.add('(');
|
||||||
|
|
||||||
@@ -178,6 +180,14 @@ public class MethodGen {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private MethodNode getMethodForDefinition() {
|
||||||
|
MethodReplaceAttr replaceAttr = mth.get(AType.METHOD_REPLACE);
|
||||||
|
if (replaceAttr != null) {
|
||||||
|
return replaceAttr.getReplaceMth();
|
||||||
|
}
|
||||||
|
return mth;
|
||||||
|
}
|
||||||
|
|
||||||
private void addOverrideAnnotation(ICodeWriter code, MethodNode mth) {
|
private void addOverrideAnnotation(ICodeWriter code, MethodNode mth) {
|
||||||
MethodOverrideAttr overrideAttr = mth.get(AType.METHOD_OVERRIDE);
|
MethodOverrideAttr overrideAttr = mth.get(AType.METHOD_OVERRIDE);
|
||||||
if (overrideAttr == null) {
|
if (overrideAttr == null) {
|
||||||
|
|||||||
@@ -270,7 +270,7 @@ public class RegionGen extends InsnGen {
|
|||||||
code.startLine('}');
|
code.startLine('}');
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addCaseKey(ICodeWriter code, InsnArg arg, Object k) {
|
private void addCaseKey(ICodeWriter code, InsnArg arg, Object k) throws CodegenException {
|
||||||
if (k instanceof FieldNode) {
|
if (k instanceof FieldNode) {
|
||||||
FieldNode fn = (FieldNode) k;
|
FieldNode fn = (FieldNode) k;
|
||||||
if (fn.getParentClass().isEnum()) {
|
if (fn.getParentClass().isEnum()) {
|
||||||
|
|||||||
@@ -53,7 +53,9 @@ public class SimpleModeHelper {
|
|||||||
startLabel.set(block.getId());
|
startLabel.set(block.getId());
|
||||||
} else if (predsCount == 1 && prev != null) {
|
} else if (predsCount == 1 && prev != null) {
|
||||||
if (!prev.equals(preds.get(0))) {
|
if (!prev.equals(preds.get(0))) {
|
||||||
startLabel.set(block.getId());
|
if (!block.contains(AFlag.EXC_BOTTOM_SPLITTER)) {
|
||||||
|
startLabel.set(block.getId());
|
||||||
|
}
|
||||||
if (prev.getSuccessors().size() == 1 && !mth.isPreExitBlocks(prev)) {
|
if (prev.getSuccessors().size() == 1 && !mth.isPreExitBlocks(prev)) {
|
||||||
endGoto.set(prev.getId());
|
endGoto.set(prev.getId());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -428,7 +428,9 @@ public class Deobfuscator {
|
|||||||
return "Enum";
|
return "Enum";
|
||||||
}
|
}
|
||||||
String result = "";
|
String result = "";
|
||||||
if (cls.getAccessFlags().isAbstract()) {
|
if (cls.getAccessFlags().isInterface()) {
|
||||||
|
result += "Interface";
|
||||||
|
} else if (cls.getAccessFlags().isAbstract()) {
|
||||||
result += "Abstract";
|
result += "Abstract";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,10 +21,13 @@ public enum AFlag {
|
|||||||
DONT_GENERATE, // process as usual, but don't output to generated code
|
DONT_GENERATE, // process as usual, but don't output to generated code
|
||||||
COMMENT_OUT, // process as usual, but comment insn in generated code
|
COMMENT_OUT, // process as usual, but comment insn in generated code
|
||||||
REMOVE, // can be completely removed
|
REMOVE, // can be completely removed
|
||||||
|
REMOVE_SUPER_CLASS, // don't add super class
|
||||||
|
|
||||||
HIDDEN, // instruction used inside other instruction but not listed in args
|
HIDDEN, // instruction used inside other instruction but not listed in args
|
||||||
|
|
||||||
DONT_RENAME, // do not rename during deobfuscation
|
DONT_RENAME, // do not rename during deobfuscation
|
||||||
|
FORCE_RAW_NAME, // force use of raw name instead alias
|
||||||
|
|
||||||
ADDED_TO_REGION,
|
ADDED_TO_REGION,
|
||||||
|
|
||||||
EXC_TOP_SPLITTER,
|
EXC_TOP_SPLITTER,
|
||||||
@@ -34,7 +37,9 @@ public enum AFlag {
|
|||||||
SKIP_FIRST_ARG,
|
SKIP_FIRST_ARG,
|
||||||
SKIP_ARG, // skip argument in invoke call
|
SKIP_ARG, // skip argument in invoke call
|
||||||
NO_SKIP_ARGS,
|
NO_SKIP_ARGS,
|
||||||
|
|
||||||
ANONYMOUS_CONSTRUCTOR,
|
ANONYMOUS_CONSTRUCTOR,
|
||||||
|
INLINE_INSTANCE_FIELD,
|
||||||
|
|
||||||
THIS,
|
THIS,
|
||||||
SUPER,
|
SUPER,
|
||||||
|
|||||||
@@ -7,12 +7,19 @@ import jadx.core.dex.nodes.ClassNode;
|
|||||||
|
|
||||||
public class AnonymousClassAttr extends PinnedAttribute {
|
public class AnonymousClassAttr extends PinnedAttribute {
|
||||||
|
|
||||||
|
public enum InlineType {
|
||||||
|
CONSTRUCTOR,
|
||||||
|
INSTANCE_FIELD,
|
||||||
|
}
|
||||||
|
|
||||||
private final ClassNode outerCls;
|
private final ClassNode outerCls;
|
||||||
private final ArgType baseType;
|
private final ArgType baseType;
|
||||||
|
private final InlineType inlineType;
|
||||||
|
|
||||||
public AnonymousClassAttr(ClassNode outerCls, ArgType baseType) {
|
public AnonymousClassAttr(ClassNode outerCls, ArgType baseType, InlineType inlineType) {
|
||||||
this.outerCls = outerCls;
|
this.outerCls = outerCls;
|
||||||
this.baseType = baseType;
|
this.baseType = baseType;
|
||||||
|
this.inlineType = inlineType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ClassNode getOuterCls() {
|
public ClassNode getOuterCls() {
|
||||||
@@ -23,6 +30,10 @@ public class AnonymousClassAttr extends PinnedAttribute {
|
|||||||
return baseType;
|
return baseType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public InlineType getInlineType() {
|
||||||
|
return inlineType;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AType<AnonymousClassAttr> getAttrType() {
|
public AType<AnonymousClassAttr> getAttrType() {
|
||||||
return AType.ANONYMOUS_CLASS;
|
return AType.ANONYMOUS_CLASS;
|
||||||
@@ -30,6 +41,6 @@ public class AnonymousClassAttr extends PinnedAttribute {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "AnonymousClass{" + outerCls + ", base: " + baseType + '}';
|
return "AnonymousClass{" + outerCls + ", base: " + baseType + ", inline type: " + inlineType + '}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,6 +91,19 @@ public class LoopInfo {
|
|||||||
this.parentLoop = parentLoop;
|
this.parentLoop = parentLoop;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean hasParent(LoopInfo searchLoop) {
|
||||||
|
LoopInfo parent = parentLoop;
|
||||||
|
while (true) {
|
||||||
|
if (parent == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (parent == searchLoop) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
parent = parent.getParentLoop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "LOOP:" + id + ": " + start + "->" + end;
|
return "LOOP:" + id + ": " + start + "->" + end;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package jadx.core.dex.attributes.nodes;
|
package jadx.core.dex.attributes.nodes;
|
||||||
|
|
||||||
import java.util.LinkedList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import jadx.api.ICodeWriter;
|
import jadx.api.ICodeWriter;
|
||||||
@@ -10,7 +10,7 @@ import jadx.core.dex.instructions.PhiInsn;
|
|||||||
|
|
||||||
public class PhiListAttr implements IJadxAttribute {
|
public class PhiListAttr implements IJadxAttribute {
|
||||||
|
|
||||||
private final List<PhiInsn> list = new LinkedList<>();
|
private final List<PhiInsn> list = new ArrayList<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AType<PhiListAttr> getAttrType() {
|
public AType<PhiListAttr> getAttrType() {
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package jadx.core.dex.info;
|
package jadx.core.dex.info;
|
||||||
|
|
||||||
|
import org.intellij.lang.annotations.MagicConstant;
|
||||||
|
|
||||||
import jadx.api.plugins.input.data.AccessFlags;
|
import jadx.api.plugins.input.data.AccessFlags;
|
||||||
import jadx.core.Consts;
|
import jadx.core.Consts;
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
@@ -20,10 +22,21 @@ public class AccessInfo {
|
|||||||
this.type = type;
|
this.type = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MagicConstant(valuesFromClass = AccessFlags.class)
|
||||||
public boolean containsFlag(int flag) {
|
public boolean containsFlag(int flag) {
|
||||||
return (accFlags & flag) != 0;
|
return (accFlags & flag) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MagicConstant(valuesFromClass = AccessFlags.class)
|
||||||
|
public boolean containsFlags(int... flags) {
|
||||||
|
for (int flag : flags) {
|
||||||
|
if ((accFlags & flag) == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public AccessInfo remove(int flag) {
|
public AccessInfo remove(int flag) {
|
||||||
if (containsFlag(flag)) {
|
if (containsFlag(flag)) {
|
||||||
return new AccessInfo(accFlags & ~flag, type);
|
return new AccessInfo(accFlags & ~flag, type);
|
||||||
|
|||||||
@@ -82,16 +82,24 @@ public class ConstStorage {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (FieldNode f : staticFields) {
|
for (FieldNode f : staticFields) {
|
||||||
AccessInfo accFlags = f.getAccessFlags();
|
Object value = getFieldConstValue(f);
|
||||||
if (accFlags.isStatic() && accFlags.isFinal()) {
|
if (value != null) {
|
||||||
EncodedValue constVal = f.get(JadxAttrType.CONSTANT_VALUE);
|
addConstField(cls, f, value, f.getAccessFlags().isPublic());
|
||||||
if (constVal != null && constVal.getValue() != null) {
|
|
||||||
addConstField(cls, f, constVal.getValue(), accFlags.isPublic());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static @Nullable Object getFieldConstValue(FieldNode fld) {
|
||||||
|
AccessInfo accFlags = fld.getAccessFlags();
|
||||||
|
if (accFlags.isStatic() && accFlags.isFinal()) {
|
||||||
|
EncodedValue constVal = fld.get(JadxAttrType.CONSTANT_VALUE);
|
||||||
|
if (constVal != null) {
|
||||||
|
return constVal.getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public void removeForClass(ClassNode cls) {
|
public void removeForClass(ClassNode cls) {
|
||||||
classes.remove(cls);
|
classes.remove(cls);
|
||||||
globalValues.removeForCls(cls);
|
globalValues.removeForCls(cls);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package jadx.core.dex.instructions;
|
package jadx.core.dex.instructions;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
@@ -7,6 +8,7 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import jadx.api.plugins.input.data.ICodeReader;
|
import jadx.api.plugins.input.data.ICodeReader;
|
||||||
|
import jadx.api.plugins.input.data.IMethodProto;
|
||||||
import jadx.api.plugins.input.data.IMethodRef;
|
import jadx.api.plugins.input.data.IMethodRef;
|
||||||
import jadx.api.plugins.input.insns.InsnData;
|
import jadx.api.plugins.input.insns.InsnData;
|
||||||
import jadx.api.plugins.input.insns.custom.IArrayPayload;
|
import jadx.api.plugins.input.insns.custom.IArrayPayload;
|
||||||
@@ -25,6 +27,7 @@ import jadx.core.dex.nodes.FieldNode;
|
|||||||
import jadx.core.dex.nodes.InsnNode;
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
import jadx.core.dex.nodes.RootNode;
|
import jadx.core.dex.nodes.RootNode;
|
||||||
|
import jadx.core.utils.Utils;
|
||||||
import jadx.core.utils.exceptions.DecodeException;
|
import jadx.core.utils.exceptions.DecodeException;
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
import jadx.core.utils.input.InsnDataUtils;
|
import jadx.core.utils.input.InsnDataUtils;
|
||||||
@@ -440,6 +443,8 @@ public class InsnDecoder {
|
|||||||
return invokeCustom(insn, false);
|
return invokeCustom(insn, false);
|
||||||
case INVOKE_SPECIAL:
|
case INVOKE_SPECIAL:
|
||||||
return invokeSpecial(insn);
|
return invokeSpecial(insn);
|
||||||
|
case INVOKE_POLYMORPHIC:
|
||||||
|
return invokePolymorphic(insn, false);
|
||||||
|
|
||||||
case INVOKE_DIRECT_RANGE:
|
case INVOKE_DIRECT_RANGE:
|
||||||
return invoke(insn, InvokeType.DIRECT, true);
|
return invoke(insn, InvokeType.DIRECT, true);
|
||||||
@@ -451,6 +456,8 @@ public class InsnDecoder {
|
|||||||
return invoke(insn, InvokeType.VIRTUAL, true);
|
return invoke(insn, InvokeType.VIRTUAL, true);
|
||||||
case INVOKE_CUSTOM_RANGE:
|
case INVOKE_CUSTOM_RANGE:
|
||||||
return invokeCustom(insn, true);
|
return invokeCustom(insn, true);
|
||||||
|
case INVOKE_POLYMORPHIC_RANGE:
|
||||||
|
return invokePolymorphic(insn, true);
|
||||||
|
|
||||||
case NEW_INSTANCE:
|
case NEW_INSTANCE:
|
||||||
ArgType clsType = ArgType.parse(insn.getIndexAsType());
|
ArgType clsType = ArgType.parse(insn.getIndexAsType());
|
||||||
@@ -581,6 +588,22 @@ public class InsnDecoder {
|
|||||||
return InvokeCustomBuilder.build(method, insn, isRange);
|
return InvokeCustomBuilder.build(method, insn, isRange);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private InsnNode invokePolymorphic(InsnData insn, boolean isRange) {
|
||||||
|
IMethodRef mthRef = InsnDataUtils.getMethodRef(insn);
|
||||||
|
if (mthRef == null) {
|
||||||
|
throw new JadxRuntimeException("Failed to load method reference for insn: " + insn);
|
||||||
|
}
|
||||||
|
MethodInfo callMth = MethodInfo.fromRef(root, mthRef);
|
||||||
|
IMethodProto proto = insn.getIndexAsProto(insn.getTarget());
|
||||||
|
|
||||||
|
// expand call args
|
||||||
|
List<ArgType> args = Utils.collectionMap(proto.getArgTypes(), ArgType::parse);
|
||||||
|
ArgType returnType = ArgType.parse(proto.getReturnType());
|
||||||
|
MethodInfo effectiveCallMth = MethodInfo.fromDetails(root, callMth.getDeclClass(),
|
||||||
|
callMth.getName(), args, returnType);
|
||||||
|
return new InvokePolymorphicNode(effectiveCallMth, insn, proto, callMth, isRange);
|
||||||
|
}
|
||||||
|
|
||||||
private InsnNode invokeSpecial(InsnData insn) {
|
private InsnNode invokeSpecial(InsnData insn) {
|
||||||
IMethodRef mthRef = InsnDataUtils.getMethodRef(insn);
|
IMethodRef mthRef = InsnDataUtils.getMethodRef(insn);
|
||||||
if (mthRef == null) {
|
if (mthRef == null) {
|
||||||
|
|||||||
@@ -5,10 +5,15 @@ import java.util.List;
|
|||||||
import jadx.api.plugins.input.data.ICallSite;
|
import jadx.api.plugins.input.data.ICallSite;
|
||||||
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||||
import jadx.api.plugins.input.insns.InsnData;
|
import jadx.api.plugins.input.insns.InsnData;
|
||||||
|
import jadx.core.dex.attributes.AFlag;
|
||||||
|
import jadx.core.dex.attributes.AType;
|
||||||
|
import jadx.core.dex.attributes.nodes.JadxError;
|
||||||
import jadx.core.dex.instructions.invokedynamic.CustomLambdaCall;
|
import jadx.core.dex.instructions.invokedynamic.CustomLambdaCall;
|
||||||
|
import jadx.core.dex.instructions.invokedynamic.CustomRawCall;
|
||||||
import jadx.core.dex.instructions.invokedynamic.CustomStringConcat;
|
import jadx.core.dex.instructions.invokedynamic.CustomStringConcat;
|
||||||
import jadx.core.dex.nodes.InsnNode;
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
|
import jadx.core.utils.Utils;
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
import jadx.core.utils.input.InsnDataUtils;
|
import jadx.core.utils.input.InsnDataUtils;
|
||||||
|
|
||||||
@@ -28,8 +33,16 @@ public class InvokeCustomBuilder {
|
|||||||
if (CustomStringConcat.isStringConcat(values)) {
|
if (CustomStringConcat.isStringConcat(values)) {
|
||||||
return CustomStringConcat.buildStringConcat(insn, isRange, values);
|
return CustomStringConcat.buildStringConcat(insn, isRange, values);
|
||||||
}
|
}
|
||||||
// TODO: output raw dynamic call
|
try {
|
||||||
throw new JadxRuntimeException("Failed to process invoke-custom instruction: " + callSite);
|
return CustomRawCall.build(mth, insn, isRange, values);
|
||||||
|
} catch (Exception e) {
|
||||||
|
mth.addWarn("Failed to decode invoke-custom: \n" + Utils.listToString(values, "\n")
|
||||||
|
+ ",\n exception: " + Utils.getStackTrace(e));
|
||||||
|
InsnNode nop = new InsnNode(InsnType.NOP, 0);
|
||||||
|
nop.add(AFlag.SYNTHETIC);
|
||||||
|
nop.addAttr(AType.JADX_ERROR, new JadxError("Failed to decode invoke-custom: " + values, e));
|
||||||
|
return nop;
|
||||||
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new JadxRuntimeException("'invoke-custom' instruction processing error: " + e.getMessage(), e);
|
throw new JadxRuntimeException("'invoke-custom' instruction processing error: " + e.getMessage(), e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,98 @@
|
|||||||
|
package jadx.core.dex.instructions;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||||
|
import jadx.api.plugins.input.insns.InsnData;
|
||||||
|
import jadx.core.dex.info.MethodInfo;
|
||||||
|
import jadx.core.dex.instructions.args.InsnArg;
|
||||||
|
import jadx.core.dex.instructions.invokedynamic.CustomRawCall;
|
||||||
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
|
import jadx.core.utils.InsnUtils;
|
||||||
|
import jadx.core.utils.Utils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Information for raw invoke-custom instruction.<br>
|
||||||
|
* Output will be formatted as polymorphic call with equivalent semantic
|
||||||
|
* Contains two parts:
|
||||||
|
* - resolve: treated as additional invoke insn (uses only constant args)
|
||||||
|
* - invoke: call of resolved method (base for this invoke)
|
||||||
|
* <br>
|
||||||
|
* See {@link CustomRawCall} class for build details
|
||||||
|
*/
|
||||||
|
public class InvokeCustomRawNode extends InvokeNode {
|
||||||
|
private final InvokeNode resolve;
|
||||||
|
private List<EncodedValue> callSiteValues;
|
||||||
|
|
||||||
|
public InvokeCustomRawNode(InvokeNode resolve, MethodInfo mthInfo, InsnData insn, boolean isRange) {
|
||||||
|
super(mthInfo, insn, InvokeType.CUSTOM_RAW, false, isRange);
|
||||||
|
this.resolve = resolve;
|
||||||
|
}
|
||||||
|
|
||||||
|
public InvokeCustomRawNode(InvokeNode resolve, MethodInfo mthInfo, InvokeType invokeType, int argsCount) {
|
||||||
|
super(mthInfo, invokeType, argsCount);
|
||||||
|
this.resolve = resolve;
|
||||||
|
}
|
||||||
|
|
||||||
|
public InvokeNode getResolveInvoke() {
|
||||||
|
return resolve;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCallSiteValues(List<EncodedValue> callSiteValues) {
|
||||||
|
this.callSiteValues = callSiteValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<EncodedValue> getCallSiteValues() {
|
||||||
|
return callSiteValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InsnNode copy() {
|
||||||
|
InvokeCustomRawNode copy = new InvokeCustomRawNode(resolve, getCallMth(), getInvokeType(), getArgsCount());
|
||||||
|
copyCommonParams(copy);
|
||||||
|
copy.setCallSiteValues(callSiteValues);
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isStaticCall() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getFirstArgOffset() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable InsnArg getInstanceArg() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSame(InsnNode obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (obj instanceof InvokeCustomRawNode) {
|
||||||
|
return super.isSame(obj) && resolve.isSame(((InvokeCustomRawNode) obj).resolve);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(InsnUtils.formatOffset(offset)).append(": INVOKE_CUSTOM ");
|
||||||
|
if (getResult() != null) {
|
||||||
|
sb.append(getResult()).append(" = ");
|
||||||
|
}
|
||||||
|
if (!appendArgs(sb)) {
|
||||||
|
sb.append('\n');
|
||||||
|
}
|
||||||
|
sb.append(" call-site: \n ").append(Utils.listToString(callSiteValues, "\n ")).append('\n');
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -67,6 +67,19 @@ public class InvokeNode extends BaseInvokeNode {
|
|||||||
return type == InvokeType.STATIC;
|
return type == InvokeType.STATIC;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isPolymorphicCall() {
|
||||||
|
if (type == InvokeType.POLYMORPHIC) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// java bytecode uses virtual call with modified method info
|
||||||
|
if (type == InvokeType.VIRTUAL
|
||||||
|
&& mth.getDeclClass().getFullName().equals("java.lang.invoke.MethodHandle")
|
||||||
|
&& (mth.getName().equals("invoke") || mth.getName().equals("invokeExact"))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public int getFirstArgOffset() {
|
public int getFirstArgOffset() {
|
||||||
return type == InvokeType.STATIC ? 0 : 1;
|
return type == InvokeType.STATIC ? 0 : 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package jadx.core.dex.instructions;
|
||||||
|
|
||||||
|
import jadx.api.plugins.input.data.IMethodProto;
|
||||||
|
import jadx.api.plugins.input.insns.InsnData;
|
||||||
|
import jadx.core.dex.info.MethodInfo;
|
||||||
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
|
import jadx.core.utils.InsnUtils;
|
||||||
|
|
||||||
|
public class InvokePolymorphicNode extends InvokeNode {
|
||||||
|
private final IMethodProto proto;
|
||||||
|
private final MethodInfo baseCallRef;
|
||||||
|
|
||||||
|
public InvokePolymorphicNode(MethodInfo callMth, InsnData insn, IMethodProto proto, MethodInfo baseRef, boolean isRange) {
|
||||||
|
super(callMth, insn, InvokeType.POLYMORPHIC, true, isRange);
|
||||||
|
this.proto = proto;
|
||||||
|
this.baseCallRef = baseRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
public InvokePolymorphicNode(MethodInfo callMth, int argsCount, IMethodProto proto, MethodInfo baseRef) {
|
||||||
|
super(callMth, InvokeType.POLYMORPHIC, argsCount);
|
||||||
|
this.proto = proto;
|
||||||
|
this.baseCallRef = baseRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IMethodProto getProto() {
|
||||||
|
return proto;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MethodInfo getBaseCallRef() {
|
||||||
|
return baseCallRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InsnNode copy() {
|
||||||
|
InvokePolymorphicNode copy = new InvokePolymorphicNode(getCallMth(), getArgsCount(), proto, baseCallRef);
|
||||||
|
copyCommonParams(copy);
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSame(InsnNode obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(obj instanceof InvokePolymorphicNode) || !super.isSame(obj)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
InvokePolymorphicNode other = (InvokePolymorphicNode) obj;
|
||||||
|
return proto.equals(other.proto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(InsnUtils.formatOffset(offset)).append(": INVOKE_POLYMORPHIC ");
|
||||||
|
if (getResult() != null) {
|
||||||
|
sb.append(getResult()).append(" = ");
|
||||||
|
}
|
||||||
|
if (!appendArgs(sb)) {
|
||||||
|
sb.append('\n');
|
||||||
|
}
|
||||||
|
sb.append(" base: ").append(baseCallRef).append('\n');
|
||||||
|
sb.append(" proto: ").append(proto).append('\n');
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,4 +8,5 @@ public enum InvokeType {
|
|||||||
SUPER,
|
SUPER,
|
||||||
POLYMORPHIC,
|
POLYMORPHIC,
|
||||||
CUSTOM,
|
CUSTOM,
|
||||||
|
CUSTOM_RAW,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -264,6 +264,13 @@ public abstract class InsnArg extends Typed {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isSameVar(RegisterArg arg) {
|
||||||
|
if (isRegister()) {
|
||||||
|
return ((RegisterArg) this).sameRegAndSVar(arg);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
protected final <T extends InsnArg> T copyCommonParams(T copy) {
|
protected final <T extends InsnArg> T copyCommonParams(T copy) {
|
||||||
copy.copyAttributesFrom(this);
|
copy.copyAttributesFrom(this);
|
||||||
copy.setParentInsn(parentInsn);
|
copy.setParentInsn(parentInsn);
|
||||||
|
|||||||
@@ -1,24 +1,26 @@
|
|||||||
package jadx.core.dex.instructions.args;
|
package jadx.core.dex.instructions.args;
|
||||||
|
|
||||||
public enum PrimitiveType {
|
public enum PrimitiveType {
|
||||||
BOOLEAN("Z", "boolean"),
|
BOOLEAN("Z", "boolean", ArgType.object("java.lang.Boolean")),
|
||||||
CHAR("C", "char"),
|
CHAR("C", "char", ArgType.object("java.lang.Character")),
|
||||||
BYTE("B", "byte"),
|
BYTE("B", "byte", ArgType.object("java.lang.Byte")),
|
||||||
SHORT("S", "short"),
|
SHORT("S", "short", ArgType.object("java.lang.Short")),
|
||||||
INT("I", "int"),
|
INT("I", "int", ArgType.object("java.lang.Integer")),
|
||||||
FLOAT("F", "float"),
|
FLOAT("F", "float", ArgType.object("java.lang.Float")),
|
||||||
LONG("J", "long"),
|
LONG("J", "long", ArgType.object("java.lang.Long")),
|
||||||
DOUBLE("D", "double"),
|
DOUBLE("D", "double", ArgType.object("java.lang.Double")),
|
||||||
OBJECT("L", "OBJECT"),
|
OBJECT("L", "OBJECT", ArgType.OBJECT),
|
||||||
ARRAY("[", "ARRAY"),
|
ARRAY("[", "ARRAY", ArgType.OBJECT_ARRAY),
|
||||||
VOID("V", "void");
|
VOID("V", "void", ArgType.object("java.lang.Void"));
|
||||||
|
|
||||||
private final String shortName;
|
private final String shortName;
|
||||||
private final String longName;
|
private final String longName;
|
||||||
|
private final ArgType boxType;
|
||||||
|
|
||||||
PrimitiveType(String shortName, String longName) {
|
PrimitiveType(String shortName, String longName, ArgType boxType) {
|
||||||
this.shortName = shortName;
|
this.shortName = shortName;
|
||||||
this.longName = longName;
|
this.longName = longName;
|
||||||
|
this.boxType = boxType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getShortName() {
|
public String getShortName() {
|
||||||
@@ -29,6 +31,10 @@ public enum PrimitiveType {
|
|||||||
return longName;
|
return longName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ArgType getBoxType() {
|
||||||
|
return boxType;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return longName;
|
return longName;
|
||||||
|
|||||||
+9
-23
@@ -8,6 +8,7 @@ import jadx.api.plugins.input.data.IMethodHandle;
|
|||||||
import jadx.api.plugins.input.data.IMethodProto;
|
import jadx.api.plugins.input.data.IMethodProto;
|
||||||
import jadx.api.plugins.input.data.IMethodRef;
|
import jadx.api.plugins.input.data.IMethodRef;
|
||||||
import jadx.api.plugins.input.data.MethodHandleType;
|
import jadx.api.plugins.input.data.MethodHandleType;
|
||||||
|
import jadx.api.plugins.input.data.annotations.EncodedType;
|
||||||
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||||
import jadx.api.plugins.input.insns.InsnData;
|
import jadx.api.plugins.input.insns.InsnData;
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
@@ -34,18 +35,20 @@ public class CustomLambdaCall {
|
|||||||
if (values.size() < 6) {
|
if (values.size() < 6) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
IMethodHandle methodHandle = (IMethodHandle) values.get(0).getValue();
|
EncodedValue mthRef = values.get(0);
|
||||||
|
if (mthRef.getType() != EncodedType.ENCODED_METHOD_HANDLE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
IMethodHandle methodHandle = (IMethodHandle) mthRef.getValue();
|
||||||
if (methodHandle.getType() != MethodHandleType.INVOKE_STATIC) {
|
if (methodHandle.getType() != MethodHandleType.INVOKE_STATIC) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
IMethodRef methodRef = methodHandle.getMethodRef();
|
IMethodRef methodRef = methodHandle.getMethodRef();
|
||||||
if (!methodRef.getName().equals("metafactory")) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!methodRef.getParentClassType().equals("Ljava/lang/invoke/LambdaMetafactory;")) {
|
if (!methodRef.getParentClassType().equals("Ljava/lang/invoke/LambdaMetafactory;")) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
String mthName = methodRef.getName();
|
||||||
|
return mthName.equals("metafactory") || mthName.equals("altMetafactory");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static InvokeCustomNode buildLambdaMethodCall(MethodNode mth, InsnData insn, boolean isRange, List<EncodedValue> values) {
|
public static InvokeCustomNode buildLambdaMethodCall(MethodNode mth, InsnData insn, boolean isRange, List<EncodedValue> values) {
|
||||||
@@ -115,7 +118,7 @@ public class CustomLambdaCall {
|
|||||||
@NotNull
|
@NotNull
|
||||||
private static InvokeNode buildInvokeNode(MethodHandleType methodHandleType, InvokeCustomNode invokeCustomNode,
|
private static InvokeNode buildInvokeNode(MethodHandleType methodHandleType, InvokeCustomNode invokeCustomNode,
|
||||||
MethodInfo callMthInfo) {
|
MethodInfo callMthInfo) {
|
||||||
InvokeType invokeType = convertInvokeType(methodHandleType);
|
InvokeType invokeType = InvokeCustomUtils.convertInvokeType(methodHandleType);
|
||||||
int callArgsCount = callMthInfo.getArgsCount();
|
int callArgsCount = callMthInfo.getArgsCount();
|
||||||
boolean instanceCall = invokeType != InvokeType.STATIC;
|
boolean instanceCall = invokeType != InvokeType.STATIC;
|
||||||
if (instanceCall) {
|
if (instanceCall) {
|
||||||
@@ -149,21 +152,4 @@ public class CustomLambdaCall {
|
|||||||
}
|
}
|
||||||
return invokeNode;
|
return invokeNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static InvokeType convertInvokeType(MethodHandleType type) {
|
|
||||||
switch (type) {
|
|
||||||
case INVOKE_STATIC:
|
|
||||||
return InvokeType.STATIC;
|
|
||||||
case INVOKE_INSTANCE:
|
|
||||||
return InvokeType.VIRTUAL;
|
|
||||||
case INVOKE_DIRECT:
|
|
||||||
case INVOKE_CONSTRUCTOR:
|
|
||||||
return InvokeType.DIRECT;
|
|
||||||
case INVOKE_INTERFACE:
|
|
||||||
return InvokeType.INTERFACE;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new JadxRuntimeException("Unsupported method handle type: " + type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package jadx.core.dex.instructions.invokedynamic;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import jadx.api.plugins.input.data.IMethodHandle;
|
||||||
|
import jadx.api.plugins.input.data.IMethodProto;
|
||||||
|
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||||
|
import jadx.api.plugins.input.insns.InsnData;
|
||||||
|
import jadx.core.dex.info.ClassInfo;
|
||||||
|
import jadx.core.dex.info.MethodInfo;
|
||||||
|
import jadx.core.dex.instructions.ConstStringNode;
|
||||||
|
import jadx.core.dex.instructions.InvokeCustomRawNode;
|
||||||
|
import jadx.core.dex.instructions.InvokeNode;
|
||||||
|
import jadx.core.dex.instructions.InvokeType;
|
||||||
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
|
import jadx.core.dex.instructions.args.InsnArg;
|
||||||
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
|
import jadx.core.dex.nodes.RootNode;
|
||||||
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
|
import static jadx.core.utils.EncodedValueUtils.buildLookupArg;
|
||||||
|
import static jadx.core.utils.EncodedValueUtils.convertToInsnArg;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show `invoke-custom` similar to polymorphic call
|
||||||
|
*/
|
||||||
|
public class CustomRawCall {
|
||||||
|
|
||||||
|
public static InsnNode build(MethodNode mth, InsnData insn, boolean isRange, List<EncodedValue> values) {
|
||||||
|
IMethodHandle resolveHandle = (IMethodHandle) values.get(0).getValue();
|
||||||
|
String invokeName = (String) values.get(1).getValue();
|
||||||
|
IMethodProto invokeProto = (IMethodProto) values.get(2).getValue();
|
||||||
|
List<InsnArg> resolveArgs = buildArgs(mth, values);
|
||||||
|
|
||||||
|
if (resolveHandle.getType().isField()) {
|
||||||
|
throw new JadxRuntimeException("Field handle not yet supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
RootNode root = mth.root();
|
||||||
|
MethodInfo resolveMth = MethodInfo.fromRef(root, resolveHandle.getMethodRef());
|
||||||
|
InvokeType resolveInvokeType = InvokeCustomUtils.convertInvokeType(resolveHandle.getType());
|
||||||
|
InvokeNode resolve = new InvokeNode(resolveMth, resolveInvokeType, resolveArgs.size());
|
||||||
|
resolveArgs.forEach(resolve::addArg);
|
||||||
|
|
||||||
|
ClassInfo invokeCls = ClassInfo.fromType(root, ArgType.OBJECT); // type will be known at runtime
|
||||||
|
MethodInfo invokeMth = MethodInfo.fromMethodProto(root, invokeCls, invokeName, invokeProto);
|
||||||
|
InvokeCustomRawNode customRawNode = new InvokeCustomRawNode(resolve, invokeMth, insn, isRange);
|
||||||
|
customRawNode.setCallSiteValues(values);
|
||||||
|
return customRawNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<InsnArg> buildArgs(MethodNode mth, List<EncodedValue> values) {
|
||||||
|
int valuesCount = values.size();
|
||||||
|
List<InsnArg> list = new ArrayList<>(valuesCount);
|
||||||
|
RootNode root = mth.root();
|
||||||
|
list.add(buildLookupArg(root)); // use `java.lang.invoke.MethodHandles.lookup()` as first arg
|
||||||
|
for (int i = 1; i < valuesCount; i++) {
|
||||||
|
EncodedValue value = values.get(i);
|
||||||
|
try {
|
||||||
|
list.add(convertToInsnArg(root, value));
|
||||||
|
} catch (Exception e) {
|
||||||
|
mth.addWarnComment("Failed to build arg in invoke-custom insn: " + value, e);
|
||||||
|
list.add(InsnArg.wrapArg(new ConstStringNode(value.toString())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
}
|
||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
package jadx.core.dex.instructions.invokedynamic;
|
||||||
|
|
||||||
|
import jadx.api.plugins.input.data.MethodHandleType;
|
||||||
|
import jadx.core.dex.instructions.InvokeType;
|
||||||
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
|
public class InvokeCustomUtils {
|
||||||
|
|
||||||
|
public static InvokeType convertInvokeType(MethodHandleType type) {
|
||||||
|
switch (type) {
|
||||||
|
case INVOKE_STATIC:
|
||||||
|
return InvokeType.STATIC;
|
||||||
|
case INVOKE_INSTANCE:
|
||||||
|
return InvokeType.VIRTUAL;
|
||||||
|
case INVOKE_DIRECT:
|
||||||
|
case INVOKE_CONSTRUCTOR:
|
||||||
|
return InvokeType.DIRECT;
|
||||||
|
case INVOKE_INTERFACE:
|
||||||
|
return InvokeType.INTERFACE;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new JadxRuntimeException("Unsupported method handle type: " + type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,29 +19,59 @@ import static jadx.core.utils.Utils.lockList;
|
|||||||
|
|
||||||
public final class BlockNode extends AttrNode implements IBlock, Comparable<BlockNode> {
|
public final class BlockNode extends AttrNode implements IBlock, Comparable<BlockNode> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Const ID
|
||||||
|
*/
|
||||||
|
private final int cid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ID linked to position in blocks list (easier to use BitSet)
|
||||||
|
* TODO: rename to avoid confusion
|
||||||
|
*/
|
||||||
private int id;
|
private int id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Offset in methods bytecode
|
||||||
|
*/
|
||||||
private final int startOffset;
|
private final int startOffset;
|
||||||
|
|
||||||
private final List<InsnNode> instructions = new ArrayList<>(2);
|
private final List<InsnNode> instructions = new ArrayList<>(2);
|
||||||
|
|
||||||
private List<BlockNode> predecessors = new ArrayList<>(1);
|
private List<BlockNode> predecessors = new ArrayList<>(1);
|
||||||
private List<BlockNode> successors = new ArrayList<>(1);
|
private List<BlockNode> successors = new ArrayList<>(1);
|
||||||
private List<BlockNode> cleanSuccessors;
|
private List<BlockNode> cleanSuccessors;
|
||||||
|
|
||||||
// all dominators
|
/**
|
||||||
|
* All dominators, excluding self
|
||||||
|
*/
|
||||||
private BitSet doms = EmptyBitSet.EMPTY;
|
private BitSet doms = EmptyBitSet.EMPTY;
|
||||||
// dominance frontier
|
|
||||||
|
/**
|
||||||
|
* Dominance frontier
|
||||||
|
*/
|
||||||
private BitSet domFrontier;
|
private BitSet domFrontier;
|
||||||
// immediate dominator
|
|
||||||
|
/**
|
||||||
|
* Immediate dominator
|
||||||
|
*/
|
||||||
private BlockNode idom;
|
private BlockNode idom;
|
||||||
// blocks on which dominates this block
|
|
||||||
|
/**
|
||||||
|
* Blocks on which dominates this block
|
||||||
|
*/
|
||||||
private List<BlockNode> dominatesOn = new ArrayList<>(3);
|
private List<BlockNode> dominatesOn = new ArrayList<>(3);
|
||||||
|
|
||||||
public BlockNode(int id, int offset) {
|
public BlockNode(int cid, int id, int offset) {
|
||||||
|
this.cid = cid;
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.startOffset = offset;
|
this.startOffset = offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setId(int id) {
|
public int getCId() {
|
||||||
|
return cid;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setId(int id) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,12 +218,12 @@ public final class BlockNode extends AttrNode implements IBlock, Comparable<Bloc
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
BlockNode other = (BlockNode) obj;
|
BlockNode other = (BlockNode) obj;
|
||||||
return id == other.id && startOffset == other.startOffset;
|
return cid == other.cid && startOffset == other.startOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compareTo(@NotNull BlockNode o) {
|
public int compareTo(@NotNull BlockNode o) {
|
||||||
return Integer.compare(id, o.id);
|
return Integer.compare(cid, o.cid);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -203,6 +233,6 @@ public final class BlockNode extends AttrNode implements IBlock, Comparable<Bloc
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "B:" + id + ':' + InsnUtils.formatOffset(startOffset);
|
return "B:" + cid + ':' + InsnUtils.formatOffset(startOffset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,14 +13,14 @@ import java.util.stream.Collectors;
|
|||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import jadx.api.DecompilationMode;
|
import jadx.api.DecompilationMode;
|
||||||
import jadx.api.ICodeCache;
|
import jadx.api.ICodeCache;
|
||||||
import jadx.api.ICodeInfo;
|
import jadx.api.ICodeInfo;
|
||||||
import jadx.api.ICodeWriter;
|
import jadx.api.ICodeWriter;
|
||||||
import jadx.api.JadxArgs;
|
import jadx.api.JadxArgs;
|
||||||
|
import jadx.api.JavaClass;
|
||||||
|
import jadx.api.impl.SimpleCodeInfo;
|
||||||
import jadx.api.plugins.input.data.IClassData;
|
import jadx.api.plugins.input.data.IClassData;
|
||||||
import jadx.api.plugins.input.data.IFieldData;
|
import jadx.api.plugins.input.data.IFieldData;
|
||||||
import jadx.api.plugins.input.data.IMethodData;
|
import jadx.api.plugins.input.data.IMethodData;
|
||||||
@@ -54,8 +54,6 @@ import static jadx.core.dex.nodes.ProcessState.LOADED;
|
|||||||
import static jadx.core.dex.nodes.ProcessState.NOT_LOADED;
|
import static jadx.core.dex.nodes.ProcessState.NOT_LOADED;
|
||||||
|
|
||||||
public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeNode, Comparable<ClassNode> {
|
public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeNode, Comparable<ClassNode> {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(ClassNode.class);
|
|
||||||
|
|
||||||
private final RootNode root;
|
private final RootNode root;
|
||||||
private final IClassData clsData;
|
private final IClassData clsData;
|
||||||
|
|
||||||
@@ -99,6 +97,8 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
|||||||
// cache maps
|
// cache maps
|
||||||
private Map<MethodInfo, MethodNode> mthInfoMap = Collections.emptyMap();
|
private Map<MethodInfo, MethodNode> mthInfoMap = Collections.emptyMap();
|
||||||
|
|
||||||
|
private JavaClass javaNode;
|
||||||
|
|
||||||
public ClassNode(RootNode root, IClassData cls) {
|
public ClassNode(RootNode root, IClassData cls) {
|
||||||
this.root = root;
|
this.root = root;
|
||||||
this.clsInfo = ClassInfo.fromType(root, ArgType.object(cls.getType()));
|
this.clsInfo = ClassInfo.fromType(root, ArgType.object(cls.getType()));
|
||||||
@@ -170,10 +170,10 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
|||||||
return ArgType.object(superType);
|
return ArgType.object(superType);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateGenericClsData(ArgType superClass, List<ArgType> interfaces, List<ArgType> generics) {
|
public void updateGenericClsData(List<ArgType> generics, ArgType superClass, List<ArgType> interfaces) {
|
||||||
|
this.generics = generics;
|
||||||
this.superClass = superClass;
|
this.superClass = superClass;
|
||||||
this.interfaces = interfaces;
|
this.interfaces = interfaces;
|
||||||
this.generics = generics;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void processAttributes(ClassNode cls) {
|
private static void processAttributes(ClassNode cls) {
|
||||||
@@ -378,7 +378,13 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
|||||||
return code;
|
return code;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ICodeInfo codeInfo = root.getProcessClasses().generateCode(this);
|
ICodeInfo codeInfo;
|
||||||
|
try {
|
||||||
|
codeInfo = root.getProcessClasses().generateCode(this);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
addError("Code generation failed", e);
|
||||||
|
codeInfo = new SimpleCodeInfo(Utils.getStackTrace(e));
|
||||||
|
}
|
||||||
if (codeInfo != ICodeInfo.EMPTY) {
|
if (codeInfo != ICodeInfo.EMPTY) {
|
||||||
codeCache.add(clsRawName, codeInfo);
|
codeCache.add(clsRawName, codeInfo);
|
||||||
}
|
}
|
||||||
@@ -462,6 +468,9 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void addField(FieldNode fld) {
|
public void addField(FieldNode fld) {
|
||||||
|
if (fields == null || fields.isEmpty()) {
|
||||||
|
fields = new ArrayList<>(1);
|
||||||
|
}
|
||||||
fields.add(fld);
|
fields.add(fld);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -650,6 +659,10 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
|||||||
return contains(AType.ANONYMOUS_CLASS);
|
return contains(AType.ANONYMOUS_CLASS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isSynthetic() {
|
||||||
|
return contains(AFlag.SYNTHETIC);
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isInner() {
|
public boolean isInner() {
|
||||||
return parentClass != this;
|
return parentClass != this;
|
||||||
}
|
}
|
||||||
@@ -791,7 +804,9 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void addCodegenDep(ClassNode dep) {
|
public void addCodegenDep(ClassNode dep) {
|
||||||
this.codegenDeps = ListUtils.safeAdd(this.codegenDeps, dep);
|
if (!codegenDeps.contains(dep)) {
|
||||||
|
this.codegenDeps = ListUtils.safeAdd(this.codegenDeps, dep);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getTotalDepsCount() {
|
public int getTotalDepsCount() {
|
||||||
@@ -819,6 +834,14 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
|||||||
return clsData == null ? "synthetic" : clsData.getInputFileName();
|
return clsData == null ? "synthetic" : clsData.getInputFileName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public JavaClass getJavaNode() {
|
||||||
|
return javaNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setJavaNode(JavaClass javaNode) {
|
||||||
|
this.javaNode = javaNode;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AnnType getAnnType() {
|
public AnnType getAnnType() {
|
||||||
return AnnType.CLASS;
|
return AnnType.CLASS;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package jadx.core.dex.nodes;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import jadx.api.JavaField;
|
||||||
import jadx.api.plugins.input.data.IFieldData;
|
import jadx.api.plugins.input.data.IFieldData;
|
||||||
import jadx.core.dex.attributes.nodes.NotificationAttrNode;
|
import jadx.core.dex.attributes.nodes.NotificationAttrNode;
|
||||||
import jadx.core.dex.info.AccessInfo;
|
import jadx.core.dex.info.AccessInfo;
|
||||||
@@ -21,6 +22,8 @@ public class FieldNode extends NotificationAttrNode implements ICodeNode {
|
|||||||
|
|
||||||
private List<MethodNode> useIn = Collections.emptyList();
|
private List<MethodNode> useIn = Collections.emptyList();
|
||||||
|
|
||||||
|
private JavaField javaNode;
|
||||||
|
|
||||||
public static FieldNode build(ClassNode cls, IFieldData fieldData) {
|
public static FieldNode build(ClassNode cls, IFieldData fieldData) {
|
||||||
FieldInfo fieldInfo = FieldInfo.fromRef(cls.root(), fieldData);
|
FieldInfo fieldInfo = FieldInfo.fromRef(cls.root(), fieldData);
|
||||||
FieldNode fieldNode = new FieldNode(cls, fieldInfo, fieldData.getAccessFlags());
|
FieldNode fieldNode = new FieldNode(cls, fieldInfo, fieldData.getAccessFlags());
|
||||||
@@ -57,6 +60,10 @@ public class FieldNode extends NotificationAttrNode implements ICodeNode {
|
|||||||
return accFlags.isStatic();
|
return accFlags.isStatic();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isInstance() {
|
||||||
|
return !accFlags.isStatic();
|
||||||
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return fieldInfo.getName();
|
return fieldInfo.getName();
|
||||||
}
|
}
|
||||||
@@ -65,6 +72,10 @@ public class FieldNode extends NotificationAttrNode implements ICodeNode {
|
|||||||
return fieldInfo.getAlias();
|
return fieldInfo.getAlias();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void rename(String alias) {
|
||||||
|
fieldInfo.setAlias(alias);
|
||||||
|
}
|
||||||
|
|
||||||
public ArgType getType() {
|
public ArgType getType() {
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
@@ -73,6 +84,10 @@ public class FieldNode extends NotificationAttrNode implements ICodeNode {
|
|||||||
return parentClass;
|
return parentClass;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ClassNode getTopParentClass() {
|
||||||
|
return parentClass.getTopParentClass();
|
||||||
|
}
|
||||||
|
|
||||||
public List<MethodNode> getUseIn() {
|
public List<MethodNode> getUseIn() {
|
||||||
return useIn;
|
return useIn;
|
||||||
}
|
}
|
||||||
@@ -100,6 +115,14 @@ public class FieldNode extends NotificationAttrNode implements ICodeNode {
|
|||||||
return parentClass.root();
|
return parentClass.root();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public JavaField getJavaNode() {
|
||||||
|
return javaNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setJavaNode(JavaField javaNode) {
|
||||||
|
this.javaNode = javaNode;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AnnType getAnnType() {
|
public AnnType getAnnType() {
|
||||||
return AnnType.FIELD;
|
return AnnType.FIELD;
|
||||||
|
|||||||
@@ -264,21 +264,6 @@ public class InsnNode extends LineAttrNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean canReorderRecursive() {
|
|
||||||
if (!canReorder()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (InsnArg arg : this.getArguments()) {
|
|
||||||
if (arg.isInsnWrap()) {
|
|
||||||
InsnNode wrapInsn = ((InsnWrapArg) arg).getWrapInsn();
|
|
||||||
if (!wrapInsn.canReorderRecursive()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean containsWrappedInsn() {
|
public boolean containsWrappedInsn() {
|
||||||
for (InsnArg arg : this.getArguments()) {
|
for (InsnArg arg : this.getArguments()) {
|
||||||
if (arg.isInsnWrap()) {
|
if (arg.isInsnWrap()) {
|
||||||
@@ -554,19 +539,25 @@ public class InsnNode extends LineAttrNode {
|
|||||||
return super.equals(obj);
|
return super.equals(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void appendArgs(StringBuilder sb) {
|
/**
|
||||||
|
* Append arguments type, wrap line if too long
|
||||||
|
*
|
||||||
|
* @return true if args wrapped
|
||||||
|
*/
|
||||||
|
protected boolean appendArgs(StringBuilder sb) {
|
||||||
if (arguments.isEmpty()) {
|
if (arguments.isEmpty()) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
String argsStr = Utils.listToString(arguments);
|
String argsStr = Utils.listToString(arguments);
|
||||||
if (argsStr.length() < 120) {
|
if (argsStr.length() < 120) {
|
||||||
sb.append(argsStr);
|
sb.append(argsStr);
|
||||||
} else {
|
return false;
|
||||||
// wrap args
|
|
||||||
String separator = ICodeWriter.NL + " ";
|
|
||||||
sb.append(separator).append(Utils.listToString(arguments, separator));
|
|
||||||
sb.append(ICodeWriter.NL);
|
|
||||||
}
|
}
|
||||||
|
// wrap args
|
||||||
|
String separator = ICodeWriter.NL + " ";
|
||||||
|
sb.append(separator).append(Utils.listToString(arguments, separator));
|
||||||
|
sb.append(ICodeWriter.NL);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import jadx.api.JavaMethod;
|
||||||
import jadx.api.plugins.input.data.ICodeReader;
|
import jadx.api.plugins.input.data.ICodeReader;
|
||||||
import jadx.api.plugins.input.data.IDebugInfo;
|
import jadx.api.plugins.input.data.IDebugInfo;
|
||||||
import jadx.api.plugins.input.data.IMethodData;
|
import jadx.api.plugins.input.data.IMethodData;
|
||||||
@@ -61,6 +62,7 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
|
|||||||
private List<RegisterArg> argsList;
|
private List<RegisterArg> argsList;
|
||||||
private InsnNode[] instructions;
|
private InsnNode[] instructions;
|
||||||
private List<BlockNode> blocks;
|
private List<BlockNode> blocks;
|
||||||
|
private int blocksMaxCId;
|
||||||
private BlockNode enterBlock;
|
private BlockNode enterBlock;
|
||||||
private BlockNode exitBlock;
|
private BlockNode exitBlock;
|
||||||
private List<SSAVar> sVars;
|
private List<SSAVar> sVars;
|
||||||
@@ -70,6 +72,8 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
|
|||||||
|
|
||||||
private List<MethodNode> useIn = Collections.emptyList();
|
private List<MethodNode> useIn = Collections.emptyList();
|
||||||
|
|
||||||
|
private JavaMethod javaNode;
|
||||||
|
|
||||||
public static MethodNode build(ClassNode classNode, IMethodData methodData) {
|
public static MethodNode build(ClassNode classNode, IMethodData methodData) {
|
||||||
MethodNode methodNode = new MethodNode(classNode, methodData);
|
MethodNode methodNode = new MethodNode(classNode, methodData);
|
||||||
methodNode.addAttrs(methodData.getAttributes());
|
methodNode.addAttrs(methodData.getAttributes());
|
||||||
@@ -316,6 +320,19 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
|
|||||||
return blocks;
|
return blocks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setBasicBlocks(List<BlockNode> blocks) {
|
||||||
|
this.blocks = blocks;
|
||||||
|
int i = 0;
|
||||||
|
for (BlockNode block : blocks) {
|
||||||
|
block.setId(i);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getNextBlockCId() {
|
||||||
|
return blocksMaxCId++;
|
||||||
|
}
|
||||||
|
|
||||||
public BlockNode getEnterBlock() {
|
public BlockNode getEnterBlock() {
|
||||||
return enterBlock;
|
return enterBlock;
|
||||||
}
|
}
|
||||||
@@ -596,6 +613,14 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
|
|||||||
this.useIn = useIn;
|
this.useIn = useIn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public JavaMethod getJavaNode() {
|
||||||
|
return javaNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setJavaNode(JavaMethod javaNode) {
|
||||||
|
this.javaNode = javaNode;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AnnType getAnnType() {
|
public AnnType getAnnType() {
|
||||||
return AnnType.METHOD;
|
return AnnType.METHOD;
|
||||||
|
|||||||
@@ -42,7 +42,9 @@ import jadx.core.utils.StringUtils;
|
|||||||
import jadx.core.utils.Utils;
|
import jadx.core.utils.Utils;
|
||||||
import jadx.core.utils.android.AndroidResourcesUtils;
|
import jadx.core.utils.android.AndroidResourcesUtils;
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
import jadx.core.xmlgen.ResTableParser;
|
import jadx.core.xmlgen.IResParser;
|
||||||
|
import jadx.core.xmlgen.ManifestAttributes;
|
||||||
|
import jadx.core.xmlgen.ResDecoder;
|
||||||
import jadx.core.xmlgen.ResourceStorage;
|
import jadx.core.xmlgen.ResourceStorage;
|
||||||
import jadx.core.xmlgen.entry.ResourceEntry;
|
import jadx.core.xmlgen.entry.ResourceEntry;
|
||||||
import jadx.core.xmlgen.entry.ValuesParser;
|
import jadx.core.xmlgen.entry.ValuesParser;
|
||||||
@@ -163,32 +165,37 @@ public class RootNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void loadResources(List<ResourceFile> resources) {
|
public void loadResources(List<ResourceFile> resources) {
|
||||||
ResourceFile arsc = null;
|
ResourceFile arsc = getResourceFile(resources);
|
||||||
for (ResourceFile rf : resources) {
|
|
||||||
if (rf.getType() == ResourceType.ARSC) {
|
|
||||||
arsc = rf;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (arsc == null) {
|
if (arsc == null) {
|
||||||
LOG.debug("'.arsc' file not found");
|
LOG.debug("'.arsc' file not found");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
ResTableParser parser = ResourcesLoader.decodeStream(arsc, (size, is) -> {
|
IResParser parser = ResourcesLoader.decodeStream(arsc, (size, is) -> ResDecoder.decode(this, arsc, is));
|
||||||
ResTableParser tableParser = new ResTableParser(this);
|
|
||||||
tableParser.decode(is);
|
|
||||||
return tableParser;
|
|
||||||
});
|
|
||||||
if (parser != null) {
|
if (parser != null) {
|
||||||
processResources(parser.getResStorage());
|
processResources(parser.getResStorage());
|
||||||
updateObfuscatedFiles(parser, resources);
|
updateObfuscatedFiles(parser, resources);
|
||||||
|
updateManifestAttribMap(parser);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error("Failed to parse '.arsc' file", e);
|
LOG.error("Failed to parse '.arsc' file", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateManifestAttribMap(IResParser parser) {
|
||||||
|
ManifestAttributes manifestAttributes = ManifestAttributes.getInstance();
|
||||||
|
manifestAttributes.updateAttributes(parser);
|
||||||
|
}
|
||||||
|
|
||||||
|
private @Nullable ResourceFile getResourceFile(List<ResourceFile> resources) {
|
||||||
|
for (ResourceFile rf : resources) {
|
||||||
|
if (rf.getType() == ResourceType.ARSC) {
|
||||||
|
return rf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public void processResources(ResourceStorage resStorage) {
|
public void processResources(ResourceStorage resStorage) {
|
||||||
constValues.setResourcesNames(resStorage.getResourcesNames());
|
constValues.setResourcesNames(resStorage.getResourcesNames());
|
||||||
appPackage = resStorage.getAppPackage();
|
appPackage = resStorage.getAppPackage();
|
||||||
@@ -209,7 +216,7 @@ public class RootNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateObfuscatedFiles(ResTableParser parser, List<ResourceFile> resources) {
|
private void updateObfuscatedFiles(IResParser parser, List<ResourceFile> resources) {
|
||||||
if (args.isSkipResources()) {
|
if (args.isSkipResources()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ public final class IfCondition extends AttrNode {
|
|||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
if (c.getOp() == IfOp.EQ && c.getB().isFalse()) {
|
if (c.getOp() == IfOp.EQ && c.getB().isFalse()) {
|
||||||
cond = not(new IfCondition(c.invert()));
|
cond = new IfCondition(Mode.NOT, Collections.singletonList(new IfCondition(c.invert())));
|
||||||
} else {
|
} else {
|
||||||
c.normalize();
|
c.normalize();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,6 +49,10 @@ public final class LoopRegion extends ConditionRegion {
|
|||||||
return header;
|
return header;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isEndless() {
|
||||||
|
return header == null;
|
||||||
|
}
|
||||||
|
|
||||||
public IRegion getBody() {
|
public IRegion getBody() {
|
||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,6 +60,10 @@ public class TryCatchBlockAttr implements IJadxAttribute {
|
|||||||
return throwFound;
|
return throwFound;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
public List<ExceptionHandler> getHandlers() {
|
public List<ExceptionHandler> getHandlers() {
|
||||||
return handlers;
|
return handlers;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import java.util.Objects;
|
|||||||
import jadx.api.plugins.input.data.AccessFlags;
|
import jadx.api.plugins.input.data.AccessFlags;
|
||||||
import jadx.core.Consts;
|
import jadx.core.Consts;
|
||||||
import jadx.core.dex.attributes.AFlag;
|
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.FieldReplaceAttr;
|
||||||
import jadx.core.dex.attributes.nodes.MethodReplaceAttr;
|
import jadx.core.dex.attributes.nodes.MethodReplaceAttr;
|
||||||
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
|
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
|
||||||
@@ -225,7 +226,7 @@ public class ClassModifier extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static boolean removeBridgeMethod(ClassNode cls, MethodNode mth) {
|
private static boolean removeBridgeMethod(ClassNode cls, MethodNode mth) {
|
||||||
if (cls.root().getArgs().isRenameValid()) {
|
if (cls.root().getArgs().isInlineMethods()) { // simple wrapper remove is same as inline
|
||||||
List<InsnNode> allInsns = BlockUtils.collectAllInsns(mth.getBasicBlocks());
|
List<InsnNode> allInsns = BlockUtils.collectAllInsns(mth.getBasicBlocks());
|
||||||
if (allInsns.size() == 1) {
|
if (allInsns.size() == 1) {
|
||||||
InsnNode wrappedInsn = allInsns.get(0);
|
InsnNode wrappedInsn = allInsns.get(0);
|
||||||
@@ -235,12 +236,10 @@ public class ClassModifier extends AbstractVisitor {
|
|||||||
wrappedInsn = ((InsnWrapArg) arg).getWrapInsn();
|
wrappedInsn = ((InsnWrapArg) arg).getWrapInsn();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (checkSyntheticWrapper(mth, wrappedInsn)) {
|
return checkSyntheticWrapper(mth, wrappedInsn);
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return !isMethodUnique(cls, mth);
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean checkSyntheticWrapper(MethodNode mth, InsnNode insn) {
|
private static boolean checkSyntheticWrapper(MethodNode mth, InsnNode insn) {
|
||||||
@@ -283,6 +282,9 @@ public class ClassModifier extends AbstractVisitor {
|
|||||||
if (!Objects.equals(wrappedMth.getAlias(), alias)) {
|
if (!Objects.equals(wrappedMth.getAlias(), alias)) {
|
||||||
wrappedMth.getMethodInfo().setAlias(alias);
|
wrappedMth.getMethodInfo().setAlias(alias);
|
||||||
}
|
}
|
||||||
|
wrappedMth.addAttr(new MethodReplaceAttr(mth));
|
||||||
|
wrappedMth.copyAttributeFrom(mth, AType.METHOD_OVERRIDE);
|
||||||
|
wrappedMth.addDebugComment("Method merged with bridge method");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,20 +301,6 @@ public class ClassModifier extends AbstractVisitor {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isMethodUnique(ClassNode cls, MethodNode mth) {
|
|
||||||
MethodInfo mi = mth.getMethodInfo();
|
|
||||||
for (MethodNode otherMth : cls.getMethods()) {
|
|
||||||
if (otherMth != mth) {
|
|
||||||
MethodInfo omi = otherMth.getMethodInfo();
|
|
||||||
if (omi.getName().equals(mi.getName())
|
|
||||||
&& Objects.equals(omi.getArgumentsTypes(), mi.getArgumentsTypes())) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove public empty constructors (static or default)
|
* Remove public empty constructors (static or default)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -62,45 +62,47 @@ public class ConstInlineVisitor extends AbstractVisitor {
|
|||||||
|| insn.getResult() == null) {
|
|| insn.getResult() == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SSAVar sVar = insn.getResult().getSVar();
|
SSAVar sVar = insn.getResult().getSVar();
|
||||||
InsnArg constArg;
|
InsnArg constArg;
|
||||||
Runnable onSuccess = null;
|
Runnable onSuccess = null;
|
||||||
|
switch (insn.getType()) {
|
||||||
InsnType insnType = insn.getType();
|
case CONST:
|
||||||
if (insnType == InsnType.CONST || insnType == InsnType.MOVE) {
|
case MOVE: {
|
||||||
constArg = insn.getArg(0);
|
constArg = insn.getArg(0);
|
||||||
if (!constArg.isLiteral()) {
|
if (!constArg.isLiteral()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
long lit = ((LiteralArg) constArg).getLiteral();
|
||||||
|
if (lit == 0 && forbidNullInlines(sVar)) {
|
||||||
|
// all usages forbids inlining
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CONST_STR: {
|
||||||
|
String s = ((ConstStringNode) insn).getString();
|
||||||
|
FieldNode f = mth.getParentClass().getConstField(s);
|
||||||
|
if (f == null) {
|
||||||
|
InsnNode copy = insn.copyWithoutResult();
|
||||||
|
constArg = InsnArg.wrapArg(copy);
|
||||||
|
} else {
|
||||||
|
InsnNode constGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0);
|
||||||
|
constArg = InsnArg.wrapArg(constGet);
|
||||||
|
constArg.setType(ArgType.STRING);
|
||||||
|
onSuccess = () -> f.addUseIn(mth);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CONST_CLASS: {
|
||||||
|
if (sVar.isUsedInPhi()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
constArg = InsnArg.wrapArg(insn.copyWithoutResult());
|
||||||
|
constArg.setType(ArgType.CLASS);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
long lit = ((LiteralArg) constArg).getLiteral();
|
|
||||||
if (lit == 0 && forbidNullInlines(sVar)) {
|
|
||||||
// all usages forbids inlining
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else if (insnType == InsnType.CONST_STR) {
|
|
||||||
if (sVar.isUsedInPhi()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
String s = ((ConstStringNode) insn).getString();
|
|
||||||
FieldNode f = mth.getParentClass().getConstField(s);
|
|
||||||
if (f == null) {
|
|
||||||
InsnNode copy = insn.copyWithoutResult();
|
|
||||||
constArg = InsnArg.wrapArg(copy);
|
|
||||||
} else {
|
|
||||||
InsnNode constGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0);
|
|
||||||
constArg = InsnArg.wrapArg(constGet);
|
|
||||||
constArg.setType(ArgType.STRING);
|
|
||||||
onSuccess = () -> f.addUseIn(mth);
|
|
||||||
}
|
|
||||||
} else if (insnType == InsnType.CONST_CLASS) {
|
|
||||||
if (sVar.isUsedInPhi()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
constArg = InsnArg.wrapArg(insn.copyWithoutResult());
|
|
||||||
constArg.setType(ArgType.CLASS);
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// all check passed, run replace
|
// all check passed, run replace
|
||||||
@@ -123,17 +125,30 @@ public class ConstInlineVisitor extends AbstractVisitor {
|
|||||||
int k = 0;
|
int k = 0;
|
||||||
for (RegisterArg useArg : useList) {
|
for (RegisterArg useArg : useList) {
|
||||||
InsnNode insn = useArg.getParentInsn();
|
InsnNode insn = useArg.getParentInsn();
|
||||||
if (insn == null) {
|
if (insn != null && forbidNullArgInline(insn, useArg)) {
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!canUseNull(insn, useArg)) {
|
|
||||||
useArg.add(AFlag.DONT_INLINE_CONST);
|
|
||||||
k++;
|
k++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return k == useList.size();
|
return k == useList.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean forbidNullArgInline(InsnNode insn, RegisterArg useArg) {
|
||||||
|
switch (insn.getType()) {
|
||||||
|
case MOVE:
|
||||||
|
case CAST:
|
||||||
|
case CHECK_CAST:
|
||||||
|
// result is null, chain checks
|
||||||
|
return forbidNullInlines(insn.getResult().getSVar());
|
||||||
|
|
||||||
|
default:
|
||||||
|
if (!canUseNull(insn, useArg)) {
|
||||||
|
useArg.add(AFlag.DONT_INLINE_CONST);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static boolean canUseNull(InsnNode insn, RegisterArg useArg) {
|
private static boolean canUseNull(InsnNode insn, RegisterArg useArg) {
|
||||||
switch (insn.getType()) {
|
switch (insn.getType()) {
|
||||||
case INVOKE:
|
case INVOKE:
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ public class DotGraphVisitor extends AbstractVisitor {
|
|||||||
if (insnArr == null) {
|
if (insnArr == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
BlockNode block = new BlockNode(0, 0);
|
BlockNode block = new BlockNode(0, 0, 0);
|
||||||
List<InsnNode> insnList = block.getInstructions();
|
List<InsnNode> insnList = block.getInstructions();
|
||||||
for (InsnNode insn : insnArr) {
|
for (InsnNode insn : insnArr) {
|
||||||
if (insn != null) {
|
if (insn != null) {
|
||||||
@@ -199,7 +199,7 @@ public class DotGraphVisitor extends AbstractVisitor {
|
|||||||
dot.add("color=red,");
|
dot.add("color=red,");
|
||||||
}
|
}
|
||||||
dot.add("label=\"{");
|
dot.add("label=\"{");
|
||||||
dot.add(String.valueOf(block.getId())).add("\\:\\ ");
|
dot.add(String.valueOf(block.getCId())).add("\\:\\ ");
|
||||||
dot.add(InsnUtils.formatOffset(block.getStartOffset()));
|
dot.add(InsnUtils.formatOffset(block.getStartOffset()));
|
||||||
if (!attrs.isEmpty()) {
|
if (!attrs.isEmpty()) {
|
||||||
dot.add('|').add(attrs);
|
dot.add('|').add(attrs);
|
||||||
@@ -230,10 +230,10 @@ public class DotGraphVisitor extends AbstractVisitor {
|
|||||||
|
|
||||||
if (PRINT_DOMINATORS) {
|
if (PRINT_DOMINATORS) {
|
||||||
for (BlockNode c : block.getDominatesOn()) {
|
for (BlockNode c : block.getDominatesOn()) {
|
||||||
conn.startLine(block.getId() + " -> " + c.getId() + "[color=green];");
|
conn.startLine(block.getCId() + " -> " + c.getCId() + "[color=green];");
|
||||||
}
|
}
|
||||||
for (BlockNode dom : BlockUtils.bitSetToBlocks(mth, block.getDomFrontier())) {
|
for (BlockNode dom : BlockUtils.bitSetToBlocks(mth, block.getDomFrontier())) {
|
||||||
conn.startLine("f_" + block.getId() + " -> f_" + dom.getId() + "[color=blue];");
|
conn.startLine("f_" + block.getCId() + " -> f_" + dom.getCId() + "[color=blue];");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -273,7 +273,7 @@ public class DotGraphVisitor extends AbstractVisitor {
|
|||||||
private String makeName(IContainer c) {
|
private String makeName(IContainer c) {
|
||||||
String name;
|
String name;
|
||||||
if (c instanceof BlockNode) {
|
if (c instanceof BlockNode) {
|
||||||
name = "Node_" + ((BlockNode) c).getId();
|
name = "Node_" + ((BlockNode) c).getCId();
|
||||||
} else if (c instanceof IBlock) {
|
} else if (c instanceof IBlock) {
|
||||||
name = "Node_" + c.getClass().getSimpleName() + '_' + c.hashCode();
|
name = "Node_" + c.getClass().getSimpleName() + '_' + c.hashCode();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import jadx.core.dex.attributes.AFlag;
|
|||||||
import jadx.core.dex.attributes.AType;
|
import jadx.core.dex.attributes.AType;
|
||||||
import jadx.core.dex.attributes.nodes.EnumClassAttr;
|
import jadx.core.dex.attributes.nodes.EnumClassAttr;
|
||||||
import jadx.core.dex.attributes.nodes.EnumClassAttr.EnumField;
|
import jadx.core.dex.attributes.nodes.EnumClassAttr.EnumField;
|
||||||
|
import jadx.core.dex.attributes.nodes.RenameReasonAttr;
|
||||||
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
|
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
|
||||||
import jadx.core.dex.info.AccessInfo;
|
import jadx.core.dex.info.AccessInfo;
|
||||||
import jadx.core.dex.info.ClassInfo;
|
import jadx.core.dex.info.ClassInfo;
|
||||||
@@ -26,6 +27,7 @@ import jadx.core.dex.info.MethodInfo;
|
|||||||
import jadx.core.dex.instructions.IndexInsnNode;
|
import jadx.core.dex.instructions.IndexInsnNode;
|
||||||
import jadx.core.dex.instructions.InsnType;
|
import jadx.core.dex.instructions.InsnType;
|
||||||
import jadx.core.dex.instructions.InvokeNode;
|
import jadx.core.dex.instructions.InvokeNode;
|
||||||
|
import jadx.core.dex.instructions.InvokeType;
|
||||||
import jadx.core.dex.instructions.args.ArgType;
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
import jadx.core.dex.instructions.args.InsnArg;
|
import jadx.core.dex.instructions.args.InsnArg;
|
||||||
import jadx.core.dex.instructions.args.InsnWrapArg;
|
import jadx.core.dex.instructions.args.InsnWrapArg;
|
||||||
@@ -35,9 +37,13 @@ import jadx.core.dex.instructions.mods.ConstructorInsn;
|
|||||||
import jadx.core.dex.nodes.BlockNode;
|
import jadx.core.dex.nodes.BlockNode;
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
import jadx.core.dex.nodes.FieldNode;
|
import jadx.core.dex.nodes.FieldNode;
|
||||||
|
import jadx.core.dex.nodes.IContainer;
|
||||||
import jadx.core.dex.nodes.InsnNode;
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
import jadx.core.dex.nodes.RootNode;
|
import jadx.core.dex.nodes.RootNode;
|
||||||
|
import jadx.core.dex.regions.Region;
|
||||||
|
import jadx.core.dex.visitors.regions.CheckRegions;
|
||||||
|
import jadx.core.dex.visitors.regions.IfRegionVisitor;
|
||||||
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
|
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
|
||||||
import jadx.core.utils.BlockInsnPair;
|
import jadx.core.utils.BlockInsnPair;
|
||||||
import jadx.core.utils.BlockUtils;
|
import jadx.core.utils.BlockUtils;
|
||||||
@@ -45,6 +51,7 @@ import jadx.core.utils.InsnRemover;
|
|||||||
import jadx.core.utils.InsnUtils;
|
import jadx.core.utils.InsnUtils;
|
||||||
import jadx.core.utils.Utils;
|
import jadx.core.utils.Utils;
|
||||||
import jadx.core.utils.exceptions.JadxException;
|
import jadx.core.utils.exceptions.JadxException;
|
||||||
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
import static jadx.core.utils.InsnUtils.checkInsnType;
|
import static jadx.core.utils.InsnUtils.checkInsnType;
|
||||||
import static jadx.core.utils.InsnUtils.getSingleArg;
|
import static jadx.core.utils.InsnUtils.getSingleArg;
|
||||||
@@ -53,12 +60,21 @@ import static jadx.core.utils.InsnUtils.getWrappedInsn;
|
|||||||
@JadxVisitor(
|
@JadxVisitor(
|
||||||
name = "EnumVisitor",
|
name = "EnumVisitor",
|
||||||
desc = "Restore enum classes",
|
desc = "Restore enum classes",
|
||||||
runAfter = { CodeShrinkVisitor.class, ModVisitor.class, ReSugarCode.class },
|
runAfter = {
|
||||||
runBefore = { ExtractFieldInit.class }
|
CodeShrinkVisitor.class, // all possible instructions already inlined
|
||||||
|
ModVisitor.class,
|
||||||
|
ReSugarCode.class,
|
||||||
|
IfRegionVisitor.class, // ternary operator inlined
|
||||||
|
CheckRegions.class // regions processing finished
|
||||||
|
},
|
||||||
|
runBefore = {
|
||||||
|
ExtractFieldInit.class
|
||||||
|
}
|
||||||
)
|
)
|
||||||
public class EnumVisitor extends AbstractVisitor {
|
public class EnumVisitor extends AbstractVisitor {
|
||||||
|
|
||||||
private MethodInfo enumValueOfMth;
|
private MethodInfo enumValueOfMth;
|
||||||
|
private MethodInfo cloneMth;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(RootNode root) {
|
public void init(RootNode root) {
|
||||||
@@ -68,86 +84,77 @@ public class EnumVisitor extends AbstractVisitor {
|
|||||||
"valueOf",
|
"valueOf",
|
||||||
Arrays.asList(ArgType.CLASS, ArgType.STRING),
|
Arrays.asList(ArgType.CLASS, ArgType.STRING),
|
||||||
ArgType.ENUM);
|
ArgType.ENUM);
|
||||||
|
|
||||||
|
cloneMth = MethodInfo.fromDetails(root,
|
||||||
|
ClassInfo.fromType(root, ArgType.OBJECT),
|
||||||
|
"clone",
|
||||||
|
Collections.emptyList(),
|
||||||
|
ArgType.OBJECT);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean visit(ClassNode cls) throws JadxException {
|
public boolean visit(ClassNode cls) throws JadxException {
|
||||||
boolean converted;
|
if (cls.isEnum()) {
|
||||||
try {
|
boolean converted;
|
||||||
converted = convertToEnum(cls);
|
try {
|
||||||
} catch (Exception e) {
|
converted = convertToEnum(cls);
|
||||||
cls.addWarnComment("Enum visitor error", e);
|
} catch (Exception e) {
|
||||||
converted = false;
|
cls.addWarnComment("Enum visitor error", e);
|
||||||
}
|
converted = false;
|
||||||
if (!converted) {
|
}
|
||||||
AccessInfo accessFlags = cls.getAccessFlags();
|
if (!converted) {
|
||||||
if (accessFlags.isEnum()) {
|
AccessInfo accessFlags = cls.getAccessFlags();
|
||||||
cls.setAccessFlags(accessFlags.remove(AccessFlags.ENUM));
|
if (accessFlags.isEnum()) {
|
||||||
cls.addWarnComment("Failed to restore enum class, 'enum' modifier removed");
|
cls.setAccessFlags(accessFlags.remove(AccessFlags.ENUM));
|
||||||
|
cls.addWarnComment("Failed to restore enum class, 'enum' modifier and super class removed");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean convertToEnum(ClassNode cls) {
|
private boolean convertToEnum(ClassNode cls) {
|
||||||
if (!cls.isEnum()) {
|
ArgType superType = cls.getSuperClass();
|
||||||
return false;
|
if (superType != null && superType.getObject().equals(ArgType.ENUM.getObject())) {
|
||||||
|
cls.add(AFlag.REMOVE_SUPER_CLASS);
|
||||||
}
|
}
|
||||||
MethodNode classInitMth = cls.getClassInitMth();
|
MethodNode classInitMth = cls.getClassInitMth();
|
||||||
if (classInitMth == null) {
|
if (classInitMth == null) {
|
||||||
cls.addWarnComment("Enum class init method not found");
|
cls.addWarnComment("Enum class init method not found");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (classInitMth.getBasicBlocks().isEmpty()) {
|
Region staticRegion = classInitMth.getRegion();
|
||||||
|
if (staticRegion == null || classInitMth.getBasicBlocks().isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
ArgType clsType = cls.getClassInfo().getType();
|
// collect blocks on linear part of static method (ignore branching on method end)
|
||||||
|
List<BlockNode> staticBlocks = new ArrayList<>();
|
||||||
// search "$VALUES" field (holds all enum values)
|
for (IContainer subBlock : staticRegion.getSubBlocks()) {
|
||||||
List<FieldNode> valuesCandidates = cls.getFields().stream()
|
if (subBlock instanceof BlockNode) {
|
||||||
.filter(f -> f.getAccessFlags().isStatic())
|
staticBlocks.add((BlockNode) subBlock);
|
||||||
.filter(f -> f.getType().isArray())
|
} else {
|
||||||
.filter(f -> Objects.equals(f.getType().getArrayRootElement(), clsType))
|
break;
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
if (valuesCandidates.isEmpty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (valuesCandidates.size() > 1) {
|
|
||||||
valuesCandidates.removeIf(f -> !f.getAccessFlags().isSynthetic());
|
|
||||||
}
|
|
||||||
if (valuesCandidates.size() > 1) {
|
|
||||||
Optional<FieldNode> valuesOpt = valuesCandidates.stream().filter(f -> f.getName().equals("$VALUES")).findAny();
|
|
||||||
if (valuesOpt.isPresent()) {
|
|
||||||
valuesCandidates.clear();
|
|
||||||
valuesCandidates.add(valuesOpt.get());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (valuesCandidates.size() != 1) {
|
if (staticBlocks.isEmpty()) {
|
||||||
cls.addWarnComment("Found several \"values\" enum fields: " + valuesCandidates);
|
cls.addWarnComment("Unexpected branching in enum static init block");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
FieldNode valuesField = valuesCandidates.get(0);
|
EnumData data = new EnumData(cls, classInitMth, staticBlocks);
|
||||||
List<InsnNode> toRemove = new ArrayList<>();
|
if (!searchValuesField(data)) {
|
||||||
|
|
||||||
// search "$VALUES" array init and collect enum fields
|
|
||||||
BlockInsnPair valuesInitPair = getValuesInitInsn(classInitMth, valuesField);
|
|
||||||
if (valuesInitPair == null) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
BlockNode staticBlock = valuesInitPair.getBlock();
|
|
||||||
InsnNode valuesInitInsn = valuesInitPair.getInsn();
|
|
||||||
|
|
||||||
List<EnumField> enumFields = null;
|
List<EnumField> enumFields = null;
|
||||||
InsnArg arrArg = valuesInitInsn.getArg(0);
|
InsnArg arrArg = data.valuesInitInsn.getArg(0);
|
||||||
if (arrArg.isInsnWrap()) {
|
if (arrArg.isInsnWrap()) {
|
||||||
InsnNode wrappedInsn = ((InsnWrapArg) arrArg).getWrapInsn();
|
InsnNode wrappedInsn = ((InsnWrapArg) arrArg).getWrapInsn();
|
||||||
enumFields = extractEnumFieldsFromInsn(cls, staticBlock, wrappedInsn, toRemove);
|
enumFields = extractEnumFieldsFromInsn(data, wrappedInsn);
|
||||||
}
|
}
|
||||||
if (enumFields == null) {
|
if (enumFields == null) {
|
||||||
|
cls.addWarnComment("Unknown enum class pattern. Please report as an issue!");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
toRemove.add(valuesInitInsn);
|
data.toRemove.add(data.valuesInitInsn);
|
||||||
|
|
||||||
// all checks complete, perform transform
|
// all checks complete, perform transform
|
||||||
EnumClassAttr attr = new EnumClassAttr(enumFields);
|
EnumClassAttr attr = new EnumClassAttr(enumFields);
|
||||||
@@ -167,59 +174,92 @@ public class EnumVisitor extends AbstractVisitor {
|
|||||||
fieldNode.getFieldInfo().setAlias(name);
|
fieldNode.getFieldInfo().setAlias(name);
|
||||||
}
|
}
|
||||||
fieldNode.add(AFlag.DONT_GENERATE);
|
fieldNode.add(AFlag.DONT_GENERATE);
|
||||||
processConstructorInsn(cls, enumField, classInitMth, staticBlock, toRemove);
|
processConstructorInsn(data, enumField, classInitMth);
|
||||||
}
|
}
|
||||||
valuesField.add(AFlag.DONT_GENERATE);
|
data.valuesField.add(AFlag.DONT_GENERATE);
|
||||||
InsnRemover.removeAllAndUnbind(classInitMth, staticBlock, toRemove);
|
InsnRemover.removeAllAndUnbind(classInitMth, data.toRemove);
|
||||||
if (classInitMth.countInsns() == 0) {
|
if (classInitMth.countInsns() == 0) {
|
||||||
classInitMth.add(AFlag.DONT_GENERATE);
|
classInitMth.add(AFlag.DONT_GENERATE);
|
||||||
} else if (!toRemove.isEmpty()) {
|
} else if (!data.toRemove.isEmpty()) {
|
||||||
CodeShrinkVisitor.shrinkMethod(classInitMth);
|
CodeShrinkVisitor.shrinkMethod(classInitMth);
|
||||||
}
|
}
|
||||||
removeEnumMethods(cls, clsType, valuesField);
|
removeEnumMethods(cls, data.valuesField);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processConstructorInsn(ClassNode cls, EnumField enumField, MethodNode classInitMth,
|
/**
|
||||||
BlockNode staticBlock, List<InsnNode> toRemove) {
|
* Search "$VALUES" field (holds all enum values)
|
||||||
ConstructorInsn co = enumField.getConstrInsn();
|
*/
|
||||||
ClassInfo enumClsInfo = co.getClassType();
|
private boolean searchValuesField(EnumData data) {
|
||||||
if (!enumClsInfo.equals(cls.getClassInfo())) {
|
ArgType clsType = data.cls.getClassInfo().getType();
|
||||||
ClassNode enumCls = cls.root().resolveClass(enumClsInfo);
|
List<FieldNode> valuesCandidates = data.cls.getFields().stream()
|
||||||
if (enumCls != null) {
|
.filter(f -> f.getAccessFlags().isStatic())
|
||||||
processEnumCls(cls, enumField, enumCls);
|
.filter(f -> f.getType().isArray())
|
||||||
|
.filter(f -> Objects.equals(f.getType().getArrayRootElement(), clsType))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
if (valuesCandidates.isEmpty()) {
|
||||||
|
data.cls.addWarnComment("$VALUES field not found");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (valuesCandidates.size() > 1) {
|
||||||
|
valuesCandidates.removeIf(f -> !f.getAccessFlags().isSynthetic());
|
||||||
|
}
|
||||||
|
if (valuesCandidates.size() > 1) {
|
||||||
|
Optional<FieldNode> valuesOpt = valuesCandidates.stream().filter(f -> f.getName().equals("$VALUES")).findAny();
|
||||||
|
if (valuesOpt.isPresent()) {
|
||||||
|
valuesCandidates.clear();
|
||||||
|
valuesCandidates.add(valuesOpt.get());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
List<RegisterArg> regs = new ArrayList<>();
|
if (valuesCandidates.size() != 1) {
|
||||||
co.getRegisterArgs(regs);
|
data.cls.addWarnComment("Found several \"values\" enum fields: " + valuesCandidates);
|
||||||
if (!regs.isEmpty()) {
|
return false;
|
||||||
cls.addWarnComment("Init of enum " + enumField.getField().getName() + " can be incorrect");
|
|
||||||
}
|
}
|
||||||
MethodNode ctrMth = cls.root().resolveMethod(co.getCallMth());
|
data.valuesField = valuesCandidates.get(0);
|
||||||
|
|
||||||
|
// search "$VALUES" array init and collect enum fields
|
||||||
|
BlockInsnPair valuesInitPair = getValuesInitInsn(data);
|
||||||
|
if (valuesInitPair == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
data.valuesInitInsn = valuesInitPair.getInsn();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processConstructorInsn(EnumData data, EnumField enumField, MethodNode classInitMth) {
|
||||||
|
ConstructorInsn co = enumField.getConstrInsn();
|
||||||
|
ClassInfo enumClsInfo = co.getClassType();
|
||||||
|
if (!enumClsInfo.equals(data.cls.getClassInfo())) {
|
||||||
|
ClassNode enumCls = data.cls.root().resolveClass(enumClsInfo);
|
||||||
|
if (enumCls != null) {
|
||||||
|
processEnumCls(data.cls, enumField, enumCls);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MethodNode ctrMth = data.cls.root().resolveMethod(co.getCallMth());
|
||||||
if (ctrMth != null) {
|
if (ctrMth != null) {
|
||||||
markArgsForSkip(ctrMth);
|
markArgsForSkip(ctrMth);
|
||||||
}
|
}
|
||||||
RegisterArg coResArg = co.getResult();
|
RegisterArg coResArg = co.getResult();
|
||||||
if (coResArg == null || coResArg.getSVar().getUseList().size() <= 2) {
|
if (coResArg == null || coResArg.getSVar().getUseList().size() <= 2) {
|
||||||
toRemove.add(co);
|
data.toRemove.add(co);
|
||||||
} else {
|
} else {
|
||||||
// constructor result used in other places -> replace constructor with enum field get (SGET)
|
// constructor result used in other places -> replace constructor with enum field get (SGET)
|
||||||
IndexInsnNode enumGet = new IndexInsnNode(InsnType.SGET, enumField.getField().getFieldInfo(), 0);
|
IndexInsnNode enumGet = new IndexInsnNode(InsnType.SGET, enumField.getField().getFieldInfo(), 0);
|
||||||
enumGet.setResult(coResArg.duplicate());
|
enumGet.setResult(coResArg.duplicate());
|
||||||
BlockUtils.replaceInsn(classInitMth, staticBlock, co, enumGet);
|
BlockUtils.replaceInsn(classInitMth, co, enumGet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private List<EnumField> extractEnumFieldsFromInsn(ClassNode cls, BlockNode staticBlock,
|
private List<EnumField> extractEnumFieldsFromInsn(EnumData enumData, InsnNode wrappedInsn) {
|
||||||
InsnNode wrappedInsn, List<InsnNode> toRemove) {
|
|
||||||
switch (wrappedInsn.getType()) {
|
switch (wrappedInsn.getType()) {
|
||||||
case FILLED_NEW_ARRAY:
|
case FILLED_NEW_ARRAY:
|
||||||
return extractEnumFieldsFromFilledArray(cls, wrappedInsn, staticBlock, toRemove);
|
return extractEnumFieldsFromFilledArray(enumData, wrappedInsn);
|
||||||
|
|
||||||
case INVOKE:
|
case INVOKE:
|
||||||
// handle redirection of values array fill (added in java 15)
|
// handle redirection of values array fill (added in java 15)
|
||||||
return extractEnumFieldsFromInvoke(cls, staticBlock, (InvokeNode) wrappedInsn, toRemove);
|
return extractEnumFieldsFromInvoke(enumData, (InvokeNode) wrappedInsn);
|
||||||
|
|
||||||
case NEW_ARRAY:
|
case NEW_ARRAY:
|
||||||
InsnArg arg = wrappedInsn.getArg(0);
|
InsnArg arg = wrappedInsn.getArg(0);
|
||||||
@@ -234,10 +274,9 @@ public class EnumVisitor extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<EnumField> extractEnumFieldsFromInvoke(ClassNode cls, BlockNode staticBlock,
|
private List<EnumField> extractEnumFieldsFromInvoke(EnumData enumData, InvokeNode invokeNode) {
|
||||||
InvokeNode invokeNode, List<InsnNode> toRemove) {
|
|
||||||
MethodInfo callMth = invokeNode.getCallMth();
|
MethodInfo callMth = invokeNode.getCallMth();
|
||||||
MethodNode valuesMth = cls.root().resolveMethod(callMth);
|
MethodNode valuesMth = enumData.cls.root().resolveMethod(callMth);
|
||||||
if (valuesMth == null || valuesMth.isVoidReturn()) {
|
if (valuesMth == null || valuesMth.isVoidReturn()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -247,16 +286,16 @@ public class EnumVisitor extends AbstractVisitor {
|
|||||||
if (wrappedInsn == null) {
|
if (wrappedInsn == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
List<EnumField> enumFields = extractEnumFieldsFromInsn(cls, staticBlock, wrappedInsn, toRemove);
|
List<EnumField> enumFields = extractEnumFieldsFromInsn(enumData, wrappedInsn);
|
||||||
if (enumFields != null) {
|
if (enumFields != null) {
|
||||||
valuesMth.add(AFlag.DONT_GENERATE);
|
valuesMth.add(AFlag.DONT_GENERATE);
|
||||||
}
|
}
|
||||||
return enumFields;
|
return enumFields;
|
||||||
}
|
}
|
||||||
|
|
||||||
private BlockInsnPair getValuesInitInsn(MethodNode classInitMth, FieldNode valuesField) {
|
private BlockInsnPair getValuesInitInsn(EnumData data) {
|
||||||
FieldInfo searchField = valuesField.getFieldInfo();
|
FieldInfo searchField = data.valuesField.getFieldInfo();
|
||||||
for (BlockNode blockNode : classInitMth.getBasicBlocks()) {
|
for (BlockNode blockNode : data.staticBlocks) {
|
||||||
for (InsnNode insn : blockNode.getInstructions()) {
|
for (InsnNode insn : blockNode.getInstructions()) {
|
||||||
if (insn.getType() == InsnType.SPUT) {
|
if (insn.getType() == InsnType.SPUT) {
|
||||||
IndexInsnNode indexInsnNode = (IndexInsnNode) insn;
|
IndexInsnNode indexInsnNode = (IndexInsnNode) insn;
|
||||||
@@ -270,50 +309,49 @@ public class EnumVisitor extends AbstractVisitor {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<EnumField> extractEnumFieldsFromFilledArray(ClassNode cls, InsnNode arrFillInsn, BlockNode staticBlock,
|
private List<EnumField> extractEnumFieldsFromFilledArray(EnumData enumData, InsnNode arrFillInsn) {
|
||||||
List<InsnNode> toRemove) {
|
|
||||||
List<EnumField> enumFields = new ArrayList<>();
|
List<EnumField> enumFields = new ArrayList<>();
|
||||||
for (InsnArg arg : arrFillInsn.getArguments()) {
|
for (InsnArg arg : arrFillInsn.getArguments()) {
|
||||||
EnumField field = null;
|
EnumField field = null;
|
||||||
if (arg.isInsnWrap()) {
|
if (arg.isInsnWrap()) {
|
||||||
InsnNode wrappedInsn = ((InsnWrapArg) arg).getWrapInsn();
|
InsnNode wrappedInsn = ((InsnWrapArg) arg).getWrapInsn();
|
||||||
field = processEnumFieldByWrappedInsn(cls, wrappedInsn, staticBlock, toRemove);
|
field = processEnumFieldByWrappedInsn(enumData, wrappedInsn);
|
||||||
} else if (arg.isRegister()) {
|
} else if (arg.isRegister()) {
|
||||||
field = processEnumFieldByRegister(cls, (RegisterArg) arg, staticBlock, toRemove);
|
field = processEnumFieldByRegister(enumData, (RegisterArg) arg);
|
||||||
}
|
}
|
||||||
if (field == null) {
|
if (field == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
enumFields.add(field);
|
enumFields.add(field);
|
||||||
}
|
}
|
||||||
toRemove.add(arrFillInsn);
|
enumData.toRemove.add(arrFillInsn);
|
||||||
return enumFields;
|
return enumFields;
|
||||||
}
|
}
|
||||||
|
|
||||||
private EnumField processEnumFieldByWrappedInsn(ClassNode cls, InsnNode wrappedInsn, BlockNode staticBlock, List<InsnNode> toRemove) {
|
private EnumField processEnumFieldByWrappedInsn(EnumData data, InsnNode wrappedInsn) {
|
||||||
if (wrappedInsn.getType() == InsnType.SGET) {
|
if (wrappedInsn.getType() == InsnType.SGET) {
|
||||||
return processEnumFieldByField(cls, wrappedInsn, staticBlock, toRemove);
|
return processEnumFieldByField(data, wrappedInsn);
|
||||||
}
|
}
|
||||||
ConstructorInsn constructorInsn = castConstructorInsn(wrappedInsn);
|
ConstructorInsn constructorInsn = castConstructorInsn(wrappedInsn);
|
||||||
if (constructorInsn != null) {
|
if (constructorInsn != null) {
|
||||||
FieldNode enumFieldNode = createFakeField(cls, "EF" + constructorInsn.getOffset());
|
FieldNode enumFieldNode = createFakeField(data.cls, "EF" + constructorInsn.getOffset());
|
||||||
cls.addField(enumFieldNode);
|
data.cls.addField(enumFieldNode);
|
||||||
return createEnumFieldByConstructor(cls, enumFieldNode, constructorInsn);
|
return createEnumFieldByConstructor(data.cls, enumFieldNode, constructorInsn);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private EnumField processEnumFieldByField(ClassNode cls, InsnNode sgetInsn, BlockNode staticBlock, List<InsnNode> toRemove) {
|
private EnumField processEnumFieldByField(EnumData data, InsnNode sgetInsn) {
|
||||||
if (sgetInsn.getType() != InsnType.SGET) {
|
if (sgetInsn.getType() != InsnType.SGET) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
FieldInfo fieldInfo = (FieldInfo) ((IndexInsnNode) sgetInsn).getIndex();
|
FieldInfo fieldInfo = (FieldInfo) ((IndexInsnNode) sgetInsn).getIndex();
|
||||||
FieldNode enumFieldNode = cls.searchField(fieldInfo);
|
FieldNode enumFieldNode = data.cls.searchField(fieldInfo);
|
||||||
if (enumFieldNode == null) {
|
if (enumFieldNode == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
InsnNode sputInsn = searchFieldPutInsn(cls, staticBlock, enumFieldNode);
|
InsnNode sputInsn = searchFieldPutInsn(data, enumFieldNode);
|
||||||
if (sputInsn == null) {
|
if (sputInsn == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -324,17 +362,17 @@ public class EnumVisitor extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
RegisterArg sgetResult = sgetInsn.getResult();
|
RegisterArg sgetResult = sgetInsn.getResult();
|
||||||
if (sgetResult == null || sgetResult.getSVar().getUseCount() == 1) {
|
if (sgetResult == null || sgetResult.getSVar().getUseCount() == 1) {
|
||||||
toRemove.add(sgetInsn);
|
data.toRemove.add(sgetInsn);
|
||||||
}
|
}
|
||||||
toRemove.add(sputInsn);
|
data.toRemove.add(sputInsn);
|
||||||
return createEnumFieldByConstructor(cls, enumFieldNode, co);
|
return createEnumFieldByConstructor(data.cls, enumFieldNode, co);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private EnumField processEnumFieldByRegister(ClassNode cls, RegisterArg arg, BlockNode staticBlock, List<InsnNode> toRemove) {
|
private EnumField processEnumFieldByRegister(EnumData data, RegisterArg arg) {
|
||||||
InsnNode assignInsn = arg.getAssignInsn();
|
InsnNode assignInsn = arg.getAssignInsn();
|
||||||
if (assignInsn != null && assignInsn.getType() == InsnType.SGET) {
|
if (assignInsn != null && assignInsn.getType() == InsnType.SGET) {
|
||||||
return processEnumFieldByField(cls, assignInsn, staticBlock, toRemove);
|
return processEnumFieldByField(data, assignInsn);
|
||||||
}
|
}
|
||||||
|
|
||||||
SSAVar ssaVar = arg.getSVar();
|
SSAVar ssaVar = arg.getSVar();
|
||||||
@@ -345,12 +383,12 @@ public class EnumVisitor extends AbstractVisitor {
|
|||||||
if (constrInsn == null || constrInsn.getType() != InsnType.CONSTRUCTOR) {
|
if (constrInsn == null || constrInsn.getType() != InsnType.CONSTRUCTOR) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
FieldNode enumFieldNode = searchEnumField(cls, ssaVar, toRemove);
|
FieldNode enumFieldNode = searchEnumField(data, ssaVar);
|
||||||
if (enumFieldNode == null) {
|
if (enumFieldNode == null) {
|
||||||
enumFieldNode = createFakeField(cls, "EF" + arg.getRegNum());
|
enumFieldNode = createFakeField(data.cls, "EF" + arg.getRegNum());
|
||||||
cls.addField(enumFieldNode);
|
data.cls.addField(enumFieldNode);
|
||||||
}
|
}
|
||||||
return createEnumFieldByConstructor(cls, enumFieldNode, (ConstructorInsn) constrInsn);
|
return createEnumFieldByConstructor(data.cls, enumFieldNode, (ConstructorInsn) constrInsn);
|
||||||
}
|
}
|
||||||
|
|
||||||
private FieldNode createFakeField(ClassNode cls, String name) {
|
private FieldNode createFakeField(ClassNode cls, String name) {
|
||||||
@@ -363,20 +401,21 @@ public class EnumVisitor extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private FieldNode searchEnumField(ClassNode cls, SSAVar ssaVar, List<InsnNode> toRemove) {
|
private FieldNode searchEnumField(EnumData data, SSAVar ssaVar) {
|
||||||
InsnNode sputInsn = ssaVar.getUseList().get(0).getParentInsn();
|
InsnNode sputInsn = ssaVar.getUseList().get(0).getParentInsn();
|
||||||
if (sputInsn == null || sputInsn.getType() != InsnType.SPUT) {
|
if (sputInsn == null || sputInsn.getType() != InsnType.SPUT) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
FieldInfo fieldInfo = (FieldInfo) ((IndexInsnNode) sputInsn).getIndex();
|
FieldInfo fieldInfo = (FieldInfo) ((IndexInsnNode) sputInsn).getIndex();
|
||||||
FieldNode enumFieldNode = cls.searchField(fieldInfo);
|
FieldNode enumFieldNode = data.cls.searchField(fieldInfo);
|
||||||
if (enumFieldNode == null) {
|
if (enumFieldNode == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
toRemove.add(sputInsn);
|
data.toRemove.add(sputInsn);
|
||||||
return enumFieldNode;
|
return enumFieldNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("StatementWithEmptyBody")
|
||||||
private EnumField createEnumFieldByConstructor(ClassNode cls, FieldNode enumFieldNode, ConstructorInsn co) {
|
private EnumField createEnumFieldByConstructor(ClassNode cls, FieldNode enumFieldNode, ConstructorInsn co) {
|
||||||
// usually constructor signature is '<init>(Ljava/lang/String;I)V'.
|
// usually constructor signature is '<init>(Ljava/lang/String;I)V'.
|
||||||
// sometimes for one field enum second arg can be omitted
|
// sometimes for one field enum second arg can be omitted
|
||||||
@@ -399,31 +438,38 @@ public class EnumVisitor extends AbstractVisitor {
|
|||||||
if (ctrMth == null) {
|
if (ctrMth == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
List<RegisterArg> regs = new ArrayList<>();
|
||||||
|
co.getRegisterArgs(regs);
|
||||||
|
if (!regs.isEmpty()) {
|
||||||
|
throw new JadxRuntimeException("Init of enum " + enumFieldNode.getName() + " uses external variables");
|
||||||
|
}
|
||||||
return new EnumField(enumFieldNode, co);
|
return new EnumField(enumFieldNode, co);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private InsnNode searchFieldPutInsn(ClassNode cls, BlockNode staticBlock, FieldNode enumFieldNode) {
|
private InsnNode searchFieldPutInsn(EnumData data, FieldNode enumFieldNode) {
|
||||||
for (InsnNode sputInsn : staticBlock.getInstructions()) {
|
for (BlockNode block : data.staticBlocks) {
|
||||||
if (sputInsn != null && sputInsn.getType() == InsnType.SPUT) {
|
for (InsnNode sputInsn : block.getInstructions()) {
|
||||||
FieldInfo f = (FieldInfo) ((IndexInsnNode) sputInsn).getIndex();
|
if (sputInsn != null && sputInsn.getType() == InsnType.SPUT) {
|
||||||
FieldNode fieldNode = cls.searchField(f);
|
FieldInfo f = (FieldInfo) ((IndexInsnNode) sputInsn).getIndex();
|
||||||
if (Objects.equals(fieldNode, enumFieldNode)) {
|
FieldNode fieldNode = data.cls.searchField(f);
|
||||||
return sputInsn;
|
if (Objects.equals(fieldNode, enumFieldNode)) {
|
||||||
|
return sputInsn;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeEnumMethods(ClassNode cls, ArgType clsType, FieldNode valuesField) {
|
private void removeEnumMethods(ClassNode cls, FieldNode valuesField) {
|
||||||
String valuesMethod = "values()" + TypeGen.signature(ArgType.array(clsType));
|
ArgType clsType = cls.getClassInfo().getType();
|
||||||
FieldInfo valuesFieldInfo = valuesField.getFieldInfo();
|
String valuesMethodShortId = "values()" + TypeGen.signature(ArgType.array(clsType));
|
||||||
|
MethodNode valuesMethod = null;
|
||||||
// remove compiler generated methods
|
// remove compiler generated methods
|
||||||
for (MethodNode mth : cls.getMethods()) {
|
for (MethodNode mth : cls.getMethods()) {
|
||||||
MethodInfo mi = mth.getMethodInfo();
|
MethodInfo mi = mth.getMethodInfo();
|
||||||
if (mi.isClassInit()) {
|
if (mi.isClassInit() || mth.isNoCode()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
String shortId = mi.getShortId();
|
String shortId = mi.getShortId();
|
||||||
@@ -432,12 +478,33 @@ public class EnumVisitor extends AbstractVisitor {
|
|||||||
mth.add(AFlag.DONT_GENERATE);
|
mth.add(AFlag.DONT_GENERATE);
|
||||||
}
|
}
|
||||||
markArgsForSkip(mth);
|
markArgsForSkip(mth);
|
||||||
} else if (shortId.equals(valuesMethod)
|
} else if (mi.getShortId().equals(valuesMethodShortId)) {
|
||||||
|| usesValuesField(mth, valuesFieldInfo)
|
if (isValuesMethod(mth, clsType)) {
|
||||||
|| simpleValueOfMth(mth, clsType)) {
|
valuesMethod = mth;
|
||||||
|
mth.add(AFlag.DONT_GENERATE);
|
||||||
|
} else {
|
||||||
|
// custom values method => rename to resolve conflict with enum method
|
||||||
|
mth.getMethodInfo().setAlias("valuesCustom");
|
||||||
|
mth.addAttr(new RenameReasonAttr(mth).append("to resolve conflict with enum method"));
|
||||||
|
}
|
||||||
|
} else if (isValuesMethod(mth, clsType)) {
|
||||||
|
if (!mth.getMethodInfo().getAlias().equals("values") && !mth.getUseIn().isEmpty()) {
|
||||||
|
// rename to use default values method
|
||||||
|
mth.getMethodInfo().setAlias("values");
|
||||||
|
mth.addAttr(new RenameReasonAttr(mth).append("to match enum method name"));
|
||||||
|
mth.add(AFlag.DONT_RENAME);
|
||||||
|
}
|
||||||
|
valuesMethod = mth;
|
||||||
|
mth.add(AFlag.DONT_GENERATE);
|
||||||
|
} else if (simpleValueOfMth(mth, clsType)) {
|
||||||
mth.add(AFlag.DONT_GENERATE);
|
mth.add(AFlag.DONT_GENERATE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
FieldInfo valuesFieldInfo = valuesField.getFieldInfo();
|
||||||
|
for (MethodNode mth : cls.getMethods()) {
|
||||||
|
// fix access to 'values' field and 'values()' method
|
||||||
|
fixValuesAccess(mth, valuesFieldInfo, clsType, valuesMethod);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void markArgsForSkip(MethodNode mth) {
|
private void markArgsForSkip(MethodNode mth) {
|
||||||
@@ -458,6 +525,25 @@ public class EnumVisitor extends AbstractVisitor {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: support other method patterns ???
|
||||||
|
private boolean isValuesMethod(MethodNode mth, ArgType clsType) {
|
||||||
|
ArgType retType = mth.getReturnType();
|
||||||
|
if (!retType.isArray() || !retType.getArrayElement().equals(clsType)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
InsnNode returnInsn = BlockUtils.getOnlyOneInsnFromMth(mth);
|
||||||
|
if (returnInsn == null || returnInsn.getType() != InsnType.RETURN || returnInsn.getArgsCount() != 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
InsnNode wrappedInsn = getWrappedInsn(getSingleArg(returnInsn));
|
||||||
|
IndexInsnNode castInsn = (IndexInsnNode) checkInsnType(wrappedInsn, InsnType.CHECK_CAST);
|
||||||
|
if (castInsn != null && Objects.equals(castInsn.getIndex(), ArgType.array(clsType))) {
|
||||||
|
InvokeNode invokeInsn = (InvokeNode) checkInsnType(getWrappedInsn(getSingleArg(castInsn)), InsnType.INVOKE);
|
||||||
|
return invokeInsn != null && invokeInsn.getCallMth().equals(cloneMth);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private boolean simpleValueOfMth(MethodNode mth, ArgType clsType) {
|
private boolean simpleValueOfMth(MethodNode mth, ArgType clsType) {
|
||||||
InsnNode returnInsn = InsnUtils.searchSingleReturnInsn(mth, insn -> insn.getArgsCount() == 1);
|
InsnNode returnInsn = InsnUtils.searchSingleReturnInsn(mth, insn -> insn.getArgsCount() == 1);
|
||||||
if (returnInsn == null) {
|
if (returnInsn == null) {
|
||||||
@@ -472,9 +558,41 @@ public class EnumVisitor extends AbstractVisitor {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean usesValuesField(MethodNode mth, FieldInfo valuesFieldInfo) {
|
private void fixValuesAccess(MethodNode mth, FieldInfo valuesFieldInfo, ArgType clsType, @Nullable MethodNode valuesMethod) {
|
||||||
|
MethodInfo mi = mth.getMethodInfo();
|
||||||
|
if (mi.isConstructor() || mi.isClassInit() || mth.isNoCode() || mth == valuesMethod) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// search value field usage
|
||||||
Predicate<InsnNode> insnTest = insn -> Objects.equals(((IndexInsnNode) insn).getIndex(), valuesFieldInfo);
|
Predicate<InsnNode> insnTest = insn -> Objects.equals(((IndexInsnNode) insn).getIndex(), valuesFieldInfo);
|
||||||
return InsnUtils.searchInsn(mth, InsnType.SGET, insnTest) != null;
|
InsnNode useInsn = InsnUtils.searchInsn(mth, InsnType.SGET, insnTest);
|
||||||
|
if (useInsn == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// replace 'values' field with 'values()' method
|
||||||
|
InsnUtils.replaceInsns(mth, insn -> {
|
||||||
|
if (insn.getType() == InsnType.SGET && insnTest.test(insn)) {
|
||||||
|
MethodInfo valueMth = valuesMethod == null
|
||||||
|
? getValueMthInfo(mth.root(), clsType)
|
||||||
|
: valuesMethod.getMethodInfo();
|
||||||
|
InvokeNode invokeNode = new InvokeNode(valueMth, InvokeType.STATIC, 0);
|
||||||
|
invokeNode.setResult(insn.getResult());
|
||||||
|
if (valuesMethod == null) {
|
||||||
|
// forcing enum method (can overlap and get renamed by custom method)
|
||||||
|
invokeNode.add(AFlag.FORCE_RAW_NAME);
|
||||||
|
}
|
||||||
|
mth.addDebugComment("Replace access to removed values field (" + valuesFieldInfo.getName() + ") with 'values()' method");
|
||||||
|
return invokeNode;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private MethodInfo getValueMthInfo(RootNode root, ArgType clsType) {
|
||||||
|
return MethodInfo.fromDetails(root,
|
||||||
|
ClassInfo.fromType(root, clsType),
|
||||||
|
"values",
|
||||||
|
Collections.emptyList(), ArgType.array(clsType));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void processEnumCls(ClassNode cls, EnumField field, ClassNode innerCls) {
|
private static void processEnumCls(ClassNode cls, EnumField field, ClassNode innerCls) {
|
||||||
@@ -524,4 +642,19 @@ public class EnumVisitor extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class EnumData {
|
||||||
|
final ClassNode cls;
|
||||||
|
final MethodNode classInitMth;
|
||||||
|
final List<BlockNode> staticBlocks;
|
||||||
|
final List<InsnNode> toRemove = new ArrayList<>();
|
||||||
|
FieldNode valuesField;
|
||||||
|
InsnNode valuesInitInsn;
|
||||||
|
|
||||||
|
public EnumData(ClassNode cls, MethodNode classInitMth, List<BlockNode> staticBlocks) {
|
||||||
|
this.cls = cls;
|
||||||
|
this.classInitMth = classInitMth;
|
||||||
|
this.staticBlocks = staticBlocks;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import jadx.core.dex.nodes.MethodNode;
|
|||||||
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
|
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
|
||||||
import jadx.core.utils.BlockUtils;
|
import jadx.core.utils.BlockUtils;
|
||||||
import jadx.core.utils.InsnRemover;
|
import jadx.core.utils.InsnRemover;
|
||||||
|
import jadx.core.utils.ListUtils;
|
||||||
import jadx.core.utils.Utils;
|
import jadx.core.utils.Utils;
|
||||||
import jadx.core.utils.exceptions.JadxException;
|
import jadx.core.utils.exceptions.JadxException;
|
||||||
|
|
||||||
@@ -45,20 +46,22 @@ public class ExtractFieldInit extends AbstractVisitor {
|
|||||||
for (ClassNode inner : cls.getInnerClasses()) {
|
for (ClassNode inner : cls.getInnerClasses()) {
|
||||||
visit(inner);
|
visit(inner);
|
||||||
}
|
}
|
||||||
moveStaticFieldsInit(cls);
|
if (!cls.getFields().isEmpty()) {
|
||||||
moveCommonFieldsInit(cls);
|
moveStaticFieldsInit(cls);
|
||||||
|
moveCommonFieldsInit(cls);
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class FieldInitInfo {
|
private static final class FieldInitInfo {
|
||||||
final FieldNode fieldNode;
|
final FieldNode fieldNode;
|
||||||
final IndexInsnNode putInsn;
|
final IndexInsnNode putInsn;
|
||||||
final boolean singlePath;
|
final boolean canMove;
|
||||||
|
|
||||||
public FieldInitInfo(FieldNode fieldNode, IndexInsnNode putInsn, boolean singlePath) {
|
public FieldInitInfo(FieldNode fieldNode, IndexInsnNode putInsn, boolean canMove) {
|
||||||
this.fieldNode = fieldNode;
|
this.fieldNode = fieldNode;
|
||||||
this.putInsn = putInsn;
|
this.putInsn = putInsn;
|
||||||
this.singlePath = singlePath;
|
this.canMove = canMove;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,6 +83,9 @@ public class ExtractFieldInit extends AbstractVisitor {
|
|||||||
|| classInitMth.getBasicBlocks() == null) {
|
|| classInitMth.getBasicBlocks() == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (ListUtils.noneMatch(cls.getFields(), FieldNode::isStatic)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
while (processStaticFields(cls, classInitMth)) {
|
while (processStaticFields(cls, classInitMth)) {
|
||||||
// sometimes instructions moved to field init prevent from vars inline -> inline and try again
|
// sometimes instructions moved to field init prevent from vars inline -> inline and try again
|
||||||
CodeShrinkVisitor.shrinkMethod(classInitMth);
|
CodeShrinkVisitor.shrinkMethod(classInitMth);
|
||||||
@@ -116,15 +122,15 @@ public class ExtractFieldInit extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void moveCommonFieldsInit(ClassNode cls) {
|
private static void moveCommonFieldsInit(ClassNode cls) {
|
||||||
|
if (ListUtils.noneMatch(cls.getFields(), FieldNode::isInstance)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
List<MethodNode> constructors = getConstructorsList(cls);
|
List<MethodNode> constructors = getConstructorsList(cls);
|
||||||
if (constructors.isEmpty()) {
|
if (constructors.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
List<ConstructorInitInfo> infoList = new ArrayList<>(constructors.size());
|
List<ConstructorInitInfo> infoList = new ArrayList<>(constructors.size());
|
||||||
for (MethodNode constructorMth : constructors) {
|
for (MethodNode constructorMth : constructors) {
|
||||||
if (constructorMth.isNoCode()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
List<FieldInitInfo> inits = collectFieldsInit(cls, constructorMth, InsnType.IPUT);
|
List<FieldInitInfo> inits = collectFieldsInit(cls, constructorMth, InsnType.IPUT);
|
||||||
filterFieldsInit(inits);
|
filterFieldsInit(inits);
|
||||||
if (inits.isEmpty()) {
|
if (inits.isEmpty()) {
|
||||||
@@ -168,19 +174,25 @@ public class ExtractFieldInit extends AbstractVisitor {
|
|||||||
Set<BlockNode> singlePathBlocks = new HashSet<>();
|
Set<BlockNode> singlePathBlocks = new HashSet<>();
|
||||||
BlockUtils.visitSinglePath(mth.getEnterBlock(), singlePathBlocks::add);
|
BlockUtils.visitSinglePath(mth.getEnterBlock(), singlePathBlocks::add);
|
||||||
|
|
||||||
|
boolean canReorder = true;
|
||||||
for (BlockNode block : mth.getBasicBlocks()) {
|
for (BlockNode block : mth.getBasicBlocks()) {
|
||||||
for (InsnNode insn : block.getInstructions()) {
|
for (InsnNode insn : block.getInstructions()) {
|
||||||
|
boolean fieldInsn = false;
|
||||||
if (insn.getType() == putType) {
|
if (insn.getType() == putType) {
|
||||||
IndexInsnNode putInsn = (IndexInsnNode) insn;
|
IndexInsnNode putInsn = (IndexInsnNode) insn;
|
||||||
FieldInfo field = (FieldInfo) putInsn.getIndex();
|
FieldInfo field = (FieldInfo) putInsn.getIndex();
|
||||||
if (field.getDeclClass().equals(cls.getClassInfo())) {
|
if (field.getDeclClass().equals(cls.getClassInfo())) {
|
||||||
FieldNode fn = cls.searchField(field);
|
FieldNode fn = cls.searchField(field);
|
||||||
if (fn != null) {
|
if (fn != null) {
|
||||||
boolean singlePath = singlePathBlocks.contains(block);
|
boolean canMove = canReorder && singlePathBlocks.contains(block);
|
||||||
fieldsInit.add(new FieldInitInfo(fn, putInsn, singlePath));
|
fieldsInit.add(new FieldInitInfo(fn, putInsn, canMove));
|
||||||
|
fieldInsn = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!fieldInsn && canReorder && !insn.canReorder()) {
|
||||||
|
canReorder = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return fieldsInit;
|
return fieldsInit;
|
||||||
@@ -226,14 +238,14 @@ public class ExtractFieldInit extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static boolean checkInsn(FieldInitInfo initInfo) {
|
private static boolean checkInsn(FieldInitInfo initInfo) {
|
||||||
if (!initInfo.singlePath) {
|
if (!initInfo.canMove) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
IndexInsnNode insn = initInfo.putInsn;
|
IndexInsnNode insn = initInfo.putInsn;
|
||||||
InsnArg arg = insn.getArg(0);
|
InsnArg arg = insn.getArg(0);
|
||||||
if (arg.isInsnWrap()) {
|
if (arg.isInsnWrap()) {
|
||||||
InsnNode wrapInsn = ((InsnWrapArg) arg).getWrapInsn();
|
InsnNode wrapInsn = ((InsnWrapArg) arg).getWrapInsn();
|
||||||
if (!wrapInsn.canReorderRecursive() && insn.contains(AType.EXC_CATCH)) {
|
if (!wrapInsn.canReorder() && insn.contains(AType.EXC_CATCH)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -364,7 +376,7 @@ public class ExtractFieldInit extends AbstractVisitor {
|
|||||||
AccessInfo accFlags = mth.getAccessFlags();
|
AccessInfo accFlags = mth.getAccessFlags();
|
||||||
if (!accFlags.isStatic() && accFlags.isConstructor()) {
|
if (!accFlags.isStatic() && accFlags.isConstructor()) {
|
||||||
list.add(mth);
|
list.add(mth);
|
||||||
if (BlockUtils.isAllBlocksEmpty(mth.getBasicBlocks())) {
|
if (mth.isNoCode() || BlockUtils.isAllBlocksEmpty(mth.getBasicBlocks())) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -372,8 +384,14 @@ public class ExtractFieldInit extends AbstractVisitor {
|
|||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void addFieldInitAttr(MethodNode mth, FieldNode field, InsnNode insn) {
|
private static void addFieldInitAttr(MethodNode mth, FieldNode field, IndexInsnNode putInsn) {
|
||||||
InsnNode assignInsn = InsnNode.wrapArg(insn.getArg(0));
|
InsnNode assignInsn;
|
||||||
|
InsnArg fldArg = putInsn.getArg(0);
|
||||||
|
if (fldArg.isInsnWrap()) {
|
||||||
|
assignInsn = ((InsnWrapArg) fldArg).getWrapInsn();
|
||||||
|
} else {
|
||||||
|
assignInsn = InsnNode.wrapArg(fldArg);
|
||||||
|
}
|
||||||
field.addAttr(new FieldInitInsnAttr(mth, assignInsn));
|
field.addAttr(new FieldInitInsnAttr(mth, assignInsn));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,13 +80,47 @@ public class MarkMethodsForInline extends AbstractVisitor {
|
|||||||
return addInlineAttr(mth, insn);
|
return addInlineAttr(mth, insn);
|
||||||
}
|
}
|
||||||
if (insnsCount == 2 && insns.get(1).getType() == InsnType.RETURN) {
|
if (insnsCount == 2 && insns.get(1).getType() == InsnType.RETURN) {
|
||||||
// synthetic field setter
|
InsnNode firstInsn = insns.get(0);
|
||||||
return addInlineAttr(mth, insns.get(0));
|
InsnNode retInsn = insns.get(1);
|
||||||
|
if (retInsn.getArgsCount() == 0
|
||||||
|
|| isSyntheticAccessPattern(mth, firstInsn, retInsn)) {
|
||||||
|
return addInlineAttr(mth, firstInsn);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// TODO: inline field arithmetics. Disabled tests: TestAnonymousClass3a and TestAnonymousClass5
|
// TODO: inline field arithmetics. Disabled tests: TestAnonymousClass3a and TestAnonymousClass5
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean isSyntheticAccessPattern(MethodNode mth, InsnNode firstInsn, InsnNode retInsn) {
|
||||||
|
List<RegisterArg> mthRegs = mth.getArgRegs();
|
||||||
|
switch (firstInsn.getType()) {
|
||||||
|
case IGET:
|
||||||
|
return mthRegs.size() == 1
|
||||||
|
&& retInsn.getArg(0).isSameVar(firstInsn.getResult())
|
||||||
|
&& firstInsn.getArg(0).isSameVar(mthRegs.get(0));
|
||||||
|
case SGET:
|
||||||
|
return mthRegs.size() == 0
|
||||||
|
&& retInsn.getArg(0).isSameVar(firstInsn.getResult());
|
||||||
|
|
||||||
|
case IPUT:
|
||||||
|
return mthRegs.size() == 2
|
||||||
|
&& retInsn.getArg(0).isSameVar(mthRegs.get(1))
|
||||||
|
&& firstInsn.getArg(0).isSameVar(mthRegs.get(1))
|
||||||
|
&& firstInsn.getArg(1).isSameVar(mthRegs.get(0));
|
||||||
|
case SPUT:
|
||||||
|
return mthRegs.size() == 1
|
||||||
|
&& retInsn.getArg(0).isSameVar(mthRegs.get(0))
|
||||||
|
&& firstInsn.getArg(0).isSameVar(mthRegs.get(0));
|
||||||
|
|
||||||
|
case INVOKE:
|
||||||
|
return mthRegs.size() >= 1
|
||||||
|
&& firstInsn.getArg(0).isSameVar(mthRegs.get(0))
|
||||||
|
&& retInsn.getArg(0).isSameVar(firstInsn.getResult());
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static MethodInlineAttr addInlineAttr(MethodNode mth, InsnNode insn) {
|
private static MethodInlineAttr addInlineAttr(MethodNode mth, InsnNode insn) {
|
||||||
if (!fixVisibilityOfInlineCode(mth, insn)) {
|
if (!fixVisibilityOfInlineCode(mth, insn)) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ public class OverrideMethodVisitor extends AbstractVisitor {
|
|||||||
for (ArgType superType : superData.getSuperTypes()) {
|
for (ArgType superType : superData.getSuperTypes()) {
|
||||||
ClassNode classNode = mth.root().resolveClass(superType);
|
ClassNode classNode = mth.root().resolveClass(superType);
|
||||||
if (classNode != null) {
|
if (classNode != null) {
|
||||||
MethodNode ovrdMth = searchOverriddenMethod(classNode, signature);
|
MethodNode ovrdMth = searchOverriddenMethod(classNode, mth, signature);
|
||||||
if (ovrdMth != null) {
|
if (ovrdMth != null) {
|
||||||
if (isMethodVisibleInCls(ovrdMth, cls)) {
|
if (isMethodVisibleInCls(ovrdMth, cls)) {
|
||||||
overrideList.add(ovrdMth);
|
overrideList.add(ovrdMth);
|
||||||
@@ -107,6 +107,8 @@ public class OverrideMethodVisitor extends AbstractVisitor {
|
|||||||
Map<String, ClspMethod> methodsMap = clsDetails.getMethodsMap();
|
Map<String, ClspMethod> methodsMap = clsDetails.getMethodsMap();
|
||||||
for (Map.Entry<String, ClspMethod> entry : methodsMap.entrySet()) {
|
for (Map.Entry<String, ClspMethod> entry : methodsMap.entrySet()) {
|
||||||
String mthShortId = entry.getKey();
|
String mthShortId = entry.getKey();
|
||||||
|
// do not check full signature, classpath methods can be trusted
|
||||||
|
// i.e. doesn't contain methods with same signature in one class
|
||||||
if (mthShortId.startsWith(signature)) {
|
if (mthShortId.startsWith(signature)) {
|
||||||
overrideList.add(entry.getValue());
|
overrideList.add(entry.getValue());
|
||||||
break;
|
break;
|
||||||
@@ -130,12 +132,30 @@ public class OverrideMethodVisitor extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private MethodNode searchOverriddenMethod(ClassNode cls, String signature) {
|
private MethodNode searchOverriddenMethod(ClassNode cls, MethodNode mth, String signature) {
|
||||||
|
// search by exact full signature (with return value) to fight obfuscation (see test
|
||||||
|
// 'TestOverrideWithSameName')
|
||||||
|
String shortId = mth.getMethodInfo().getShortId();
|
||||||
for (MethodNode supMth : cls.getMethods()) {
|
for (MethodNode supMth : cls.getMethods()) {
|
||||||
if (!supMth.getAccessFlags().isStatic() && supMth.getMethodInfo().getShortId().startsWith(signature)) {
|
if (supMth.getMethodInfo().getShortId().equals(shortId) && !supMth.getAccessFlags().isStatic()) {
|
||||||
return supMth;
|
return supMth;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// search by signature without return value and check if return value is wider type
|
||||||
|
for (MethodNode supMth : cls.getMethods()) {
|
||||||
|
if (supMth.getMethodInfo().getShortId().startsWith(signature) && !supMth.getAccessFlags().isStatic()) {
|
||||||
|
TypeCompare typeCompare = cls.root().getTypeCompare();
|
||||||
|
ArgType supRetType = supMth.getMethodInfo().getReturnType();
|
||||||
|
ArgType mthRetType = mth.getMethodInfo().getReturnType();
|
||||||
|
TypeCompareEnum res = typeCompare.compareTypes(supRetType, mthRetType);
|
||||||
|
if (res.isWider()) {
|
||||||
|
return supMth;
|
||||||
|
}
|
||||||
|
if (res == TypeCompareEnum.UNKNOWN || res == TypeCompareEnum.CONFLICT) {
|
||||||
|
mth.addDebugComment("Possible override for method " + supMth.getMethodInfo().getFullId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,9 +10,11 @@ import java.util.Set;
|
|||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import jadx.api.plugins.input.data.AccessFlags;
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
import jadx.core.dex.attributes.AType;
|
import jadx.core.dex.attributes.AType;
|
||||||
import jadx.core.dex.attributes.nodes.AnonymousClassAttr;
|
import jadx.core.dex.attributes.nodes.AnonymousClassAttr;
|
||||||
|
import jadx.core.dex.attributes.nodes.AnonymousClassAttr.InlineType;
|
||||||
import jadx.core.dex.info.AccessInfo;
|
import jadx.core.dex.info.AccessInfo;
|
||||||
import jadx.core.dex.instructions.args.ArgType;
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
@@ -30,6 +32,7 @@ import jadx.core.utils.exceptions.JadxException;
|
|||||||
UsageInfoVisitor.class
|
UsageInfoVisitor.class
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||||
public class ProcessAnonymous extends AbstractVisitor {
|
public class ProcessAnonymous extends AbstractVisitor {
|
||||||
|
|
||||||
private boolean inlineAnonymousClasses;
|
private boolean inlineAnonymousClasses;
|
||||||
@@ -64,17 +67,26 @@ public class ProcessAnonymous extends AbstractVisitor {
|
|||||||
if (!canBeAnonymous(cls)) {
|
if (!canBeAnonymous(cls)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
MethodNode anonymousConstructor = checkUsage(cls);
|
MethodNode anonymousConstructor = ListUtils.filterOnlyOne(cls.getMethods(), MethodNode::isConstructor);
|
||||||
if (anonymousConstructor == null) {
|
if (anonymousConstructor == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
InlineType inlineType = checkUsage(cls, anonymousConstructor);
|
||||||
|
if (inlineType == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
ArgType baseType = getBaseType(cls);
|
ArgType baseType = getBaseType(cls);
|
||||||
if (baseType == null) {
|
if (baseType == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ClassNode outerCls = anonymousConstructor.getUseIn().get(0).getParentClass();
|
ClassNode outerCls;
|
||||||
|
if (inlineType == InlineType.INSTANCE_FIELD) {
|
||||||
|
outerCls = cls.getUseInMth().get(0).getParentClass();
|
||||||
|
} else {
|
||||||
|
outerCls = anonymousConstructor.getUseIn().get(0).getParentClass();
|
||||||
|
}
|
||||||
outerCls.addInlinedClass(cls);
|
outerCls.addInlinedClass(cls);
|
||||||
cls.addAttr(new AnonymousClassAttr(outerCls, baseType));
|
cls.addAttr(new AnonymousClassAttr(outerCls, baseType, inlineType));
|
||||||
cls.add(AFlag.DONT_GENERATE);
|
cls.add(AFlag.DONT_GENERATE);
|
||||||
anonymousConstructor.add(AFlag.ANONYMOUS_CONSTRUCTOR);
|
anonymousConstructor.add(AFlag.ANONYMOUS_CONSTRUCTOR);
|
||||||
|
|
||||||
@@ -202,14 +214,11 @@ public class ProcessAnonymous extends AbstractVisitor {
|
|||||||
* Checks:
|
* Checks:
|
||||||
* - class have only one constructor which used only once (allow common code for field init)
|
* - class have only one constructor which used only once (allow common code for field init)
|
||||||
* - methods or fields not used outside (allow only nested inner classes with synthetic usage)
|
* - methods or fields not used outside (allow only nested inner classes with synthetic usage)
|
||||||
|
* - if constructor used only in class init check if possible inline by instance field
|
||||||
*
|
*
|
||||||
* @return anonymous constructor method
|
* @return decided inline type
|
||||||
*/
|
*/
|
||||||
private static MethodNode checkUsage(ClassNode cls) {
|
private static InlineType checkUsage(ClassNode cls, MethodNode ctr) {
|
||||||
MethodNode ctr = ListUtils.filterOnlyOne(cls.getMethods(), MethodNode::isConstructor);
|
|
||||||
if (ctr == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (ctr.getUseIn().size() != 1) {
|
if (ctr.getUseIn().size() != 1) {
|
||||||
// check if used in common field init in all constructors
|
// check if used in common field init in all constructors
|
||||||
if (!checkForCommonFieldInit(ctr)) {
|
if (!checkForCommonFieldInit(ctr)) {
|
||||||
@@ -219,9 +228,30 @@ public class ProcessAnonymous extends AbstractVisitor {
|
|||||||
MethodNode ctrUseMth = ctr.getUseIn().get(0);
|
MethodNode ctrUseMth = ctr.getUseIn().get(0);
|
||||||
ClassNode ctrUseCls = ctrUseMth.getParentClass();
|
ClassNode ctrUseCls = ctrUseMth.getParentClass();
|
||||||
if (ctrUseCls.equals(cls)) {
|
if (ctrUseCls.equals(cls)) {
|
||||||
|
if (checkForInstanceFieldUsage(cls, ctr)) {
|
||||||
|
return InlineType.INSTANCE_FIELD;
|
||||||
|
}
|
||||||
// exclude self usage
|
// exclude self usage
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
if (ctrUseCls.getTopParentClass().equals(cls)) {
|
||||||
|
// exclude usage inside inner classes
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!checkMethodsUsage(cls, ctr, ctrUseMth)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (FieldNode field : cls.getFields()) {
|
||||||
|
for (MethodNode useMth : field.getUseIn()) {
|
||||||
|
if (badMethodUsage(cls, useMth, field.getAccessFlags())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return InlineType.CONSTRUCTOR;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean checkMethodsUsage(ClassNode cls, MethodNode ctr, MethodNode ctrUseMth) {
|
||||||
for (MethodNode mth : cls.getMethods()) {
|
for (MethodNode mth : cls.getMethods()) {
|
||||||
if (mth == ctr) {
|
if (mth == ctr) {
|
||||||
continue;
|
continue;
|
||||||
@@ -231,18 +261,46 @@ public class ProcessAnonymous extends AbstractVisitor {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (badMethodUsage(cls, useMth, mth.getAccessFlags())) {
|
if (badMethodUsage(cls, useMth, mth.getAccessFlags())) {
|
||||||
return null;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean checkForInstanceFieldUsage(ClassNode cls, MethodNode ctr) {
|
||||||
|
MethodNode ctrUseMth = ctr.getUseIn().get(0);
|
||||||
|
if (!ctrUseMth.getMethodInfo().isClassInit()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
FieldNode instFld = ListUtils.filterOnlyOne(cls.getFields(),
|
||||||
|
f -> f.getAccessFlags().containsFlags(AccessFlags.PUBLIC, AccessFlags.STATIC, AccessFlags.FINAL)
|
||||||
|
&& f.getFieldInfo().getType().equals(cls.getClassInfo().getType()));
|
||||||
|
if (instFld == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
List<MethodNode> instFldUseIn = instFld.getUseIn();
|
||||||
|
if (instFldUseIn.size() != 2
|
||||||
|
|| !instFldUseIn.contains(ctrUseMth) // initialized in class init
|
||||||
|
|| !instFldUseIn.containsAll(cls.getUseInMth()) // class used only with this field
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!checkMethodsUsage(cls, ctr, ctrUseMth)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
for (FieldNode field : cls.getFields()) {
|
for (FieldNode field : cls.getFields()) {
|
||||||
|
if (field == instFld) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
for (MethodNode useMth : field.getUseIn()) {
|
for (MethodNode useMth : field.getUseIn()) {
|
||||||
if (badMethodUsage(cls, useMth, field.getAccessFlags())) {
|
if (badMethodUsage(cls, useMth, field.getAccessFlags())) {
|
||||||
return null;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ctr;
|
instFld.add(AFlag.INLINE_INSTANCE_FIELD);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean badMethodUsage(ClassNode cls, MethodNode useMth, AccessInfo accessFlags) {
|
private static boolean badMethodUsage(ClassNode cls, MethodNode useMth, AccessInfo accessFlags) {
|
||||||
@@ -293,6 +351,13 @@ public class ProcessAnonymous extends AbstractVisitor {
|
|||||||
if (cls.root().getClsp().isImplements(superCls.getObject(), interfaceType.getObject())) {
|
if (cls.root().getClsp().isImplements(superCls.getObject(), interfaceType.getObject())) {
|
||||||
return superCls;
|
return superCls;
|
||||||
}
|
}
|
||||||
|
if (cls.root().getArgs().isAllowInlineKotlinLambda()) {
|
||||||
|
if (superCls.getObject().equals("kotlin.jvm.internal.Lambda")) {
|
||||||
|
// Inline such class with have different semantic: missing 'arity' property.
|
||||||
|
// For now, it is unclear how it may affect code execution.
|
||||||
|
return interfaceType;
|
||||||
|
}
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
package jadx.core.dex.visitors;
|
package jadx.core.dex.visitors;
|
||||||
|
|
||||||
|
import jadx.core.Consts;
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
import jadx.core.dex.info.AccessInfo;
|
import jadx.core.dex.info.AccessInfo;
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
import jadx.core.dex.nodes.RootNode;
|
import jadx.core.dex.nodes.RootNode;
|
||||||
import jadx.core.dex.visitors.usage.UsageInfoVisitor;
|
import jadx.core.dex.visitors.usage.UsageInfoVisitor;
|
||||||
import jadx.core.utils.ListUtils;
|
|
||||||
import jadx.core.utils.exceptions.JadxException;
|
import jadx.core.utils.exceptions.JadxException;
|
||||||
|
|
||||||
@JadxVisitor(
|
@JadxVisitor(
|
||||||
@@ -45,7 +45,14 @@ public class ProcessMethodsForInline extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
AccessInfo accessFlags = mth.getAccessFlags();
|
AccessInfo accessFlags = mth.getAccessFlags();
|
||||||
boolean isSynthetic = accessFlags.isSynthetic() || mth.getName().contains("$");
|
boolean isSynthetic = accessFlags.isSynthetic() || mth.getName().contains("$");
|
||||||
return isSynthetic && (accessFlags.isStatic() || mth.isConstructor());
|
return isSynthetic && canInlineMethod(mth, accessFlags);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean canInlineMethod(MethodNode mth, AccessInfo accessFlags) {
|
||||||
|
if (accessFlags.isStatic()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return mth.isConstructor() && mth.root().getArgs().isInlineAnonymousClasses();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void fixClassDependencies(MethodNode mth) {
|
private static void fixClassDependencies(MethodNode mth) {
|
||||||
@@ -54,8 +61,14 @@ public class ProcessMethodsForInline extends AbstractVisitor {
|
|||||||
// remove possible cross dependency
|
// remove possible cross dependency
|
||||||
// to force class with inline method to be processed before its usage
|
// to force class with inline method to be processed before its usage
|
||||||
ClassNode useTopCls = useInMth.getTopParentClass();
|
ClassNode useTopCls = useInMth.getTopParentClass();
|
||||||
parentClass.setDependencies(ListUtils.safeRemoveAndTrim(parentClass.getDependencies(), useTopCls));
|
if (useTopCls != parentClass) {
|
||||||
useTopCls.addCodegenDep(parentClass);
|
parentClass.removeDependency(useTopCls);
|
||||||
|
useTopCls.addCodegenDep(parentClass);
|
||||||
|
if (Consts.DEBUG_USAGE) {
|
||||||
|
parentClass.addDebugComment("Remove dependency: " + useTopCls + " to inline " + mth);
|
||||||
|
useTopCls.addDebugComment("Add dependency: " + parentClass + " to inline " + mth);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,12 @@ package jadx.core.dex.visitors;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import jadx.core.dex.info.MethodInfo;
|
import jadx.core.dex.info.MethodInfo;
|
||||||
import jadx.core.dex.instructions.args.ArgType;
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
@@ -18,7 +22,6 @@ import jadx.core.utils.Utils;
|
|||||||
import jadx.core.utils.exceptions.JadxException;
|
import jadx.core.utils.exceptions.JadxException;
|
||||||
|
|
||||||
public class SignatureProcessor extends AbstractVisitor {
|
public class SignatureProcessor extends AbstractVisitor {
|
||||||
|
|
||||||
private RootNode root;
|
private RootNode root;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -55,12 +58,54 @@ public class SignatureProcessor extends AbstractVisitor {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cls.updateGenericClsData(superClass, interfaces, generics);
|
generics = fixTypeParamDeclarations(cls, generics, superClass, interfaces);
|
||||||
|
cls.updateGenericClsData(generics, superClass, interfaces);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
cls.addWarnComment("Failed to parse class signature: " + sp.getSignature(), e);
|
cls.addWarnComment("Failed to parse class signature: " + sp.getSignature(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add missing type parameters from super type and interfaces to make code compilable
|
||||||
|
*/
|
||||||
|
private static List<ArgType> fixTypeParamDeclarations(ClassNode cls,
|
||||||
|
List<ArgType> generics, ArgType superClass, List<ArgType> interfaces) {
|
||||||
|
if (interfaces.isEmpty() && superClass.equals(ArgType.OBJECT)) {
|
||||||
|
return generics;
|
||||||
|
}
|
||||||
|
Set<String> typeParams = new HashSet<>();
|
||||||
|
superClass.visitTypes(t -> addGenericType(typeParams, t));
|
||||||
|
interfaces.forEach(i -> i.visitTypes(t -> addGenericType(typeParams, t)));
|
||||||
|
if (typeParams.isEmpty()) {
|
||||||
|
return generics;
|
||||||
|
}
|
||||||
|
List<ArgType> knownTypeParams;
|
||||||
|
if (cls.isInner()) {
|
||||||
|
knownTypeParams = new ArrayList<>(generics);
|
||||||
|
cls.visitParentClasses(p -> knownTypeParams.addAll(p.getGenericTypeParameters()));
|
||||||
|
} else {
|
||||||
|
knownTypeParams = generics;
|
||||||
|
}
|
||||||
|
for (ArgType declTypeParam : knownTypeParams) {
|
||||||
|
typeParams.remove(declTypeParam.getObject());
|
||||||
|
}
|
||||||
|
if (typeParams.isEmpty()) {
|
||||||
|
return generics;
|
||||||
|
}
|
||||||
|
cls.addInfoComment("Add missing generic type declarations: " + typeParams);
|
||||||
|
List<ArgType> fixedGenerics = new ArrayList<>(generics.size() + typeParams.size());
|
||||||
|
fixedGenerics.addAll(generics);
|
||||||
|
typeParams.stream().sorted().map(ArgType::genericType).forEach(fixedGenerics::add);
|
||||||
|
return fixedGenerics;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static @Nullable Object addGenericType(Set<String> usedTypeParameters, ArgType t) {
|
||||||
|
if (t.isGenericType()) {
|
||||||
|
usedTypeParameters.add(t.getObject());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private ArgType validateClsType(ClassNode cls, ArgType candidateType, ArgType currentType) {
|
private ArgType validateClsType(ClassNode cls, ArgType candidateType, ArgType currentType) {
|
||||||
if (!candidateType.isObject()) {
|
if (!candidateType.isObject()) {
|
||||||
cls.addWarnComment("Incorrect class signature, class is not object: " + SignatureParser.getSignature(cls));
|
cls.addWarnComment("Incorrect class signature, class is not object: " + SignatureParser.getSignature(cls));
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ public class BlockExceptionHandler {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
BlockProcessor.updateCleanSuccessors(mth);
|
BlockProcessor.updateCleanSuccessors(mth);
|
||||||
BlockProcessor.computeDominanceFrontier(mth);
|
DominatorTree.computeDominanceFrontier(mth);
|
||||||
|
|
||||||
processCatchAttr(mth);
|
processCatchAttr(mth);
|
||||||
initExcHandlers(mth);
|
initExcHandlers(mth);
|
||||||
@@ -171,10 +171,6 @@ public class BlockExceptionHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static void removeTmpConnections(MethodNode mth) {
|
|
||||||
mth.getBasicBlocks().forEach(BlockExceptionHandler::removeTmpConnection);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void removeTmpConnection(BlockNode block) {
|
private static void removeTmpConnection(BlockNode block) {
|
||||||
TmpEdgeAttr tmpEdgeAttr = block.get(AType.TMP_EDGE);
|
TmpEdgeAttr tmpEdgeAttr = block.get(AType.TMP_EDGE);
|
||||||
if (tmpEdgeAttr != null) {
|
if (tmpEdgeAttr != null) {
|
||||||
@@ -402,6 +398,13 @@ public class BlockExceptionHandler {
|
|||||||
}
|
}
|
||||||
BlockNode topDom = BlockUtils.getCommonDominator(mth, blocks);
|
BlockNode topDom = BlockUtils.getCommonDominator(mth, blocks);
|
||||||
if (topDom != null) {
|
if (topDom != null) {
|
||||||
|
// dominator always return one up block if blocks already contains dominator, use successor instead
|
||||||
|
if (topDom.getSuccessors().size() == 1) {
|
||||||
|
BlockNode upBlock = topDom.getSuccessors().get(0);
|
||||||
|
if (blocks.contains(upBlock)) {
|
||||||
|
return upBlock;
|
||||||
|
}
|
||||||
|
}
|
||||||
return adjustTopBlock(topDom);
|
return adjustTopBlock(topDom);
|
||||||
}
|
}
|
||||||
throw new JadxRuntimeException("Failed to find top block for try-catch from: " + blocks);
|
throw new JadxRuntimeException("Failed to find top block for try-catch from: " + blocks);
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
package jadx.core.dex.visitors.blocks;
|
package jadx.core.dex.visitors.blocks;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.BitSet;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Deque;
|
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
@@ -31,7 +27,6 @@ import jadx.core.utils.Utils;
|
|||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
import static jadx.core.dex.visitors.blocks.BlockSplitter.connect;
|
import static jadx.core.dex.visitors.blocks.BlockSplitter.connect;
|
||||||
import static jadx.core.utils.EmptyBitSet.EMPTY;
|
|
||||||
|
|
||||||
public class BlockProcessor extends AbstractVisitor {
|
public class BlockProcessor extends AbstractVisitor {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(BlockProcessor.class);
|
private static final Logger LOG = LoggerFactory.getLogger(BlockProcessor.class);
|
||||||
@@ -50,29 +45,23 @@ public class BlockProcessor extends AbstractVisitor {
|
|||||||
computeDominators(mth);
|
computeDominators(mth);
|
||||||
if (independentBlockTreeMod(mth)) {
|
if (independentBlockTreeMod(mth)) {
|
||||||
checkForUnreachableBlocks(mth);
|
checkForUnreachableBlocks(mth);
|
||||||
clearBlocksState(mth);
|
|
||||||
computeDominators(mth);
|
computeDominators(mth);
|
||||||
}
|
}
|
||||||
if (FixMultiEntryLoops.process(mth)) {
|
if (FixMultiEntryLoops.process(mth)) {
|
||||||
clearBlocksState(mth);
|
|
||||||
computeDominators(mth);
|
computeDominators(mth);
|
||||||
}
|
}
|
||||||
updateCleanSuccessors(mth);
|
updateCleanSuccessors(mth);
|
||||||
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
while (modifyBlocksTree(mth)) {
|
while (modifyBlocksTree(mth)) {
|
||||||
// revert calculations
|
|
||||||
clearBlocksState(mth);
|
|
||||||
// recalculate dominators tree
|
|
||||||
computeDominators(mth);
|
computeDominators(mth);
|
||||||
|
|
||||||
if (i++ > 100) {
|
if (i++ > 100) {
|
||||||
throw new JadxRuntimeException("CFG modification limit reached, blocks count: " + mth.getBasicBlocks().size());
|
throw new JadxRuntimeException("CFG modification limit reached, blocks count: " + mth.getBasicBlocks().size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
checkForUnreachableBlocks(mth);
|
checkForUnreachableBlocks(mth);
|
||||||
|
|
||||||
computeDominanceFrontier(mth);
|
DominatorTree.computeDominanceFrontier(mth);
|
||||||
registerLoops(mth);
|
registerLoops(mth);
|
||||||
processNestedLoops(mth);
|
processNestedLoops(mth);
|
||||||
|
|
||||||
@@ -209,139 +198,9 @@ public class BlockProcessor extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void computeDominators(MethodNode mth) {
|
private static void computeDominators(MethodNode mth) {
|
||||||
List<BlockNode> basicBlocks = mth.getBasicBlocks();
|
clearBlocksState(mth);
|
||||||
int nBlocks = basicBlocks.size();
|
DominatorTree.compute(mth);
|
||||||
for (int i = 0; i < nBlocks; i++) {
|
|
||||||
BlockNode block = basicBlocks.get(i);
|
|
||||||
block.setId(i);
|
|
||||||
block.setDoms(new BitSet(nBlocks));
|
|
||||||
block.getDoms().set(0, nBlocks);
|
|
||||||
}
|
|
||||||
|
|
||||||
BlockNode entryBlock = mth.getEnterBlock();
|
|
||||||
calcDominators(basicBlocks, entryBlock);
|
|
||||||
markLoops(mth);
|
markLoops(mth);
|
||||||
|
|
||||||
// clear self dominance
|
|
||||||
basicBlocks.forEach(block -> {
|
|
||||||
block.getDoms().clear(block.getId());
|
|
||||||
if (block.getDoms().isEmpty()) {
|
|
||||||
block.setDoms(EMPTY);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
calcImmediateDominators(mth, basicBlocks, entryBlock);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void calcDominators(List<BlockNode> basicBlocks, BlockNode entryBlock) {
|
|
||||||
entryBlock.getDoms().clear();
|
|
||||||
entryBlock.getDoms().set(entryBlock.getId());
|
|
||||||
|
|
||||||
BitSet domSet = new BitSet(basicBlocks.size());
|
|
||||||
boolean changed;
|
|
||||||
do {
|
|
||||||
changed = false;
|
|
||||||
for (BlockNode block : basicBlocks) {
|
|
||||||
if (block == entryBlock) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
BitSet d = block.getDoms();
|
|
||||||
if (!changed) {
|
|
||||||
domSet.clear();
|
|
||||||
domSet.or(d);
|
|
||||||
}
|
|
||||||
for (BlockNode pred : block.getPredecessors()) {
|
|
||||||
d.and(pred.getDoms());
|
|
||||||
}
|
|
||||||
d.set(block.getId());
|
|
||||||
if (!changed && !d.equals(domSet)) {
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} while (changed);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void calcImmediateDominators(MethodNode mth, List<BlockNode> basicBlocks, BlockNode entryBlock) {
|
|
||||||
for (BlockNode block : basicBlocks) {
|
|
||||||
if (block == entryBlock) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
BlockNode idom;
|
|
||||||
List<BlockNode> preds = block.getPredecessors();
|
|
||||||
if (preds.size() == 1) {
|
|
||||||
idom = preds.get(0);
|
|
||||||
} else {
|
|
||||||
BitSet bs = new BitSet(block.getDoms().length());
|
|
||||||
bs.or(block.getDoms());
|
|
||||||
for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i + 1)) {
|
|
||||||
BlockNode dom = basicBlocks.get(i);
|
|
||||||
bs.andNot(dom.getDoms());
|
|
||||||
}
|
|
||||||
if (bs.cardinality() != 1) {
|
|
||||||
throw new JadxRuntimeException("Can't find immediate dominator for block " + block
|
|
||||||
+ " in " + bs + " preds:" + preds);
|
|
||||||
}
|
|
||||||
idom = basicBlocks.get(bs.nextSetBit(0));
|
|
||||||
}
|
|
||||||
block.setIDom(idom);
|
|
||||||
idom.addDominatesOn(block);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void computeDominanceFrontier(MethodNode mth) {
|
|
||||||
mth.getExitBlock().setDomFrontier(EMPTY);
|
|
||||||
List<BlockNode> domSortedBlocks = new ArrayList<>(mth.getBasicBlocks().size());
|
|
||||||
Deque<BlockNode> stack = new LinkedList<>();
|
|
||||||
stack.push(mth.getEnterBlock());
|
|
||||||
while (!stack.isEmpty()) {
|
|
||||||
BlockNode node = stack.pop();
|
|
||||||
for (BlockNode dominated : node.getDominatesOn()) {
|
|
||||||
stack.push(dominated);
|
|
||||||
}
|
|
||||||
domSortedBlocks.add(node);
|
|
||||||
}
|
|
||||||
Collections.reverse(domSortedBlocks);
|
|
||||||
for (BlockNode block : domSortedBlocks) {
|
|
||||||
try {
|
|
||||||
computeBlockDF(mth, block);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new JadxRuntimeException("Failed compute block dominance frontier", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void computeBlockDF(MethodNode mth, BlockNode block) {
|
|
||||||
if (block.getDomFrontier() != null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
List<BlockNode> blocks = mth.getBasicBlocks();
|
|
||||||
BitSet domFrontier = null;
|
|
||||||
for (BlockNode s : block.getSuccessors()) {
|
|
||||||
if (s.getIDom() != block) {
|
|
||||||
if (domFrontier == null) {
|
|
||||||
domFrontier = new BitSet(blocks.size());
|
|
||||||
}
|
|
||||||
domFrontier.set(s.getId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (BlockNode c : block.getDominatesOn()) {
|
|
||||||
BitSet frontier = c.getDomFrontier();
|
|
||||||
if (frontier == null) {
|
|
||||||
throw new JadxRuntimeException("Dominance frontier not calculated for dominated block: " + c + ", from: " + block);
|
|
||||||
}
|
|
||||||
for (int p = frontier.nextSetBit(0); p >= 0; p = frontier.nextSetBit(p + 1)) {
|
|
||||||
if (blocks.get(p).getIDom() != block) {
|
|
||||||
if (domFrontier == null) {
|
|
||||||
domFrontier = new BitSet(blocks.size());
|
|
||||||
}
|
|
||||||
domFrontier.set(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (domFrontier == null || domFrontier.isEmpty()) {
|
|
||||||
domFrontier = EMPTY;
|
|
||||||
}
|
|
||||||
block.setDomFrontier(domFrontier);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void markLoops(MethodNode mth) {
|
private static void markLoops(MethodNode mth) {
|
||||||
@@ -349,7 +208,7 @@ public class BlockProcessor extends AbstractVisitor {
|
|||||||
// Every successor that dominates its predecessor is a header of a loop,
|
// Every successor that dominates its predecessor is a header of a loop,
|
||||||
// block -> successor is a back edge.
|
// block -> successor is a back edge.
|
||||||
block.getSuccessors().forEach(successor -> {
|
block.getSuccessors().forEach(successor -> {
|
||||||
if (block.getDoms().get(successor.getId())) {
|
if (block.getDoms().get(successor.getId()) || block == successor) {
|
||||||
successor.add(AFlag.LOOP_START);
|
successor.add(AFlag.LOOP_START);
|
||||||
block.add(AFlag.LOOP_END);
|
block.add(AFlag.LOOP_END);
|
||||||
|
|
||||||
@@ -572,7 +431,7 @@ public class BlockProcessor extends AbstractVisitor {
|
|||||||
BlockNode loopHeader = loop.getStart();
|
BlockNode loopHeader = loop.getStart();
|
||||||
List<BlockNode> preds = loopHeader.getPredecessors();
|
List<BlockNode> preds = loopHeader.getPredecessors();
|
||||||
if (preds.size() > 2) {
|
if (preds.size() > 2) {
|
||||||
List<BlockNode> blocks = new LinkedList<>(preds);
|
List<BlockNode> blocks = new ArrayList<>(preds);
|
||||||
blocks.removeIf(block -> block.contains(AFlag.LOOP_END));
|
blocks.removeIf(block -> block.contains(AFlag.LOOP_END));
|
||||||
BlockNode first = blocks.remove(0);
|
BlockNode first = blocks.remove(0);
|
||||||
BlockNode preHeader = BlockSplitter.insertBlockBetween(mth, first, loopHeader);
|
BlockNode preHeader = BlockSplitter.insertBlockBetween(mth, first, loopHeader);
|
||||||
|
|||||||
@@ -137,8 +137,9 @@ public class BlockSplitter extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static BlockNode startNewBlock(MethodNode mth, int offset) {
|
static BlockNode startNewBlock(MethodNode mth, int offset) {
|
||||||
BlockNode block = new BlockNode(mth.getBasicBlocks().size(), offset);
|
List<BlockNode> blocks = mth.getBasicBlocks();
|
||||||
mth.getBasicBlocks().add(block);
|
BlockNode block = new BlockNode(mth.getNextBlockCId(), blocks.size(), offset);
|
||||||
|
blocks.add(block);
|
||||||
return block;
|
return block;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -391,7 +392,8 @@ public class BlockSplitter extends AbstractVisitor {
|
|||||||
&& block.getSuccessors().size() <= 1
|
&& block.getSuccessors().size() <= 1
|
||||||
&& !block.getPredecessors().isEmpty()
|
&& !block.getPredecessors().isEmpty()
|
||||||
&& !block.contains(AFlag.MTH_ENTER_BLOCK)
|
&& !block.contains(AFlag.MTH_ENTER_BLOCK)
|
||||||
&& !block.contains(AFlag.MTH_EXIT_BLOCK);
|
&& !block.contains(AFlag.MTH_EXIT_BLOCK)
|
||||||
|
&& !block.getSuccessors().contains(block); // no self loop
|
||||||
}
|
}
|
||||||
|
|
||||||
static void collectSuccessors(BlockNode startBlock, BlockNode methodEnterBlock, Set<BlockNode> toRemove) {
|
static void collectSuccessors(BlockNode startBlock, BlockNode methodEnterBlock, Set<BlockNode> toRemove) {
|
||||||
|
|||||||
@@ -0,0 +1,168 @@
|
|||||||
|
package jadx.core.dex.visitors.blocks;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.BitSet;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import jadx.core.dex.nodes.BlockNode;
|
||||||
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
|
import jadx.core.utils.BlockUtils;
|
||||||
|
import jadx.core.utils.EmptyBitSet;
|
||||||
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build dominator tree based on the algorithm described in paper:
|
||||||
|
* Cooper, Keith D.; Harvey, Timothy J; Kennedy, Ken (2001).
|
||||||
|
* "A Simple, Fast Dominance Algorithm"
|
||||||
|
* http://www.hipersoft.rice.edu/grads/publications/dom14.pdf
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("JavadocLinkAsPlainText")
|
||||||
|
public class DominatorTree {
|
||||||
|
|
||||||
|
public static void compute(MethodNode mth) {
|
||||||
|
List<BlockNode> sorted = sortBlocks(mth);
|
||||||
|
BlockNode[] doms = build(sorted);
|
||||||
|
apply(sorted, doms);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<BlockNode> sortBlocks(MethodNode mth) {
|
||||||
|
int blocksCount = mth.getBasicBlocks().size();
|
||||||
|
List<BlockNode> sorted = new ArrayList<>(blocksCount);
|
||||||
|
BlockUtils.dfsVisit(mth, sorted::add);
|
||||||
|
if (sorted.size() != blocksCount) {
|
||||||
|
throw new JadxRuntimeException("Found unreachable blocks");
|
||||||
|
}
|
||||||
|
mth.setBasicBlocks(sorted);
|
||||||
|
return sorted;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private static BlockNode[] build(List<BlockNode> sorted) {
|
||||||
|
int blocksCount = sorted.size();
|
||||||
|
BlockNode[] doms = new BlockNode[blocksCount];
|
||||||
|
doms[0] = sorted.get(0);
|
||||||
|
boolean changed = true;
|
||||||
|
while (changed) {
|
||||||
|
changed = false;
|
||||||
|
for (int blockId = 1; blockId < blocksCount; blockId++) {
|
||||||
|
BlockNode b = sorted.get(blockId);
|
||||||
|
List<BlockNode> preds = b.getPredecessors();
|
||||||
|
int pickedPred = -1;
|
||||||
|
BlockNode newIDom = null;
|
||||||
|
for (BlockNode pred : preds) {
|
||||||
|
int id = pred.getId();
|
||||||
|
if (doms[id] != null) {
|
||||||
|
newIDom = pred;
|
||||||
|
pickedPred = id;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (newIDom == null) {
|
||||||
|
throw new JadxRuntimeException("No predecessors for block: " + b);
|
||||||
|
}
|
||||||
|
for (BlockNode predBlock : preds) {
|
||||||
|
int predId = predBlock.getId();
|
||||||
|
if (predId == pickedPred) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (doms[predId] != null) {
|
||||||
|
newIDom = intersect(sorted, doms, predBlock, newIDom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (doms[blockId] != newIDom) {
|
||||||
|
doms[blockId] = newIDom;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return doms;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BlockNode intersect(List<BlockNode> sorted, BlockNode[] doms, BlockNode b1, BlockNode b2) {
|
||||||
|
int f1 = b1.getId();
|
||||||
|
int f2 = b2.getId();
|
||||||
|
while (f1 != f2) {
|
||||||
|
while (f1 > f2) {
|
||||||
|
f1 = doms[f1].getId();
|
||||||
|
}
|
||||||
|
while (f2 > f1) {
|
||||||
|
f2 = doms[f2].getId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sorted.get(f1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void apply(List<BlockNode> sorted, BlockNode[] doms) {
|
||||||
|
BlockNode enterBlock = sorted.get(0);
|
||||||
|
enterBlock.setDoms(EmptyBitSet.EMPTY);
|
||||||
|
enterBlock.setIDom(null);
|
||||||
|
int blocksCount = sorted.size();
|
||||||
|
for (int i = 1; i < blocksCount; i++) {
|
||||||
|
BlockNode block = sorted.get(i);
|
||||||
|
BlockNode idom = doms[i];
|
||||||
|
block.setIDom(idom);
|
||||||
|
idom.addDominatesOn(block);
|
||||||
|
BitSet domBS = collectDoms(doms, idom);
|
||||||
|
domBS.clear(i);
|
||||||
|
block.setDoms(domBS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BitSet collectDoms(BlockNode[] doms, BlockNode idom) {
|
||||||
|
BitSet domBS = new BitSet(doms.length);
|
||||||
|
BlockNode nextIDom = idom;
|
||||||
|
while (true) {
|
||||||
|
int id = nextIDom.getId();
|
||||||
|
if (domBS.get(id)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
domBS.set(id);
|
||||||
|
BitSet curDoms = nextIDom.getDoms();
|
||||||
|
if (curDoms != null) {
|
||||||
|
// use already collected set
|
||||||
|
domBS.or(curDoms);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
nextIDom = doms[id];
|
||||||
|
}
|
||||||
|
return domBS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void computeDominanceFrontier(MethodNode mth) {
|
||||||
|
List<BlockNode> blocks = mth.getBasicBlocks();
|
||||||
|
for (BlockNode block : blocks) {
|
||||||
|
block.setDomFrontier(null);
|
||||||
|
}
|
||||||
|
int blocksCount = blocks.size();
|
||||||
|
for (BlockNode block : blocks) {
|
||||||
|
List<BlockNode> preds = block.getPredecessors();
|
||||||
|
if (preds.size() >= 2) {
|
||||||
|
BlockNode idom = block.getIDom();
|
||||||
|
for (BlockNode pred : preds) {
|
||||||
|
BlockNode runner = pred;
|
||||||
|
while (runner != idom) {
|
||||||
|
addToDF(runner, block, blocksCount);
|
||||||
|
runner = runner.getIDom();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (BlockNode block : blocks) {
|
||||||
|
BitSet df = block.getDomFrontier();
|
||||||
|
if (df == null || df.isEmpty()) {
|
||||||
|
block.setDomFrontier(EmptyBitSet.EMPTY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addToDF(BlockNode block, BlockNode dfBlock, int blocksCount) {
|
||||||
|
BitSet df = block.getDomFrontier();
|
||||||
|
if (df == null) {
|
||||||
|
df = new BitSet(blocksCount);
|
||||||
|
block.setDomFrontier(df);
|
||||||
|
}
|
||||||
|
df.set(dfBlock.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import java.util.List;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import jadx.core.dex.nodes.BlockNode;
|
import jadx.core.dex.nodes.BlockNode;
|
||||||
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
import jadx.core.dex.trycatch.ExceptionHandler;
|
import jadx.core.dex.trycatch.ExceptionHandler;
|
||||||
import jadx.core.utils.Utils;
|
import jadx.core.utils.Utils;
|
||||||
@@ -19,6 +20,10 @@ public class FinallyExtractInfo {
|
|||||||
private final InsnsSlice finallyInsnsSlice = new InsnsSlice();
|
private final InsnsSlice finallyInsnsSlice = new InsnsSlice();
|
||||||
private final BlockNode startBlock;
|
private final BlockNode startBlock;
|
||||||
|
|
||||||
|
private InsnsSlice curDupSlice;
|
||||||
|
private List<InsnNode> curDupInsns;
|
||||||
|
private int curDupInsnsOffset;
|
||||||
|
|
||||||
public FinallyExtractInfo(MethodNode mth, ExceptionHandler finallyHandler, BlockNode startBlock, List<BlockNode> allHandlerBlocks) {
|
public FinallyExtractInfo(MethodNode mth, ExceptionHandler finallyHandler, BlockNode startBlock, List<BlockNode> allHandlerBlocks) {
|
||||||
this.mth = mth;
|
this.mth = mth;
|
||||||
this.finallyHandler = finallyHandler;
|
this.finallyHandler = finallyHandler;
|
||||||
@@ -54,6 +59,27 @@ public class FinallyExtractInfo {
|
|||||||
return startBlock;
|
return startBlock;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public InsnsSlice getCurDupSlice() {
|
||||||
|
return curDupSlice;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCurDupSlice(InsnsSlice curDupSlice) {
|
||||||
|
this.curDupSlice = curDupSlice;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<InsnNode> getCurDupInsns() {
|
||||||
|
return curDupInsns;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCurDupInsnsOffset() {
|
||||||
|
return curDupInsnsOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCurDupInsns(List<InsnNode> insns, int offset) {
|
||||||
|
this.curDupInsns = insns;
|
||||||
|
this.curDupInsnsOffset = offset;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "FinallyExtractInfo{"
|
return "FinallyExtractInfo{"
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
import jadx.core.Consts;
|
import jadx.core.Consts;
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
import jadx.core.dex.attributes.AType;
|
import jadx.core.dex.attributes.AType;
|
||||||
|
import jadx.core.dex.attributes.nodes.RegDebugInfoAttr;
|
||||||
import jadx.core.dex.instructions.InsnType;
|
import jadx.core.dex.instructions.InsnType;
|
||||||
import jadx.core.dex.instructions.args.InsnArg;
|
import jadx.core.dex.instructions.args.InsnArg;
|
||||||
import jadx.core.dex.instructions.args.RegisterArg;
|
import jadx.core.dex.instructions.args.RegisterArg;
|
||||||
@@ -28,6 +29,7 @@ import jadx.core.dex.visitors.IDexTreeVisitor;
|
|||||||
import jadx.core.dex.visitors.JadxVisitor;
|
import jadx.core.dex.visitors.JadxVisitor;
|
||||||
import jadx.core.dex.visitors.ssa.SSATransform;
|
import jadx.core.dex.visitors.ssa.SSATransform;
|
||||||
import jadx.core.utils.BlockUtils;
|
import jadx.core.utils.BlockUtils;
|
||||||
|
import jadx.core.utils.InsnList;
|
||||||
import jadx.core.utils.ListUtils;
|
import jadx.core.utils.ListUtils;
|
||||||
import jadx.core.utils.Utils;
|
import jadx.core.utils.Utils;
|
||||||
|
|
||||||
@@ -376,6 +378,7 @@ public class MarkFinallyVisitor extends AbstractVisitor {
|
|||||||
* 'Finally' instructions can start in the middle of the first block.
|
* 'Finally' instructions can start in the middle of the first block.
|
||||||
*/
|
*/
|
||||||
private static InsnsSlice isStartBlock(BlockNode dupBlock, BlockNode finallyBlock, FinallyExtractInfo extractInfo) {
|
private static InsnsSlice isStartBlock(BlockNode dupBlock, BlockNode finallyBlock, FinallyExtractInfo extractInfo) {
|
||||||
|
extractInfo.setCurDupSlice(null);
|
||||||
List<InsnNode> dupInsns = dupBlock.getInstructions();
|
List<InsnNode> dupInsns = dupBlock.getInstructions();
|
||||||
List<InsnNode> finallyInsns = finallyBlock.getInstructions();
|
List<InsnNode> finallyInsns = finallyBlock.getInstructions();
|
||||||
int dupSize = dupInsns.size();
|
int dupSize = dupInsns.size();
|
||||||
@@ -386,7 +389,7 @@ public class MarkFinallyVisitor extends AbstractVisitor {
|
|||||||
int startPos;
|
int startPos;
|
||||||
int endPos = 0;
|
int endPos = 0;
|
||||||
if (dupSize == finSize) {
|
if (dupSize == finSize) {
|
||||||
if (!checkInsns(dupInsns, finallyInsns, 0)) {
|
if (!checkInsns(extractInfo, dupInsns, finallyInsns, 0)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
startPos = 0;
|
startPos = 0;
|
||||||
@@ -394,11 +397,11 @@ public class MarkFinallyVisitor extends AbstractVisitor {
|
|||||||
// dupSize > finSize
|
// dupSize > finSize
|
||||||
startPos = dupSize - finSize;
|
startPos = dupSize - finSize;
|
||||||
// fast check from end of block
|
// fast check from end of block
|
||||||
if (!checkInsns(dupInsns, finallyInsns, startPos)) {
|
if (!checkInsns(extractInfo, dupInsns, finallyInsns, startPos)) {
|
||||||
// search start insn
|
// search start insn
|
||||||
boolean found = false;
|
boolean found = false;
|
||||||
for (int i = 1; i < startPos; i++) {
|
for (int i = 1; i < startPos; i++) {
|
||||||
if (checkInsns(dupInsns, finallyInsns, i)) {
|
if (checkInsns(extractInfo, dupInsns, finallyInsns, i)) {
|
||||||
startPos = i;
|
startPos = i;
|
||||||
endPos = finSize + i;
|
endPos = finSize + i;
|
||||||
found = true;
|
found = true;
|
||||||
@@ -414,6 +417,7 @@ public class MarkFinallyVisitor extends AbstractVisitor {
|
|||||||
// put instructions into slices
|
// put instructions into slices
|
||||||
boolean complete;
|
boolean complete;
|
||||||
InsnsSlice slice = new InsnsSlice();
|
InsnsSlice slice = new InsnsSlice();
|
||||||
|
extractInfo.setCurDupSlice(slice);
|
||||||
int endIndex;
|
int endIndex;
|
||||||
if (endPos != 0) {
|
if (endPos != 0) {
|
||||||
endIndex = endPos + 1;
|
endIndex = endPos + 1;
|
||||||
@@ -453,11 +457,12 @@ public class MarkFinallyVisitor extends AbstractVisitor {
|
|||||||
return slice;
|
return slice;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean checkInsns(List<InsnNode> remInsns, List<InsnNode> finallyInsns, int delta) {
|
private static boolean checkInsns(FinallyExtractInfo extractInfo, List<InsnNode> dupInsns, List<InsnNode> finallyInsns, int delta) {
|
||||||
|
extractInfo.setCurDupInsns(dupInsns, delta);
|
||||||
for (int i = finallyInsns.size() - 1; i >= 0; i--) {
|
for (int i = finallyInsns.size() - 1; i >= 0; i--) {
|
||||||
InsnNode startInsn = finallyInsns.get(i);
|
InsnNode startInsn = finallyInsns.get(i);
|
||||||
InsnNode remInsn = remInsns.get(delta + i);
|
InsnNode dupInsn = dupInsns.get(delta + i);
|
||||||
if (!sameInsns(remInsn, startInsn)) {
|
if (!sameInsns(extractInfo, dupInsn, startInsn)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -509,8 +514,9 @@ public class MarkFinallyVisitor extends AbstractVisitor {
|
|||||||
if (dupInsnCount < finallyInsnCount) {
|
if (dupInsnCount < finallyInsnCount) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
extractInfo.setCurDupInsns(dupInsns, 0);
|
||||||
for (int i = 0; i < finallyInsnCount; i++) {
|
for (int i = 0; i < finallyInsnCount; i++) {
|
||||||
if (!sameInsns(dupInsns.get(i), finallyInsns.get(i))) {
|
if (!sameInsns(extractInfo, dupInsns.get(i), finallyInsns.get(i))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -524,26 +530,85 @@ public class MarkFinallyVisitor extends AbstractVisitor {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean sameInsns(InsnNode remInsn, InsnNode fInsn) {
|
private static boolean sameInsns(FinallyExtractInfo extractInfo, InsnNode dupInsn, InsnNode fInsn) {
|
||||||
if (!remInsn.isSame(fInsn)) {
|
if (!dupInsn.isSame(fInsn)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// TODO: check instance arg in ConstructorInsn
|
// TODO: check instance arg in ConstructorInsn
|
||||||
// TODO: compare literals
|
for (int i = 0; i < dupInsn.getArgsCount(); i++) {
|
||||||
for (int i = 0; i < remInsn.getArgsCount(); i++) {
|
InsnArg dupArg = dupInsn.getArg(i);
|
||||||
InsnArg remArg = remInsn.getArg(i);
|
|
||||||
InsnArg fArg = fInsn.getArg(i);
|
InsnArg fArg = fInsn.getArg(i);
|
||||||
if (remArg.isRegister() != fArg.isRegister()) {
|
if (!isSameArgs(extractInfo, dupArg, fArg)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
boolean remConst = remArg.isConst();
|
}
|
||||||
if (remConst != fArg.isConst()) {
|
return true;
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
if (remConst && !remArg.isSameConst(fArg)) {
|
@SuppressWarnings("RedundantIfStatement")
|
||||||
|
private static boolean isSameArgs(FinallyExtractInfo extractInfo, InsnArg dupArg, InsnArg fArg) {
|
||||||
|
boolean isReg = dupArg.isRegister();
|
||||||
|
if (isReg != fArg.isRegister()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (isReg) {
|
||||||
|
RegisterArg dupReg = (RegisterArg) dupArg;
|
||||||
|
RegisterArg fReg = (RegisterArg) fArg;
|
||||||
|
if (!dupReg.sameCodeVar(fReg)
|
||||||
|
&& !sameDebugInfo(dupReg, fReg)
|
||||||
|
&& assignedOutsideHandler(extractInfo, dupReg, fReg)
|
||||||
|
&& assignInsnDifferent(dupReg, fReg)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
boolean remConst = dupArg.isConst();
|
||||||
|
if (remConst != fArg.isConst()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (remConst && !dupArg.isSameConst(fArg)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean sameDebugInfo(RegisterArg dupReg, RegisterArg fReg) {
|
||||||
|
RegDebugInfoAttr fDbgInfo = fReg.get(AType.REG_DEBUG_INFO);
|
||||||
|
RegDebugInfoAttr dupDbgInfo = dupReg.get(AType.REG_DEBUG_INFO);
|
||||||
|
if (fDbgInfo == null || dupDbgInfo == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return dupDbgInfo.equals(fDbgInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean assignInsnDifferent(RegisterArg dupReg, RegisterArg fReg) {
|
||||||
|
InsnNode assignInsn = fReg.getAssignInsn();
|
||||||
|
InsnNode dupAssign = dupReg.getAssignInsn();
|
||||||
|
if (assignInsn == null || dupAssign == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!assignInsn.isSame(dupAssign)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (assignInsn.isConstInsn() && dupAssign.isConstInsn()) {
|
||||||
|
return !assignInsn.isDeepEquals(dupAssign);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("RedundantIfStatement")
|
||||||
|
private static boolean assignedOutsideHandler(FinallyExtractInfo extractInfo, RegisterArg dupReg, RegisterArg fReg) {
|
||||||
|
if (InsnList.contains(extractInfo.getFinallyInsnsSlice().getInsnsList(), fReg.getAssignInsn())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
InsnNode dupAssign = dupReg.getAssignInsn();
|
||||||
|
InsnsSlice curDupSlice = extractInfo.getCurDupSlice();
|
||||||
|
if (curDupSlice != null && InsnList.contains(curDupSlice.getInsnsList(), dupAssign)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
List<InsnNode> curDupInsns = extractInfo.getCurDupInsns();
|
||||||
|
if (Utils.notEmpty(curDupInsns) && InsnList.contains(curDupInsns, dupAssign, extractInfo.getCurDupInsnsOffset())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import jadx.core.dex.nodes.IContainer;
|
|||||||
import jadx.core.dex.nodes.IRegion;
|
import jadx.core.dex.nodes.IRegion;
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
import jadx.core.dex.regions.Region;
|
import jadx.core.dex.regions.Region;
|
||||||
|
import jadx.core.dex.regions.loops.LoopRegion;
|
||||||
import jadx.core.dex.visitors.AbstractVisitor;
|
import jadx.core.dex.visitors.AbstractVisitor;
|
||||||
|
|
||||||
public class CleanRegions extends AbstractVisitor {
|
public class CleanRegions extends AbstractVisitor {
|
||||||
@@ -42,6 +43,13 @@ public class CleanRegions extends AbstractVisitor {
|
|||||||
BlockNode block = (BlockNode) container;
|
BlockNode block = (BlockNode) container;
|
||||||
return block.getInstructions().isEmpty();
|
return block.getInstructions().isEmpty();
|
||||||
}
|
}
|
||||||
|
if (container instanceof LoopRegion) {
|
||||||
|
LoopRegion loopRegion = (LoopRegion) container;
|
||||||
|
if (loopRegion.isEndless()) {
|
||||||
|
// keep empty endless loops
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (container instanceof IRegion) {
|
if (container instanceof IRegion) {
|
||||||
List<IContainer> subBlocks = ((IRegion) container).getSubBlocks();
|
List<IContainer> subBlocks = ((IRegion) container).getSubBlocks();
|
||||||
for (IContainer subBlock : subBlocks) {
|
for (IContainer subBlock : subBlocks) {
|
||||||
|
|||||||
@@ -19,6 +19,10 @@ public class DepthRegionTraversal {
|
|||||||
traverseInternal(mth, visitor, mth.getRegion());
|
traverseInternal(mth, visitor, mth.getRegion());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void traverse(MethodNode mth, IContainer container, IRegionVisitor visitor) {
|
||||||
|
traverseInternal(mth, visitor, container);
|
||||||
|
}
|
||||||
|
|
||||||
public static void traverseIterative(MethodNode mth, IRegionIterativeVisitor visitor) {
|
public static void traverseIterative(MethodNode mth, IRegionIterativeVisitor visitor) {
|
||||||
boolean repeat;
|
boolean repeat;
|
||||||
int k = 0;
|
int k = 0;
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ package jadx.core.dex.visitors.regions;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
|
import jadx.core.dex.instructions.InsnType;
|
||||||
import jadx.core.dex.nodes.IContainer;
|
import jadx.core.dex.nodes.IContainer;
|
||||||
import jadx.core.dex.nodes.IRegion;
|
import jadx.core.dex.nodes.IRegion;
|
||||||
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
import jadx.core.dex.regions.Region;
|
import jadx.core.dex.regions.Region;
|
||||||
import jadx.core.dex.regions.conditions.IfCondition;
|
import jadx.core.dex.regions.conditions.IfCondition;
|
||||||
@@ -45,7 +47,7 @@ public class IfRegionVisitor extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("UnnecessaryReturnStatement")
|
@SuppressWarnings({ "UnnecessaryReturnStatement", "StatementWithEmptyBody" })
|
||||||
private static void orderBranches(MethodNode mth, IfRegion ifRegion) {
|
private static void orderBranches(MethodNode mth, IfRegion ifRegion) {
|
||||||
if (RegionUtils.isEmpty(ifRegion.getElseRegion())) {
|
if (RegionUtils.isEmpty(ifRegion.getElseRegion())) {
|
||||||
return;
|
return;
|
||||||
@@ -79,9 +81,15 @@ public class IfRegionVisitor extends AbstractVisitor {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
boolean lastRegion = ifRegion == RegionUtils.getLastRegion(mth.getRegion());
|
boolean lastRegion = RegionUtils.hasExitEdge(ifRegion);
|
||||||
if (elseSize == 1 && lastRegion && mth.isVoidReturn()) {
|
if (elseSize == 1 && lastRegion && mth.isVoidReturn()) {
|
||||||
// single return at method end will be removed later
|
InsnNode lastElseInsn = RegionUtils.getLastInsn(ifRegion.getElseRegion());
|
||||||
|
if (lastElseInsn != null && lastElseInsn.getType() == InsnType.THROW) {
|
||||||
|
// move `throw` into `then` block
|
||||||
|
invertIfRegion(ifRegion);
|
||||||
|
} else {
|
||||||
|
// single return at method end will be removed later
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!lastRegion) {
|
if (!lastRegion) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package jadx.core.dex.visitors.regions;
|
package jadx.core.dex.visitors.regions;
|
||||||
|
|
||||||
import java.util.LinkedList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -125,7 +125,7 @@ public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// can't make loop if argument from increment instruction is assign in loop
|
// can't make loop if argument from increment instruction is assign in loop
|
||||||
List<RegisterArg> args = new LinkedList<>();
|
List<RegisterArg> args = new ArrayList<>();
|
||||||
incrInsn.getRegisterArgs(args);
|
incrInsn.getRegisterArgs(args);
|
||||||
for (RegisterArg iArg : args) {
|
for (RegisterArg iArg : args) {
|
||||||
try {
|
try {
|
||||||
@@ -268,7 +268,7 @@ public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor
|
|||||||
|| !checkInvoke(nextCall, "java.util.Iterator", "next()Ljava/lang/Object;")) {
|
|| !checkInvoke(nextCall, "java.util.Iterator", "next()Ljava/lang/Object;")) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
List<InsnNode> toSkip = new LinkedList<>();
|
List<InsnNode> toSkip = new ArrayList<>();
|
||||||
RegisterArg iterVar;
|
RegisterArg iterVar;
|
||||||
if (nextCall.contains(AFlag.WRAPPED)) {
|
if (nextCall.contains(AFlag.WRAPPED)) {
|
||||||
InsnArg wrapArg = BlockUtils.searchWrappedInsnParent(mth, nextCall);
|
InsnArg wrapArg = BlockUtils.searchWrappedInsnParent(mth, nextCall);
|
||||||
|
|||||||
@@ -464,7 +464,7 @@ public class RegionMaker {
|
|||||||
BlockNode exitEnd = BlockUtils.followEmptyPath(exit);
|
BlockNode exitEnd = BlockUtils.followEmptyPath(exit);
|
||||||
List<LoopInfo> loops = exitEnd.getAll(AType.LOOP);
|
List<LoopInfo> loops = exitEnd.getAll(AType.LOOP);
|
||||||
for (LoopInfo loopAtEnd : loops) {
|
for (LoopInfo loopAtEnd : loops) {
|
||||||
if (loopAtEnd != loop) {
|
if (loopAtEnd != loop && loop.hasParent(loopAtEnd)) {
|
||||||
insertEdge = exitEdge;
|
insertEdge = exitEdge;
|
||||||
confirm = true;
|
confirm = true;
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import jadx.core.dex.instructions.PhiInsn;
|
|||||||
import jadx.core.dex.instructions.args.InsnArg;
|
import jadx.core.dex.instructions.args.InsnArg;
|
||||||
import jadx.core.dex.instructions.args.InsnWrapArg;
|
import jadx.core.dex.instructions.args.InsnWrapArg;
|
||||||
import jadx.core.dex.instructions.args.RegisterArg;
|
import jadx.core.dex.instructions.args.RegisterArg;
|
||||||
|
import jadx.core.dex.instructions.args.SSAVar;
|
||||||
import jadx.core.dex.instructions.mods.TernaryInsn;
|
import jadx.core.dex.instructions.mods.TernaryInsn;
|
||||||
import jadx.core.dex.nodes.BlockNode;
|
import jadx.core.dex.nodes.BlockNode;
|
||||||
import jadx.core.dex.nodes.IContainer;
|
import jadx.core.dex.nodes.IContainer;
|
||||||
@@ -73,11 +74,7 @@ public class TernaryMod extends AbstractRegionVisitor implements IRegionIterativ
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (elseRegion == null) {
|
if (elseRegion == null) {
|
||||||
if (mth.isConstructor()) {
|
return processOneBranchTernary(mth, ifRegion);
|
||||||
// force ternary conversion to inline all code in 'super' or 'this' calls
|
|
||||||
return processOneBranchTernary(mth, ifRegion);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
BlockNode tb = getTernaryInsnBlock(thenRegion);
|
BlockNode tb = getTernaryInsnBlock(thenRegion);
|
||||||
BlockNode eb = getTernaryInsnBlock(elseRegion);
|
BlockNode eb = getTernaryInsnBlock(elseRegion);
|
||||||
@@ -93,21 +90,8 @@ public class TernaryMod extends AbstractRegionVisitor implements IRegionIterativ
|
|||||||
InsnNode thenInsn = tb.getInstructions().get(0);
|
InsnNode thenInsn = tb.getInstructions().get(0);
|
||||||
InsnNode elseInsn = eb.getInstructions().get(0);
|
InsnNode elseInsn = eb.getInstructions().get(0);
|
||||||
|
|
||||||
if (mth.contains(AFlag.USE_LINES_HINTS)
|
if (!verifyLineHints(mth, thenInsn, elseInsn)) {
|
||||||
&& thenInsn.getSourceLine() != elseInsn.getSourceLine()) {
|
return false;
|
||||||
if (thenInsn.getSourceLine() != 0 && elseInsn.getSourceLine() != 0) {
|
|
||||||
// sometimes source lines incorrect
|
|
||||||
if (!checkLineStats(thenInsn, elseInsn)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// no debug info
|
|
||||||
if (containsTernary(thenInsn) || containsTernary(elseInsn)) {
|
|
||||||
// don't make nested ternary by default
|
|
||||||
// TODO: add addition checks
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RegisterArg thenResArg = thenInsn.getResult();
|
RegisterArg thenResArg = thenInsn.getResult();
|
||||||
@@ -184,6 +168,20 @@ public class TernaryMod extends AbstractRegionVisitor implements IRegionIterativ
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean verifyLineHints(MethodNode mth, InsnNode thenInsn, InsnNode elseInsn) {
|
||||||
|
if (mth.contains(AFlag.USE_LINES_HINTS)
|
||||||
|
&& thenInsn.getSourceLine() != elseInsn.getSourceLine()) {
|
||||||
|
if (thenInsn.getSourceLine() != 0 && elseInsn.getSourceLine() != 0) {
|
||||||
|
// sometimes source lines incorrect
|
||||||
|
return checkLineStats(thenInsn, elseInsn);
|
||||||
|
}
|
||||||
|
// don't make nested ternary by default
|
||||||
|
// TODO: add addition checks
|
||||||
|
return !containsTernary(thenInsn) && !containsTernary(elseInsn);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private static void clearConditionBlocks(List<BlockNode> conditionBlocks, BlockNode header) {
|
private static void clearConditionBlocks(List<BlockNode> conditionBlocks, BlockNode header) {
|
||||||
for (BlockNode block : conditionBlocks) {
|
for (BlockNode block : conditionBlocks) {
|
||||||
if (block != header) {
|
if (block != header) {
|
||||||
@@ -277,6 +275,7 @@ public class TernaryMod extends AbstractRegionVisitor implements IRegionIterativ
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("StatementWithEmptyBody")
|
||||||
private static void replaceWithTernary(MethodNode mth, IfRegion ifRegion, BlockNode block, InsnNode insn) {
|
private static void replaceWithTernary(MethodNode mth, IfRegion ifRegion, BlockNode block, InsnNode insn) {
|
||||||
RegisterArg resArg = insn.getResult();
|
RegisterArg resArg = insn.getResult();
|
||||||
if (resArg.getSVar().getUseList().size() != 1) {
|
if (resArg.getSVar().getUseList().size() != 1) {
|
||||||
@@ -296,17 +295,45 @@ public class TernaryMod extends AbstractRegionVisitor implements IRegionIterativ
|
|||||||
if (otherArg == null) {
|
if (otherArg == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
InsnNode elseAssign = otherArg.getAssignInsn();
|
||||||
|
if (mth.isConstructor() || (mth.getParentClass().isEnum() && mth.getMethodInfo().isClassInit())) {
|
||||||
|
// forcing ternary inline for constructors (will help in moving super call to the top) and enums
|
||||||
|
// skip code style checks
|
||||||
|
} else {
|
||||||
|
if (elseAssign != null && elseAssign.isConstInsn()) {
|
||||||
|
if (!verifyLineHints(mth, insn, elseAssign)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (insn.getResult().sameCodeVar(otherArg)) {
|
||||||
|
// don't use same variable in else branch to prevent: l = (l == 0) ? 1 : l
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// all checks passed
|
// all checks passed
|
||||||
BlockNode header = ifRegion.getConditionBlocks().get(0);
|
BlockNode header = ifRegion.getConditionBlocks().get(0);
|
||||||
if (!ifRegion.getParent().replaceSubBlock(ifRegion, header)) {
|
if (!ifRegion.getParent().replaceSubBlock(ifRegion, header)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
InsnArg elseArg;
|
||||||
|
if (elseAssign != null && elseAssign.isConstInsn()) {
|
||||||
|
// inline constant
|
||||||
|
SSAVar elseVar = elseAssign.getResult().getSVar();
|
||||||
|
if (elseVar.getUseCount() == 1 && elseVar.getOnlyOneUseInPhi() == phiInsn) {
|
||||||
|
InsnRemover.remove(mth, elseAssign);
|
||||||
|
}
|
||||||
|
elseArg = InsnArg.wrapInsnIntoArg(elseAssign);
|
||||||
|
} else {
|
||||||
|
elseArg = otherArg;
|
||||||
|
}
|
||||||
TernaryInsn ternInsn = new TernaryInsn(ifRegion.getCondition(),
|
TernaryInsn ternInsn = new TernaryInsn(ifRegion.getCondition(),
|
||||||
phiInsn.getResult(), InsnArg.wrapInsnIntoArg(insn), otherArg);
|
phiInsn.getResult(), InsnArg.wrapInsnIntoArg(insn), elseArg);
|
||||||
|
ternInsn.simplifyCondition();
|
||||||
|
|
||||||
InsnRemover.unbindResult(mth, insn);
|
InsnRemover.unbindResult(mth, insn);
|
||||||
InsnList.remove(block, insn);
|
InsnList.remove(block, insn);
|
||||||
|
|
||||||
InsnRemover.unbindAllArgs(mth, phiInsn);
|
InsnRemover.unbindAllArgs(mth, phiInsn);
|
||||||
header.getInstructions().clear();
|
header.getInstructions().clear();
|
||||||
ternInsn.rebindArgs();
|
ternInsn.rebindArgs();
|
||||||
|
|||||||
@@ -15,8 +15,9 @@ import jadx.core.codegen.json.JsonMappingGen;
|
|||||||
import jadx.core.deobf.Deobfuscator;
|
import jadx.core.deobf.Deobfuscator;
|
||||||
import jadx.core.deobf.NameMapper;
|
import jadx.core.deobf.NameMapper;
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
|
import jadx.core.dex.attributes.AType;
|
||||||
|
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
|
||||||
import jadx.core.dex.attributes.nodes.RenameReasonAttr;
|
import jadx.core.dex.attributes.nodes.RenameReasonAttr;
|
||||||
import jadx.core.dex.info.AccessInfo;
|
|
||||||
import jadx.core.dex.info.ClassInfo;
|
import jadx.core.dex.info.ClassInfo;
|
||||||
import jadx.core.dex.info.FieldInfo;
|
import jadx.core.dex.info.FieldInfo;
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
@@ -45,8 +46,8 @@ public class RenameVisitor extends AbstractVisitor {
|
|||||||
deobfuscator.execute();
|
deobfuscator.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
checkClasses(deobfuscator, root, args);
|
|
||||||
UserRenames.applyForNodes(root);
|
UserRenames.applyForNodes(root);
|
||||||
|
checkClasses(deobfuscator, root, args);
|
||||||
|
|
||||||
if (args.isDeobfuscationOn() || !args.isJsonOutput()) {
|
if (args.isDeobfuscationOn() || !args.isJsonOutput()) {
|
||||||
deobfuscator.savePresets();
|
deobfuscator.savePresets();
|
||||||
@@ -196,13 +197,8 @@ public class RenameVisitor extends AbstractVisitor {
|
|||||||
if (args.isRenameValid()) {
|
if (args.isRenameValid()) {
|
||||||
Set<String> names = new HashSet<>(methods.size());
|
Set<String> names = new HashSet<>(methods.size());
|
||||||
for (MethodNode mth : methods) {
|
for (MethodNode mth : methods) {
|
||||||
AccessInfo accessFlags = mth.getAccessFlags();
|
|
||||||
if (accessFlags.isBridge() || accessFlags.isSynthetic()
|
|
||||||
|| mth.contains(AFlag.DONT_GENERATE) /* this flag not set yet */) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
String signature = mth.getMethodInfo().makeSignature(true, false);
|
String signature = mth.getMethodInfo().makeSignature(true, false);
|
||||||
if (!names.add(signature)) {
|
if (!names.add(signature) && canRename(mth)) {
|
||||||
deobfuscator.forceRenameMethod(mth);
|
deobfuscator.forceRenameMethod(mth);
|
||||||
mth.addAttr(new RenameReasonAttr("collision with other method in class"));
|
mth.addAttr(new RenameReasonAttr("collision with other method in class"));
|
||||||
}
|
}
|
||||||
@@ -210,6 +206,23 @@ public class RenameVisitor extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean canRename(MethodNode mth) {
|
||||||
|
if (mth.contains(AFlag.DONT_RENAME)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
MethodOverrideAttr overrideAttr = mth.get(AType.METHOD_OVERRIDE);
|
||||||
|
if (overrideAttr != null) {
|
||||||
|
for (MethodNode relatedMth : overrideAttr.getRelatedMthNodes()) {
|
||||||
|
if (relatedMth != mth && mth.getParentClass().equals(relatedMth.getParentClass())) {
|
||||||
|
// ignore rename if exists related method from same class (bridge method in most cases)
|
||||||
|
// such rename will also rename current method and will not help to resolve name collision
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private static void processRootPackages(Deobfuscator deobfuscator, RootNode root, List<ClassNode> classes) {
|
private static void processRootPackages(Deobfuscator deobfuscator, RootNode root, List<ClassNode> classes) {
|
||||||
Set<String> rootPkgs = collectRootPkgs(classes);
|
Set<String> rootPkgs = collectRootPkgs(classes);
|
||||||
root.getCacheStorage().setRootPkgs(rootPkgs);
|
root.getCacheStorage().setRootPkgs(rootPkgs);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package jadx.core.dex.visitors.shrink;
|
package jadx.core.dex.visitors.shrink;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.BitSet;
|
import java.util.BitSet;
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import jadx.core.dex.instructions.InsnType;
|
import jadx.core.dex.instructions.InsnType;
|
||||||
@@ -30,7 +30,7 @@ final class ArgsInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static List<RegisterArg> getArgs(InsnNode insn) {
|
public static List<RegisterArg> getArgs(InsnNode insn) {
|
||||||
List<RegisterArg> args = new LinkedList<>();
|
List<RegisterArg> args = new ArrayList<>();
|
||||||
addArgs(insn, args);
|
addArgs(insn, args);
|
||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import java.util.Set;
|
|||||||
|
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
import jadx.core.dex.instructions.InsnType;
|
import jadx.core.dex.instructions.InsnType;
|
||||||
|
import jadx.core.dex.instructions.InvokeCustomNode;
|
||||||
|
import jadx.core.dex.instructions.InvokeNode;
|
||||||
import jadx.core.dex.instructions.args.InsnArg;
|
import jadx.core.dex.instructions.args.InsnArg;
|
||||||
import jadx.core.dex.instructions.args.InsnWrapArg;
|
import jadx.core.dex.instructions.args.InsnWrapArg;
|
||||||
import jadx.core.dex.instructions.args.Named;
|
import jadx.core.dex.instructions.args.Named;
|
||||||
@@ -123,6 +125,9 @@ public class CodeShrinkVisitor extends AbstractVisitor {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!checkLambdaInline(arg, assignInsn)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
int assignPos = insnList.getIndex(assignInsn);
|
int assignPos = insnList.getIndex(assignInsn);
|
||||||
if (assignPos != -1) {
|
if (assignPos != -1) {
|
||||||
@@ -145,6 +150,26 @@ public class CodeShrinkVisitor extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forbid inline lambda into invoke as an instance arg, i.e. this will not compile:
|
||||||
|
* {@code () -> { ... }.apply(); }
|
||||||
|
*/
|
||||||
|
private static boolean checkLambdaInline(RegisterArg arg, InsnNode assignInsn) {
|
||||||
|
if (assignInsn.getType() == InsnType.INVOKE && assignInsn instanceof InvokeCustomNode) {
|
||||||
|
for (RegisterArg useArg : arg.getSVar().getUseList()) {
|
||||||
|
InsnNode parentInsn = useArg.getParentInsn();
|
||||||
|
if (parentInsn != null && parentInsn.getType() == InsnType.INVOKE) {
|
||||||
|
InvokeNode invokeNode = (InvokeNode) parentInsn;
|
||||||
|
InsnArg instArg = invokeNode.getInstanceArg();
|
||||||
|
if (instArg != null && instArg == useArg) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private static boolean varWithSameNameExists(MethodNode mth, SSAVar inlineVar) {
|
private static boolean varWithSameNameExists(MethodNode mth, SSAVar inlineVar) {
|
||||||
for (SSAVar ssaVar : mth.getSVars()) {
|
for (SSAVar ssaVar : mth.getSVars()) {
|
||||||
if (ssaVar == inlineVar || ssaVar.getCodeVar() == inlineVar.getCodeVar()) {
|
if (ssaVar == inlineVar || ssaVar.getCodeVar() == inlineVar.getCodeVar()) {
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package jadx.core.dex.visitors.ssa;
|
package jadx.core.dex.visitors.ssa;
|
||||||
|
|
||||||
|
import java.util.ArrayDeque;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.BitSet;
|
import java.util.BitSet;
|
||||||
import java.util.Deque;
|
import java.util.Deque;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
@@ -81,7 +81,7 @@ public class SSATransform extends AbstractVisitor {
|
|||||||
int blocksCount = blocks.size();
|
int blocksCount = blocks.size();
|
||||||
BitSet hasPhi = new BitSet(blocksCount);
|
BitSet hasPhi = new BitSet(blocksCount);
|
||||||
BitSet processed = new BitSet(blocksCount);
|
BitSet processed = new BitSet(blocksCount);
|
||||||
Deque<BlockNode> workList = new LinkedList<>();
|
Deque<BlockNode> workList = new ArrayDeque<>();
|
||||||
|
|
||||||
BitSet assignBlocks = la.getAssignBlocks(regNum);
|
BitSet assignBlocks = la.getAssignBlocks(regNum);
|
||||||
for (int id = assignBlocks.nextSetBit(0); id >= 0; id = assignBlocks.nextSetBit(id + 1)) {
|
for (int id = assignBlocks.nextSetBit(0); id >= 0; id = assignBlocks.nextSetBit(id + 1)) {
|
||||||
@@ -136,7 +136,7 @@ public class SSATransform extends AbstractVisitor {
|
|||||||
RenameState initState = RenameState.init(mth);
|
RenameState initState = RenameState.init(mth);
|
||||||
initPhiInEnterBlock(initState);
|
initPhiInEnterBlock(initState);
|
||||||
|
|
||||||
Deque<RenameState> stack = new LinkedList<>();
|
Deque<RenameState> stack = new ArrayDeque<>();
|
||||||
stack.push(initState);
|
stack.push(initState);
|
||||||
while (!stack.isEmpty()) {
|
while (!stack.isEmpty()) {
|
||||||
RenameState state = stack.pop();
|
RenameState state = stack.pop();
|
||||||
|
|||||||
+5
-10
@@ -325,7 +325,7 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
|
|||||||
ClassNode ctrCls = root.resolveClass(ctr.getClassType());
|
ClassNode ctrCls = root.resolveClass(ctr.getClassType());
|
||||||
if (ctrCls != null && ctrCls.contains(AFlag.DONT_GENERATE)) {
|
if (ctrCls != null && ctrCls.contains(AFlag.DONT_GENERATE)) {
|
||||||
AnonymousClassAttr baseTypeAttr = ctrCls.get(AType.ANONYMOUS_CLASS);
|
AnonymousClassAttr baseTypeAttr = ctrCls.get(AType.ANONYMOUS_CLASS);
|
||||||
if (baseTypeAttr != null) {
|
if (baseTypeAttr != null && baseTypeAttr.getInlineType() == AnonymousClassAttr.InlineType.CONSTRUCTOR) {
|
||||||
return baseTypeAttr.getBaseType();
|
return baseTypeAttr.getBaseType();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -913,25 +913,20 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
boolean fixed = false;
|
boolean fixed = false;
|
||||||
for (ITypeBound bound : typeInfo.getBounds()) {
|
for (RegisterArg arg : new ArrayList<>(var.getUseList())) {
|
||||||
if (bound.getBound() == BoundEnum.USE
|
if (fixBooleanUsage(mth, arg)) {
|
||||||
&& fixBooleanUsage(mth, bound)) {
|
|
||||||
fixed = true;
|
fixed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return fixed;
|
return fixed;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean fixBooleanUsage(MethodNode mth, ITypeBound bound) {
|
private boolean fixBooleanUsage(MethodNode mth, RegisterArg boundArg) {
|
||||||
ArgType boundType = bound.getType();
|
ArgType boundType = boundArg.getInitType();
|
||||||
if (boundType == ArgType.BOOLEAN
|
if (boundType == ArgType.BOOLEAN
|
||||||
|| (boundType.isTypeKnown() && !boundType.isPrimitive())) {
|
|| (boundType.isTypeKnown() && !boundType.isPrimitive())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
RegisterArg boundArg = bound.getArg();
|
|
||||||
if (boundArg == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
InsnNode insn = boundArg.getParentInsn();
|
InsnNode insn = boundArg.getParentInsn();
|
||||||
if (insn == null || insn.getType() == InsnType.IF) {
|
if (insn == null || insn.getType() == InsnType.IF) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import java.io.StringReader;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import javax.xml.parsers.DocumentBuilder;
|
import javax.xml.parsers.DocumentBuilder;
|
||||||
|
|
||||||
@@ -29,6 +30,8 @@ public class ExportGradleProject {
|
|||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(ExportGradleProject.class);
|
private static final Logger LOG = LoggerFactory.getLogger(ExportGradleProject.class);
|
||||||
|
|
||||||
|
private static final Pattern ILLEGAL_GRADLE_CHARS = Pattern.compile("[/\\\\:>\"?*|]");
|
||||||
|
|
||||||
private static final Set<String> IGNORE_CLS_NAMES = new HashSet<>(Arrays.asList(
|
private static final Set<String> IGNORE_CLS_NAMES = new HashSet<>(Arrays.asList(
|
||||||
"R",
|
"R",
|
||||||
"BuildConfig"));
|
"BuildConfig"));
|
||||||
@@ -72,7 +75,7 @@ public class ExportGradleProject {
|
|||||||
private void saveSettingsGradle() throws IOException {
|
private void saveSettingsGradle() throws IOException {
|
||||||
TemplateFile tmpl = TemplateFile.fromResources("/export/settings.gradle.tmpl");
|
TemplateFile tmpl = TemplateFile.fromResources("/export/settings.gradle.tmpl");
|
||||||
|
|
||||||
tmpl.add("applicationName", applicationParams.getApplicationName());
|
tmpl.add("applicationName", ILLEGAL_GRADLE_CHARS.matcher(applicationParams.getApplicationName()).replaceAll(""));
|
||||||
tmpl.save(new File(projectDir, "settings.gradle"));
|
tmpl.save(new File(projectDir, "settings.gradle"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package jadx.core.utils;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import jadx.core.deobf.NameMapper;
|
||||||
|
import jadx.core.deobf.TldHelper;
|
||||||
|
|
||||||
|
public class BetterName {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(BetterName.class);
|
||||||
|
|
||||||
|
private static final boolean DEBUG = true;
|
||||||
|
|
||||||
|
public static String compareAndGet(String first, String second) {
|
||||||
|
if (Objects.equals(first, second)) {
|
||||||
|
return first;
|
||||||
|
}
|
||||||
|
int firstRating = calcRating(first);
|
||||||
|
int secondRating = calcRating(second);
|
||||||
|
boolean firstBetter = firstRating >= secondRating;
|
||||||
|
if (DEBUG) {
|
||||||
|
if (firstBetter) {
|
||||||
|
LOG.info("Better name: '{}' > '{}' ({} > {})", first, second, firstRating, secondRating);
|
||||||
|
} else {
|
||||||
|
LOG.info("Better name: '{}' > '{}' ({} > {})", second, first, secondRating, firstRating);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return firstBetter ? first : second;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int calcRating(String str) {
|
||||||
|
int rating = str.length() * 3;
|
||||||
|
rating += differentCharsCount(str) * 20;
|
||||||
|
|
||||||
|
if (NameMapper.isAllCharsPrintable(str)) {
|
||||||
|
rating += 100;
|
||||||
|
}
|
||||||
|
if (NameMapper.isValidIdentifier(str)) {
|
||||||
|
rating += 50;
|
||||||
|
}
|
||||||
|
if (TldHelper.contains(str)) {
|
||||||
|
rating += 20;
|
||||||
|
}
|
||||||
|
if (str.contains("_")) {
|
||||||
|
// rare in obfuscated names
|
||||||
|
rating += 100;
|
||||||
|
}
|
||||||
|
return rating;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int differentCharsCount(String str) {
|
||||||
|
String lower = str.toLowerCase(Locale.ROOT);
|
||||||
|
Set<Integer> chars = new HashSet<>();
|
||||||
|
StringUtils.visitCodePoints(lower, chars::add);
|
||||||
|
return chars.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -697,7 +697,7 @@ public class BlockUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Search lowest common ancestor in dominator tree for input set.
|
* Search the lowest common ancestor in dominator tree for input set.
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
public static BlockNode getCommonDominator(MethodNode mth, List<BlockNode> blocks) {
|
public static BlockNode getCommonDominator(MethodNode mth, List<BlockNode> blocks) {
|
||||||
@@ -1013,6 +1013,9 @@ public class BlockUtils {
|
|||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
public static InsnNode getOnlyOneInsnFromMth(MethodNode mth) {
|
public static InsnNode getOnlyOneInsnFromMth(MethodNode mth) {
|
||||||
|
if (mth.isNoCode()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
InsnNode insn = null;
|
InsnNode insn = null;
|
||||||
for (BlockNode block : mth.getBasicBlocks()) {
|
for (BlockNode block : mth.getBasicBlocks()) {
|
||||||
List<InsnNode> blockInsns = block.getInstructions();
|
List<InsnNode> blockInsns = block.getInstructions();
|
||||||
|
|||||||
@@ -246,4 +246,29 @@ public class DebugUtils {
|
|||||||
Set<Object> seen = ConcurrentHashMap.newKeySet();
|
Set<Object> seen = ConcurrentHashMap.newKeySet();
|
||||||
return t -> seen.add(keyExtractor.apply(t));
|
return t -> seen.add(keyExtractor.apply(t));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Map<String, Long> execTimes;
|
||||||
|
|
||||||
|
public static void initExecTimes() {
|
||||||
|
execTimes = new ConcurrentHashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void mergeExecTimeFromStart(String tag, long startTimeMillis) {
|
||||||
|
mergeExecTime(tag, System.currentTimeMillis() - startTimeMillis);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void mergeExecTime(String tag, long execTimeMillis) {
|
||||||
|
execTimes.merge(tag, execTimeMillis, Long::sum);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void printExecTimes() {
|
||||||
|
System.out.println("Exec times:");
|
||||||
|
execTimes.forEach((tag, time) -> System.out.println(" " + tag + ": " + time + "ms"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void printExecTimesWithTotal(long totalMillis) {
|
||||||
|
System.out.println("Exec times: total " + totalMillis + "ms");
|
||||||
|
execTimes.forEach((tag, time) -> System.out.println(" " + tag + ": " + time + "ms"
|
||||||
|
+ String.format(" (%.2f%%)", time * 100. / (double) totalMillis)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -139,11 +139,13 @@ public class DecompilerScheduler implements IDecompileScheduler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void dumpBatchesStats(List<JavaClass> classes, List<List<JavaClass>> result, List<DepInfo> deps) {
|
private void dumpBatchesStats(List<JavaClass> classes, List<List<JavaClass>> result, List<DepInfo> deps) {
|
||||||
|
int clsInBatches = result.stream().mapToInt(List::size).sum();
|
||||||
double avg = result.stream().mapToInt(List::size).average().orElse(-1);
|
double avg = result.stream().mapToInt(List::size).average().orElse(-1);
|
||||||
int maxSingleDeps = classes.stream().mapToInt(JavaClass::getTotalDepsCount).max().orElse(-1);
|
int maxSingleDeps = classes.stream().mapToInt(JavaClass::getTotalDepsCount).max().orElse(-1);
|
||||||
int maxSubDeps = deps.stream().mapToInt(DepInfo::getDepsCount).max().orElse(-1);
|
int maxSubDeps = deps.stream().mapToInt(DepInfo::getDepsCount).max().orElse(-1);
|
||||||
LOG.info("Batches stats:"
|
LOG.info("Batches stats:"
|
||||||
+ "\n input classes: " + classes.size()
|
+ "\n input classes: " + classes.size()
|
||||||
|
+ ",\n classes in batches: " + clsInBatches
|
||||||
+ ",\n batches: " + result.size()
|
+ ",\n batches: " + result.size()
|
||||||
+ ",\n average batch size: " + String.format("%.2f", avg)
|
+ ",\n average batch size: " + String.format("%.2f", avg)
|
||||||
+ ",\n max single deps count: " + maxSingleDeps
|
+ ",\n max single deps count: " + maxSingleDeps
|
||||||
|
|||||||
@@ -1,11 +1,33 @@
|
|||||||
package jadx.core.utils;
|
package jadx.core.utils;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import jadx.api.plugins.input.data.IMethodHandle;
|
||||||
|
import jadx.api.plugins.input.data.IMethodProto;
|
||||||
|
import jadx.api.plugins.input.data.IMethodRef;
|
||||||
|
import jadx.api.plugins.input.data.MethodHandleType;
|
||||||
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||||
|
import jadx.core.dex.info.ClassInfo;
|
||||||
|
import jadx.core.dex.info.FieldInfo;
|
||||||
|
import jadx.core.dex.info.MethodInfo;
|
||||||
|
import jadx.core.dex.instructions.ConstClassNode;
|
||||||
|
import jadx.core.dex.instructions.ConstStringNode;
|
||||||
|
import jadx.core.dex.instructions.IndexInsnNode;
|
||||||
|
import jadx.core.dex.instructions.InsnType;
|
||||||
|
import jadx.core.dex.instructions.InvokeNode;
|
||||||
|
import jadx.core.dex.instructions.InvokeType;
|
||||||
import jadx.core.dex.instructions.args.ArgType;
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
import jadx.core.dex.instructions.args.InsnArg;
|
import jadx.core.dex.instructions.args.InsnArg;
|
||||||
import jadx.core.dex.instructions.args.LiteralArg;
|
import jadx.core.dex.instructions.args.LiteralArg;
|
||||||
|
import jadx.core.dex.instructions.args.PrimitiveType;
|
||||||
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
|
import jadx.core.dex.nodes.RootNode;
|
||||||
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
public class EncodedValueUtils {
|
public class EncodedValueUtils {
|
||||||
|
|
||||||
@@ -50,4 +72,108 @@ public class EncodedValueUtils {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static InsnArg convertToInsnArg(RootNode root, EncodedValue value) {
|
||||||
|
Object obj = value.getValue();
|
||||||
|
switch (value.getType()) {
|
||||||
|
case ENCODED_NULL:
|
||||||
|
case ENCODED_BYTE:
|
||||||
|
case ENCODED_SHORT:
|
||||||
|
case ENCODED_CHAR:
|
||||||
|
case ENCODED_INT:
|
||||||
|
case ENCODED_LONG:
|
||||||
|
case ENCODED_FLOAT:
|
||||||
|
case ENCODED_DOUBLE:
|
||||||
|
return (InsnArg) convertToConstValue(value);
|
||||||
|
|
||||||
|
case ENCODED_BOOLEAN:
|
||||||
|
return InsnArg.lit(((Boolean) obj) ? 0 : 1, ArgType.BOOLEAN);
|
||||||
|
case ENCODED_STRING:
|
||||||
|
return InsnArg.wrapArg(new ConstStringNode((String) obj));
|
||||||
|
case ENCODED_TYPE:
|
||||||
|
return InsnArg.wrapArg(new ConstClassNode(ArgType.parse((String) obj)));
|
||||||
|
case ENCODED_METHOD_TYPE:
|
||||||
|
return InsnArg.wrapArg(buildMethodType(root, (IMethodProto) obj));
|
||||||
|
case ENCODED_METHOD_HANDLE:
|
||||||
|
return InsnArg.wrapArg(buildMethodHandle(root, (IMethodHandle) obj));
|
||||||
|
|
||||||
|
}
|
||||||
|
throw new JadxRuntimeException("Unsupported type for raw invoke-custom: " + value.getType());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static InvokeNode buildMethodType(RootNode root, IMethodProto methodProto) {
|
||||||
|
ArgType retType = ArgType.parse(methodProto.getReturnType());
|
||||||
|
List<ArgType> argTypes = Utils.collectionMap(methodProto.getArgTypes(), ArgType::parse);
|
||||||
|
List<ArgType> callTypes = new ArrayList<>(1 + argTypes.size());
|
||||||
|
callTypes.add(retType);
|
||||||
|
callTypes.addAll(argTypes);
|
||||||
|
ArgType mthType = ArgType.object("java.lang.invoke.MethodType");
|
||||||
|
ClassInfo cls = ClassInfo.fromType(root, mthType);
|
||||||
|
MethodInfo mth = MethodInfo.fromDetails(root, cls, "methodType", callTypes, mthType);
|
||||||
|
InvokeNode invoke = new InvokeNode(mth, InvokeType.STATIC, callTypes.size());
|
||||||
|
for (ArgType type : callTypes) {
|
||||||
|
InsnNode argInsn;
|
||||||
|
if (type.isPrimitive()) {
|
||||||
|
argInsn = new IndexInsnNode(InsnType.SGET, getTypeField(root, type.getPrimitiveType()), 0);
|
||||||
|
} else {
|
||||||
|
argInsn = new ConstClassNode(type);
|
||||||
|
}
|
||||||
|
invoke.addArg(InsnArg.wrapArg(argInsn));
|
||||||
|
}
|
||||||
|
return invoke;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FieldInfo getTypeField(RootNode root, PrimitiveType type) {
|
||||||
|
ArgType boxType = type.getBoxType();
|
||||||
|
ClassInfo boxCls = ClassInfo.fromType(root, boxType);
|
||||||
|
return FieldInfo.from(root, boxCls, "TYPE", boxType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build `MethodHandles.lookup().find{type}(methodCls, methodName, methodType)`
|
||||||
|
*/
|
||||||
|
private static InsnNode buildMethodHandle(RootNode root, IMethodHandle methodHandle) {
|
||||||
|
if (methodHandle.getType().isField()) {
|
||||||
|
// TODO: lookup for field
|
||||||
|
return new ConstStringNode("FIELD:" + methodHandle.getFieldRef());
|
||||||
|
}
|
||||||
|
IMethodRef methodRef = methodHandle.getMethodRef();
|
||||||
|
methodRef.load();
|
||||||
|
|
||||||
|
ClassInfo lookupCls = ClassInfo.fromName(root, "java.lang.invoke.MethodHandles.Lookup");
|
||||||
|
MethodInfo findMethod = MethodInfo.fromDetails(root, lookupCls,
|
||||||
|
getFindMethodName(methodHandle.getType()),
|
||||||
|
Arrays.asList(ArgType.CLASS, ArgType.STRING, ArgType.object("java.lang.invoke.MethodType")),
|
||||||
|
ArgType.object("java.lang.invoke.MethodHandle"));
|
||||||
|
|
||||||
|
InvokeNode invoke = new InvokeNode(findMethod, InvokeType.DIRECT, 4);
|
||||||
|
invoke.addArg(buildLookupArg(root));
|
||||||
|
invoke.addArg(InsnArg.wrapArg(new ConstClassNode(ArgType.object(methodRef.getParentClassType()))));
|
||||||
|
invoke.addArg(InsnArg.wrapArg(new ConstStringNode(methodRef.getName())));
|
||||||
|
invoke.addArg(InsnArg.wrapArg(buildMethodType(root, methodRef)));
|
||||||
|
return invoke;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static InsnArg buildLookupArg(RootNode root) {
|
||||||
|
ArgType lookupType = ArgType.object("java.lang.invoke.MethodHandles.Lookup");
|
||||||
|
ClassInfo cls = ClassInfo.fromName(root, "java.lang.invoke.MethodHandles");
|
||||||
|
MethodInfo mth = MethodInfo.fromDetails(root, cls, "lookup", Collections.emptyList(), lookupType);
|
||||||
|
return InsnArg.wrapArg(new InvokeNode(mth, InvokeType.STATIC, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getFindMethodName(MethodHandleType type) {
|
||||||
|
switch (type) {
|
||||||
|
case INVOKE_STATIC:
|
||||||
|
return "findStatic";
|
||||||
|
case INVOKE_CONSTRUCTOR:
|
||||||
|
return "findConstructor";
|
||||||
|
case INVOKE_INSTANCE:
|
||||||
|
case INVOKE_DIRECT:
|
||||||
|
case INVOKE_INTERFACE:
|
||||||
|
return "findVirtual";
|
||||||
|
|
||||||
|
default:
|
||||||
|
return "<" + type + '>';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,8 +29,12 @@ public final class InsnList implements Iterable<InsnNode> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static int getIndex(List<InsnNode> list, InsnNode insn) {
|
public static int getIndex(List<InsnNode> list, InsnNode insn) {
|
||||||
|
return getIndex(list, insn, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getIndex(List<InsnNode> list, InsnNode insn, int startOffset) {
|
||||||
int size = list.size();
|
int size = list.size();
|
||||||
for (int i = 0; i < size; i++) {
|
for (int i = startOffset; i < size; i++) {
|
||||||
if (list.get(i) == insn) {
|
if (list.get(i) == insn) {
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
@@ -38,6 +42,14 @@ public final class InsnList implements Iterable<InsnNode> {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean contains(List<InsnNode> list, InsnNode insn) {
|
||||||
|
return getIndex(list, insn, 0) != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean contains(List<InsnNode> list, InsnNode insn, int startOffset) {
|
||||||
|
return getIndex(list, insn, startOffset) != -1;
|
||||||
|
}
|
||||||
|
|
||||||
public int getIndex(InsnNode insn) {
|
public int getIndex(InsnNode insn) {
|
||||||
return getIndex(list, insn);
|
return getIndex(list, insn);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import jadx.core.dex.instructions.args.InsnWrapArg;
|
|||||||
import jadx.core.dex.instructions.args.RegisterArg;
|
import jadx.core.dex.instructions.args.RegisterArg;
|
||||||
import jadx.core.dex.instructions.args.SSAVar;
|
import jadx.core.dex.instructions.args.SSAVar;
|
||||||
import jadx.core.dex.nodes.BlockNode;
|
import jadx.core.dex.nodes.BlockNode;
|
||||||
|
import jadx.core.dex.nodes.IContainer;
|
||||||
import jadx.core.dex.nodes.InsnNode;
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
@@ -228,6 +229,18 @@ public class InsnRemover {
|
|||||||
removeAll(block.getInstructions(), insns);
|
removeAll(block.getInstructions(), insns);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void removeAllAndUnbind(MethodNode mth, IContainer container, List<InsnNode> insns) {
|
||||||
|
unbindInsns(mth, insns);
|
||||||
|
RegionUtils.visitBlocks(mth, container, b -> removeAll(b.getInstructions(), insns));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void removeAllAndUnbind(MethodNode mth, List<InsnNode> insns) {
|
||||||
|
unbindInsns(mth, insns);
|
||||||
|
for (BlockNode block : mth.getBasicBlocks()) {
|
||||||
|
removeAll(block.getInstructions(), insns);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void removeAllWithoutUnbind(BlockNode block, List<InsnNode> insns) {
|
public static void removeAllWithoutUnbind(BlockNode block, List<InsnNode> insns) {
|
||||||
removeAll(block.getInstructions(), insns);
|
removeAll(block.getInstructions(), insns);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package jadx.core.utils;
|
package jadx.core.utils;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.function.Function;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
@@ -140,6 +141,37 @@ public class InsnUtils {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void replaceInsns(MethodNode mth, Function<InsnNode, InsnNode> replaceFunction) {
|
||||||
|
for (BlockNode block : mth.getBasicBlocks()) {
|
||||||
|
List<InsnNode> insns = block.getInstructions();
|
||||||
|
int insnsCount = insns.size();
|
||||||
|
for (int i = 0; i < insnsCount; i++) {
|
||||||
|
InsnNode insn = insns.get(i);
|
||||||
|
replaceInsnsInInsn(mth, insn, replaceFunction);
|
||||||
|
InsnNode replace = replaceFunction.apply(insn);
|
||||||
|
if (replace != null) {
|
||||||
|
BlockUtils.replaceInsn(mth, block, i, replace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void replaceInsnsInInsn(MethodNode mth, InsnNode insn, Function<InsnNode, InsnNode> replaceFunction) {
|
||||||
|
int argsCount = insn.getArgsCount();
|
||||||
|
for (int i = 0; i < argsCount; i++) {
|
||||||
|
InsnArg arg = insn.getArg(i);
|
||||||
|
if (arg.isInsnWrap()) {
|
||||||
|
InsnNode wrapInsn = ((InsnWrapArg) arg).getWrapInsn();
|
||||||
|
replaceInsnsInInsn(mth, wrapInsn, replaceFunction);
|
||||||
|
InsnNode replace = replaceFunction.apply(wrapInsn);
|
||||||
|
if (replace != null) {
|
||||||
|
InsnRemover.unbindArgUsage(mth, arg);
|
||||||
|
insn.setArg(i, InsnArg.wrapInsnIntoArg(replace));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public static RegisterArg getRegFromInsn(List<RegisterArg> regs, InsnType insnType) {
|
public static RegisterArg getRegFromInsn(List<RegisterArg> regs, InsnType insnType) {
|
||||||
for (RegisterArg reg : regs) {
|
for (RegisterArg reg : regs) {
|
||||||
|
|||||||
@@ -160,6 +160,10 @@ public class ListUtils {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static <T> boolean noneMatch(Collection<T> list, Predicate<T> test) {
|
||||||
|
return !anyMatch(list, test);
|
||||||
|
}
|
||||||
|
|
||||||
public static <T> boolean anyMatch(Collection<T> list, Predicate<T> test) {
|
public static <T> boolean anyMatch(Collection<T> list, Predicate<T> test) {
|
||||||
if (list == null || list.isEmpty()) {
|
if (list == null || list.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
@@ -22,9 +23,12 @@ import jadx.core.dex.nodes.IRegion;
|
|||||||
import jadx.core.dex.nodes.InsnNode;
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
import jadx.core.dex.regions.Region;
|
import jadx.core.dex.regions.Region;
|
||||||
|
import jadx.core.dex.regions.loops.LoopRegion;
|
||||||
import jadx.core.dex.trycatch.CatchAttr;
|
import jadx.core.dex.trycatch.CatchAttr;
|
||||||
import jadx.core.dex.trycatch.ExceptionHandler;
|
import jadx.core.dex.trycatch.ExceptionHandler;
|
||||||
import jadx.core.dex.trycatch.TryCatchBlockAttr;
|
import jadx.core.dex.trycatch.TryCatchBlockAttr;
|
||||||
|
import jadx.core.dex.visitors.regions.AbstractRegionVisitor;
|
||||||
|
import jadx.core.dex.visitors.regions.DepthRegionTraversal;
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
public class RegionUtils {
|
public class RegionUtils {
|
||||||
@@ -145,20 +149,6 @@ public class RegionUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public static IContainer getLastRegion(@Nullable IContainer container) {
|
|
||||||
if (container == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (container instanceof IBlock || container instanceof IBranchRegion) {
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
if (container instanceof IRegion) {
|
|
||||||
return getLastRegion(Utils.last(((IRegion) container).getSubBlocks()));
|
|
||||||
}
|
|
||||||
throw new JadxRuntimeException(unknownContainerType(container));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isExitBlock(MethodNode mth, IContainer container) {
|
public static boolean isExitBlock(MethodNode mth, IContainer container) {
|
||||||
if (container instanceof BlockNode) {
|
if (container instanceof BlockNode) {
|
||||||
return BlockUtils.isExitBlock(mth, (BlockNode) container);
|
return BlockUtils.isExitBlock(mth, (BlockNode) container);
|
||||||
@@ -289,7 +279,11 @@ public class RegionUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
} else if (container instanceof IRegion) {
|
}
|
||||||
|
if (container instanceof LoopRegion) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (container instanceof IRegion) {
|
||||||
IRegion region = (IRegion) container;
|
IRegion region = (IRegion) container;
|
||||||
for (IContainer block : region.getSubBlocks()) {
|
for (IContainer block : region.getSubBlocks()) {
|
||||||
if (notEmpty(block)) {
|
if (notEmpty(block)) {
|
||||||
@@ -297,9 +291,8 @@ public class RegionUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
} else {
|
|
||||||
throw new JadxRuntimeException(unknownContainerType(container));
|
|
||||||
}
|
}
|
||||||
|
throw new JadxRuntimeException(unknownContainerType(container));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void getAllRegionBlocks(IContainer container, Set<IBlock> blocks) {
|
public static void getAllRegionBlocks(IContainer container, Set<IBlock> blocks) {
|
||||||
@@ -483,4 +476,13 @@ public class RegionUtils {
|
|||||||
}
|
}
|
||||||
return "Unknown container type: " + container.getClass();
|
return "Unknown container type: " + container.getClass();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void visitBlocks(MethodNode mth, IContainer container, Consumer<IBlock> visitor) {
|
||||||
|
DepthRegionTraversal.traverse(mth, container, new AbstractRegionVisitor() {
|
||||||
|
@Override
|
||||||
|
public void processBlock(MethodNode mth, IBlock block) {
|
||||||
|
visitor.accept(block);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ public class AndroidResourcesUtils {
|
|||||||
FieldNode newResField = new FieldNode(typeCls, rFieldInfo,
|
FieldNode newResField = new FieldNode(typeCls, rFieldInfo,
|
||||||
AccessFlags.PUBLIC | AccessFlags.STATIC | AccessFlags.FINAL);
|
AccessFlags.PUBLIC | AccessFlags.STATIC | AccessFlags.FINAL);
|
||||||
newResField.addAttr(new EncodedValue(EncodedType.ENCODED_INT, resource.getId()));
|
newResField.addAttr(new EncodedValue(EncodedType.ENCODED_INT, resource.getId()));
|
||||||
typeCls.getFields().add(newResField);
|
typeCls.addField(newResField);
|
||||||
if (rClsExists) {
|
if (rClsExists) {
|
||||||
newResField.addInfoComment("Added by JADX");
|
newResField.addInfoComment("Added by JADX");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,12 +10,16 @@ import java.io.InputStream;
|
|||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.FileVisitOption;
|
import java.nio.file.FileVisitOption;
|
||||||
|
import java.nio.file.FileVisitResult;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
import java.nio.file.SimpleFileVisitor;
|
||||||
|
import java.nio.file.StandardOpenOption;
|
||||||
|
import java.nio.file.attribute.BasicFileAttributes;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Comparator;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.jar.JarEntry;
|
import java.util.jar.JarEntry;
|
||||||
@@ -108,13 +112,8 @@ public class FileUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static boolean deleteDir(File dir) {
|
public static boolean deleteDir(File dir) {
|
||||||
File[] content = dir.listFiles();
|
deleteDir(dir.toPath());
|
||||||
if (content != null) {
|
return true;
|
||||||
for (File file : content) {
|
|
||||||
deleteDir(file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dir.delete();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void deleteDirIfExists(Path dir) {
|
public static void deleteDirIfExists(Path dir) {
|
||||||
@@ -127,16 +126,23 @@ public class FileUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final SimpleFileVisitor<Path> FILE_DELETE_VISITOR = new SimpleFileVisitor<Path>() {
|
||||||
|
@Override
|
||||||
|
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||||
|
Files.delete(file);
|
||||||
|
return FileVisitResult.CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
|
||||||
|
Files.delete(dir);
|
||||||
|
return FileVisitResult.CONTINUE;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private static void deleteDir(Path dir) {
|
private static void deleteDir(Path dir) {
|
||||||
try (Stream<Path> pathStream = Files.walk(dir)) {
|
try {
|
||||||
pathStream.sorted(Comparator.reverseOrder())
|
Files.walkFileTree(dir, Collections.emptySet(), Integer.MAX_VALUE, FILE_DELETE_VISITOR);
|
||||||
.forEach(path -> {
|
|
||||||
try {
|
|
||||||
Files.delete(path);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new JadxRuntimeException("Failed to delete path: " + path.toAbsolutePath(), e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new JadxRuntimeException("Failed to delete directory " + dir, e);
|
throw new JadxRuntimeException("Failed to delete directory " + dir, e);
|
||||||
}
|
}
|
||||||
@@ -228,7 +234,8 @@ public class FileUtils {
|
|||||||
|
|
||||||
public static void writeFile(Path file, String data) throws IOException {
|
public static void writeFile(Path file, String data) throws IOException {
|
||||||
FileUtils.makeDirsForFile(file);
|
FileUtils.makeDirsForFile(file);
|
||||||
Files.write(file, data.getBytes(StandardCharsets.UTF_8));
|
Files.write(file, data.getBytes(StandardCharsets.UTF_8),
|
||||||
|
StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String readFile(Path textFile) throws IOException {
|
public static String readFile(Path textFile) throws IOException {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import java.util.Map;
|
|||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -51,7 +52,7 @@ public class BinaryXMLParser extends CommonBinaryParser {
|
|||||||
private boolean isLastEnd = true;
|
private boolean isLastEnd = true;
|
||||||
private boolean isOneLine = true;
|
private boolean isOneLine = true;
|
||||||
private int namespaceDepth = 0;
|
private int namespaceDepth = 0;
|
||||||
private int[] resourceIds;
|
private @Nullable int[] resourceIds;
|
||||||
|
|
||||||
private final RootNode rootNode;
|
private final RootNode rootNode;
|
||||||
private String appPackageName;
|
private String appPackageName;
|
||||||
@@ -358,7 +359,7 @@ public class BinaryXMLParser extends CommonBinaryParser {
|
|||||||
// As the outcome of https://github.com/skylot/jadx/issues/1208
|
// As the outcome of https://github.com/skylot/jadx/issues/1208
|
||||||
// Android seems to favor entries from AndroidResMap and only if
|
// Android seems to favor entries from AndroidResMap and only if
|
||||||
// there is no entry uses the values form the XML string pool
|
// there is no entry uses the values form the XML string pool
|
||||||
if (0 <= id && id < resourceIds.length) {
|
if (resourceIds != null && 0 <= id && id < resourceIds.length) {
|
||||||
int resId = resourceIds[id];
|
int resId = resourceIds[id];
|
||||||
String str = ValuesParser.getAndroidResMap().get(resId);
|
String str = ValuesParser.getAndroidResMap().get(resId);
|
||||||
if (str != null) {
|
if (str != null) {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ public class CommonBinaryParser extends ParserConstants {
|
|||||||
int[] stringsOffset = is.readInt32Array(stringCount);
|
int[] stringsOffset = is.readInt32Array(stringCount);
|
||||||
int[] stylesOffset = is.readInt32Array(styleCount);
|
int[] stylesOffset = is.readInt32Array(styleCount);
|
||||||
|
|
||||||
is.checkPos(start + stringsStart, "Expected strings start");
|
is.skipToPos(start + stringsStart, "Expected strings start");
|
||||||
String[] strings = new String[stringCount];
|
String[] strings = new String[stringCount];
|
||||||
byte[] strData = is.readInt8Array((int) (chunkEnd - is.getPos()));
|
byte[] strData = is.readInt8Array((int) (chunkEnd - is.getPos()));
|
||||||
if ((flags & UTF8_FLAG) != 0) {
|
if ((flags & UTF8_FLAG) != 0) {
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package jadx.core.xmlgen;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
public interface IResParser {
|
||||||
|
|
||||||
|
void decode(InputStream inputStream) throws IOException;
|
||||||
|
|
||||||
|
ResourceStorage getResStorage();
|
||||||
|
|
||||||
|
String[] getStrings();
|
||||||
|
}
|
||||||
@@ -4,10 +4,8 @@ import java.io.InputStream;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import javax.xml.parsers.DocumentBuilder;
|
import javax.xml.parsers.DocumentBuilder;
|
||||||
|
|
||||||
@@ -19,6 +17,9 @@ import org.w3c.dom.Node;
|
|||||||
import org.w3c.dom.NodeList;
|
import org.w3c.dom.NodeList;
|
||||||
|
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
import jadx.core.xmlgen.entry.RawNamedValue;
|
||||||
|
import jadx.core.xmlgen.entry.ResourceEntry;
|
||||||
|
import jadx.core.xmlgen.entry.ValuesParser;
|
||||||
|
|
||||||
public class ManifestAttributes {
|
public class ManifestAttributes {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(ManifestAttributes.class);
|
private static final Logger LOG = LoggerFactory.getLogger(ManifestAttributes.class);
|
||||||
@@ -54,6 +55,8 @@ public class ManifestAttributes {
|
|||||||
|
|
||||||
private final Map<String, MAttr> attrMap = new HashMap<>();
|
private final Map<String, MAttr> attrMap = new HashMap<>();
|
||||||
|
|
||||||
|
private final Map<String, MAttr> appAttrMap = new HashMap<>();
|
||||||
|
|
||||||
private static ManifestAttributes instance;
|
private static ManifestAttributes instance;
|
||||||
|
|
||||||
public static ManifestAttributes getInstance() {
|
public static ManifestAttributes getInstance() {
|
||||||
@@ -170,12 +173,15 @@ public class ManifestAttributes {
|
|||||||
public String decode(String attrName, long value) {
|
public String decode(String attrName, long value) {
|
||||||
MAttr attr = attrMap.get(attrName);
|
MAttr attr = attrMap.get(attrName);
|
||||||
if (attr == null) {
|
if (attr == null) {
|
||||||
return null;
|
attr = appAttrMap.get(attrName);
|
||||||
|
if (attr == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (attr.getType() == MAttrType.ENUM) {
|
if (attr.getType() == MAttrType.ENUM) {
|
||||||
return attr.getValues().get(value);
|
return attr.getValues().get(value);
|
||||||
} else if (attr.getType() == MAttrType.FLAG) {
|
} else if (attr.getType() == MAttrType.FLAG) {
|
||||||
List<String> flagList = new LinkedList<>();
|
List<String> flagList = new ArrayList<>();
|
||||||
List<Long> attrKeys = new ArrayList<>(attr.getValues().keySet());
|
List<Long> attrKeys = new ArrayList<>(attr.getValues().keySet());
|
||||||
attrKeys.sort((a, b) -> Long.compare(b, a)); // sort descending
|
attrKeys.sort((a, b) -> Long.compare(b, a)); // sort descending
|
||||||
for (Long key : attrKeys) {
|
for (Long key : attrKeys) {
|
||||||
@@ -188,8 +194,36 @@ public class ManifestAttributes {
|
|||||||
value ^= key;
|
value ^= key;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return flagList.stream().collect(Collectors.joining("|"));
|
return String.join("|", flagList);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void updateAttributes(IResParser parser) {
|
||||||
|
appAttrMap.clear();
|
||||||
|
|
||||||
|
ResourceStorage resStorage = parser.getResStorage();
|
||||||
|
ValuesParser vp = new ValuesParser(parser.getStrings(), resStorage.getResourcesNames());
|
||||||
|
|
||||||
|
for (ResourceEntry ri : resStorage.getResources()) {
|
||||||
|
if (ri.getTypeName().equals("attr") && ri.getNamedValues().size() > 1) {
|
||||||
|
RawNamedValue first = ri.getNamedValues().get(0);
|
||||||
|
MAttrType attrTyp;
|
||||||
|
if (first.getRawValue().getData() == ValuesParser.ATTR_TYPE_FLAGS) {
|
||||||
|
attrTyp = MAttrType.FLAG;
|
||||||
|
} else if (first.getRawValue().getData() == ValuesParser.ATTR_TYPE_ENUM || first.getRawValue().getData() == 65600) {
|
||||||
|
attrTyp = MAttrType.ENUM;
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
MAttr attr = new MAttr(attrTyp);
|
||||||
|
for (int i = 1; i < ri.getNamedValues().size(); i++) {
|
||||||
|
RawNamedValue rv = ri.getNamedValues().get(i);
|
||||||
|
String value = vp.decodeNameRef(rv.getNameRef());
|
||||||
|
attr.getValues().put((long) rv.getRawValue().getData(), value.startsWith("id.") ? value.substring(3) : value);
|
||||||
|
}
|
||||||
|
appAttrMap.put(ri.getKeyName(), attr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user